mirror of
https://github.com/docmost/docmost.git
synced 2025-11-12 16:12:39 +10:00
feat: add new languages to selection (#626)
* Add new languages to selection * more translations
This commit is contained in:
@ -226,7 +226,11 @@
|
|||||||
"Select a group": "Select a group",
|
"Select a group": "Select a group",
|
||||||
"Export all pages and attachments in this space.": "Export all pages and attachments in this space.",
|
"Export all pages and attachments in this space.": "Export all pages and attachments in this space.",
|
||||||
"Delete space": "Delete space",
|
"Delete space": "Delete space",
|
||||||
|
"Are you sure you want to delete this space?": "Are you sure you want to delete this space?",
|
||||||
"Delete this space with all its pages and data.": "Delete this space with all its pages and data.",
|
"Delete this space with all its pages and data.": "Delete this space with all its pages and data.",
|
||||||
|
"All pages, comments, attachments and permissions in this space will be deleted irreversibly.": "All pages, comments, attachments and permissions in this space will be deleted irreversibly.",
|
||||||
|
"Confirm space name": "Confirm space name",
|
||||||
|
"Type the space name <b>{{spaceName}}</b> to confirm your action.": "Type the space name <b>{{spaceName}}</b> to confirm your action.",
|
||||||
"Format": "Format",
|
"Format": "Format",
|
||||||
"Include subpages": "Include subpages",
|
"Include subpages": "Include subpages",
|
||||||
"Include attachments": "Include attachments",
|
"Include attachments": "Include attachments",
|
||||||
@ -331,5 +335,8 @@
|
|||||||
"Multiple": "Multiple",
|
"Multiple": "Multiple",
|
||||||
"Heading {{level}}": "Heading {{level}}",
|
"Heading {{level}}": "Heading {{level}}",
|
||||||
"Toggle title": "Toggle title",
|
"Toggle title": "Toggle title",
|
||||||
"Write anything. Enter \"/\" for commands": "Write anything. Enter \"/\" for commands"
|
"Write anything. Enter \"/\" for commands": "Write anything. Enter \"/\" for commands",
|
||||||
|
"Names do not match": "Names do not match",
|
||||||
|
"Today, {{time}}": "Today, {{time}}",
|
||||||
|
"Yesterday, {{time}}": "Yesterday, {{time}}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,8 +64,8 @@ import clojure from "highlight.js/lib/languages/clojure";
|
|||||||
import fortran from "highlight.js/lib/languages/fortran";
|
import fortran from "highlight.js/lib/languages/fortran";
|
||||||
import haskell from "highlight.js/lib/languages/haskell";
|
import haskell from "highlight.js/lib/languages/haskell";
|
||||||
import scala from "highlight.js/lib/languages/scala";
|
import scala from "highlight.js/lib/languages/scala";
|
||||||
import i18n from "@/i18n.ts";
|
|
||||||
import { MarkdownClipboard } from "@/features/editor/extensions/markdown-clipboard.ts";
|
import { MarkdownClipboard } from "@/features/editor/extensions/markdown-clipboard.ts";
|
||||||
|
import i18n from "i18next";
|
||||||
|
|
||||||
const lowlight = createLowlight(common);
|
const lowlight = createLowlight(common);
|
||||||
lowlight.register("mermaid", plaintext);
|
lowlight.register("mermaid", plaintext);
|
||||||
|
|||||||
@ -1,27 +1,29 @@
|
|||||||
import { Button, Divider, Group, Modal, Text, TextInput } from '@mantine/core';
|
import { Button, Divider, Group, Modal, Text, TextInput } from "@mantine/core";
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
import { useDeleteSpaceMutation } from '../queries/space-query';
|
import { useDeleteSpaceMutation } from "../queries/space-query";
|
||||||
import { useField } from '@mantine/form';
|
import { useField } from "@mantine/form";
|
||||||
import { ISpace } from '../types/space.types';
|
import { ISpace } from "../types/space.types";
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from "react-router-dom";
|
||||||
import APP_ROUTE from '@/lib/app-route';
|
import APP_ROUTE from "@/lib/app-route";
|
||||||
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface DeleteSpaceModalProps {
|
interface DeleteSpaceModalProps {
|
||||||
space: ISpace;
|
space: ISpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
const deleteSpaceMutation = useDeleteSpaceMutation();
|
const deleteSpaceMutation = useDeleteSpaceMutation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const confirmNameField = useField({
|
const confirmNameField = useField({
|
||||||
initialValue: '',
|
initialValue: "",
|
||||||
validateOnChange: true,
|
validateOnChange: true,
|
||||||
validate: (value) =>
|
validate: (value) =>
|
||||||
value.trim().toLowerCase() === space.name.trim().toLocaleLowerCase()
|
value.trim().toLowerCase() === space.name.trim().toLocaleLowerCase()
|
||||||
? null
|
? null
|
||||||
: 'Names do not match',
|
: t("Names do not match"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
@ -38,46 +40,47 @@ export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
|||||||
await deleteSpaceMutation.mutateAsync({ id: space.id, slug: space.slug });
|
await deleteSpaceMutation.mutateAsync({ id: space.id, slug: space.slug });
|
||||||
navigate(APP_ROUTE.HOME);
|
navigate(APP_ROUTE.HOME);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete space', error);
|
console.error("Failed to delete space", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={open} variant="light" color="red">
|
<Button onClick={open} variant="light" color="red">
|
||||||
Delete
|
{t("Delete")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
title="Are you sure you want to delete this space?"
|
title={t("Are you sure you want to delete this space?")}
|
||||||
>
|
>
|
||||||
<Divider size="xs" mb="xs" />
|
<Divider size="xs" mb="xs" />
|
||||||
<Text>
|
<Text>
|
||||||
All pages, comments, attachments and permissions in this space will be
|
{t(
|
||||||
deleted irreversibly.
|
"All pages, comments, attachments and permissions in this space will be deleted irreversibly.",
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text mt="sm">
|
<Text mt="sm">
|
||||||
Type the space name{' '}
|
<Trans
|
||||||
<Text span fw={500}>
|
defaults="Type the space name <b>{{spaceName}}</b> to confirm your action."
|
||||||
'{space.name}'
|
values={{ spaceName: space.name }}
|
||||||
</Text>{' '}
|
components={{ b: <Text span fw={500} /> }}
|
||||||
to confirm your action.
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
{...confirmNameField.getInputProps()}
|
{...confirmNameField.getInputProps()}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
placeholder="Confirm space name"
|
placeholder={t("Confirm space name")}
|
||||||
py="sm"
|
py="sm"
|
||||||
data-autofocus
|
data-autofocus
|
||||||
/>
|
/>
|
||||||
<Group justify="flex-end" mt="md">
|
<Group justify="flex-end" mt="md">
|
||||||
<Button onClick={close} variant="default">
|
<Button onClick={close} variant="default">
|
||||||
Cancel
|
{t("Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleDelete} color="red">
|
<Button onClick={handleDelete} color="red">
|
||||||
Confirm
|
{t("Confirm")}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@ -82,7 +82,7 @@ export function SpaceSidebar() {
|
|||||||
classes.menu,
|
classes.menu,
|
||||||
location.pathname.toLowerCase() === getSpaceUrl(spaceSlug)
|
location.pathname.toLowerCase() === getSpaceUrl(spaceSlug)
|
||||||
? classes.activeButton
|
? classes.activeButton
|
||||||
: ""
|
: "",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={classes.menuItemInner}>
|
<div className={classes.menuItemInner}>
|
||||||
@ -119,7 +119,7 @@ export function SpaceSidebar() {
|
|||||||
|
|
||||||
{spaceAbility.can(
|
{spaceAbility.can(
|
||||||
SpaceCaslAction.Manage,
|
SpaceCaslAction.Manage,
|
||||||
SpaceCaslSubject.Page
|
SpaceCaslSubject.Page,
|
||||||
) && (
|
) && (
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
className={classes.menu}
|
className={classes.menu}
|
||||||
@ -146,7 +146,7 @@ export function SpaceSidebar() {
|
|||||||
|
|
||||||
{spaceAbility.can(
|
{spaceAbility.can(
|
||||||
SpaceCaslAction.Manage,
|
SpaceCaslAction.Manage,
|
||||||
SpaceCaslSubject.Page
|
SpaceCaslSubject.Page,
|
||||||
) && (
|
) && (
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<SpaceMenu spaceId={space.id} onSpaceSettings={openSettings} />
|
<SpaceMenu spaceId={space.id} onSpaceSettings={openSettings} />
|
||||||
@ -170,7 +170,7 @@ export function SpaceSidebar() {
|
|||||||
spaceId={space.id}
|
spaceId={space.id}
|
||||||
readOnly={spaceAbility.cannot(
|
readOnly={spaceAbility.cannot(
|
||||||
SpaceCaslAction.Manage,
|
SpaceCaslAction.Manage,
|
||||||
SpaceCaslSubject.Page
|
SpaceCaslSubject.Page,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -230,7 +230,7 @@ function SpaceMenu({ spaceId, onSpaceSettings }: SpaceMenuProps) {
|
|||||||
onClick={openExportModal}
|
onClick={openExportModal}
|
||||||
leftSection={<IconFileExport size={16} />}
|
leftSection={<IconFileExport size={16} />}
|
||||||
>
|
>
|
||||||
Export space
|
{t("Export space")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export default function SpaceDetails({ spaceId, readOnly }: SpaceDetailsProps) {
|
|||||||
|
|
||||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
<Group justify="space-between" wrap="nowrap" gap="xl">
|
||||||
<div>
|
<div>
|
||||||
<Text size="md">Export space</Text>
|
<Text size="md">{t("Export space")}</Text>
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
{t("Export all pages and attachments in this space.")}
|
{t("Export all pages and attachments in this space.")}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@ -1,20 +1,21 @@
|
|||||||
import { IRoleData, SpaceRole } from "@/lib/types.ts";
|
import { IRoleData, SpaceRole } from "@/lib/types.ts";
|
||||||
|
import i18n from "i18next";
|
||||||
|
|
||||||
export const spaceRoleData: IRoleData[] = [
|
export const spaceRoleData: IRoleData[] = [
|
||||||
{
|
{
|
||||||
label: "Full access",
|
label: i18n.t("Full access"),
|
||||||
value: SpaceRole.ADMIN,
|
value: SpaceRole.ADMIN,
|
||||||
description: "Has full access to space settings and pages",
|
description: i18n.t("Has full access to space settings and pages."),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Can edit",
|
label: i18n.t("Can edit"),
|
||||||
value: SpaceRole.WRITER,
|
value: SpaceRole.WRITER,
|
||||||
description: "Can create and edit pages in space",
|
description: i18n.t("Can create and edit pages in space."),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Can view",
|
label: i18n.t("Can view"),
|
||||||
value: SpaceRole.READER,
|
value: SpaceRole.READER,
|
||||||
description: "Can view pages in space but not edit",
|
description: i18n.t("Can view pages in space but not edit."),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -42,6 +42,9 @@ function LanguageSwitcher() {
|
|||||||
label={t("Select language")}
|
label={t("Select language")}
|
||||||
data={[
|
data={[
|
||||||
{ value: "en-US", label: "English (United States)" },
|
{ value: "en-US", label: "English (United States)" },
|
||||||
|
{ value: "de-DE", label: "Deutsch (Germany)" },
|
||||||
|
{ value: "fr-FR", label: "Français (France)" },
|
||||||
|
{ value: "pt-BR", label: "Português (Brazilian)" },
|
||||||
{ value: "zh-CN", label: "中文 (简体)" },
|
{ value: "zh-CN", label: "中文 (简体)" },
|
||||||
]}
|
]}
|
||||||
value={language}
|
value={language}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { formatDistanceStrict } from "date-fns";
|
import { formatDistanceStrict } from "date-fns";
|
||||||
import { format, isToday, isYesterday } from "date-fns";
|
import { format, isToday, isYesterday } from "date-fns";
|
||||||
|
import i18n from "i18next";
|
||||||
|
|
||||||
export function timeAgo(date: Date) {
|
export function timeAgo(date: Date) {
|
||||||
return formatDistanceStrict(new Date(date), new Date(), { addSuffix: true });
|
return formatDistanceStrict(new Date(date), new Date(), { addSuffix: true });
|
||||||
@ -7,9 +8,9 @@ export function timeAgo(date: Date) {
|
|||||||
|
|
||||||
export function formattedDate(date: Date) {
|
export function formattedDate(date: Date) {
|
||||||
if (isToday(date)) {
|
if (isToday(date)) {
|
||||||
return `Today, ${format(date, "h:mma")}`;
|
return i18n.t("Today, {{time}}", { time: format(date, "h:mma") });
|
||||||
} else if (isYesterday(date)) {
|
} else if (isYesterday(date)) {
|
||||||
return `Yesterday, ${format(date, "h:mma")}`;
|
return i18n.t("Yesterday, {{time}}", { time: format(date, "h:mma") });
|
||||||
} else {
|
} else {
|
||||||
return format(date, "MMM dd, yyyy, h:mma");
|
return format(date, "MMM dd, yyyy, h:mma");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import SettingsTitle from "@/components/settings/settings-title.tsx";
|
import SettingsTitle from "@/components/settings/settings-title.tsx";
|
||||||
import AccountLanguage from "@/features/user/components/account-languate";
|
import AccountLanguage from "@/features/user/components/account-language.tsx";
|
||||||
import AccountTheme from "@/features/user/components/account-theme.tsx";
|
import AccountTheme from "@/features/user/components/account-theme.tsx";
|
||||||
import PageWidthPref from "@/features/user/components/page-width-pref.tsx";
|
import PageWidthPref from "@/features/user/components/page-width-pref.tsx";
|
||||||
import { Divider } from "@mantine/core";
|
import { Divider } from "@mantine/core";
|
||||||
|
|||||||
Reference in New Issue
Block a user