diff --git a/apps/venus/src/app/i18n/resources/en.json b/apps/venus/src/app/i18n/resources/en.json index cd7a377352..2cffda93bd 100644 --- a/apps/venus/src/app/i18n/resources/en.json +++ b/apps/venus/src/app/i18n/resources/en.json @@ -1,5 +1,7 @@ { "translation": { + "Blog": "Blog", + "AboutUs": "About AboutUs", "Open Source": "Open Source", "Privacy First": "Privacy First", "Alternative": "Alternative", diff --git a/apps/venus/src/app/i18n/resources/zh.json b/apps/venus/src/app/i18n/resources/zh.json index eb4cf432dd..ca0a060072 100644 --- a/apps/venus/src/app/i18n/resources/zh.json +++ b/apps/venus/src/app/i18n/resources/zh.json @@ -1,5 +1,7 @@ { "translation": { + "Blog": "博客", + "AboutUs": "关于我们", "Open Source": "开源", "Privacy First": "隐私第一", "Alternative": "的另一种选择", diff --git a/libs/components/layout/package.json b/libs/components/layout/package.json index a36a746d84..d7a0a26481 100644 --- a/libs/components/layout/package.json +++ b/libs/components/layout/package.json @@ -11,7 +11,9 @@ "@mui/icons-material": "^5.8.4", "clsx": "^1.2.1", "date-fns": "^2.29.2", + "i18next": "^21.9.1", "jotai": "^1.8.1", + "react-i18next": "^11.18.4", "tinycolor2": "^1.4.2", "turndown": "7.1.1" }, diff --git a/libs/components/layout/src/header/FileSystem.tsx b/libs/components/layout/src/header/FileSystem.tsx index 016b26d1c4..30c2b68816 100644 --- a/libs/components/layout/src/header/FileSystem.tsx +++ b/libs/components/layout/src/header/FileSystem.tsx @@ -4,7 +4,7 @@ import { CloseIcon } from '@toeverything/components/common'; import { IconButton, MuiSnackbar, styled } from '@toeverything/components/ui'; import { services } from '@toeverything/datasource/db-service'; import { useLocalTrigger } from '@toeverything/datasource/state'; - +import { useTranslation } from 'react-i18next'; const cleanupWorkspace = (workspace: string) => new Promise((resolve, reject) => { const req = indexedDB.deleteDatabase(workspace); @@ -75,7 +75,7 @@ export const FileSystem = () => { setError(true); setTimeout(() => setError(false), 3000); }, []); - + const { t } = useTranslation(); const apiSupported = useMemo(() => fsApiSupported(), []); if (apiSupported && !selected) { @@ -105,7 +105,7 @@ export const FileSystem = () => { } }} > - Sync to Disk + {t('Sync to Disk')} ); diff --git a/libs/components/layout/src/header/LayoutHeader.tsx b/libs/components/layout/src/header/LayoutHeader.tsx index 2d6913ac60..9619c97c84 100644 --- a/libs/components/layout/src/header/LayoutHeader.tsx +++ b/libs/components/layout/src/header/LayoutHeader.tsx @@ -12,6 +12,7 @@ import { useShowSettingsSidebar, } from '@toeverything/datasource/state'; +import { useTranslation } from 'react-i18next'; import { EditorBoardSwitcher } from './EditorBoardSwitcher'; import { FileSystem, fsApiSupported } from './FileSystem'; import { CurrentPageTitle } from './Title'; @@ -20,16 +21,17 @@ export const LayoutHeader = () => { const [isLocalWorkspace] = useLocalTrigger(); const { toggleSettingsSidebar: toggleInfoSidebar, showSettingsSidebar } = useShowSettingsSidebar(); + const { t } = useTranslation(); const warningTips = useMemo(() => { if (!fsApiSupported()) { - return 'Welcome to the AFFiNE demo. To begin saving changes you can SYNC DATA TO DISK with the latest version of Chromium based browser like Chrome/Edge'; + return t('WarningTips.IsNotfsApiSupported'); } else if (!isLocalWorkspace) { - return 'Welcome to the AFFiNE demo. To begin saving changes you can SYNC TO DISK.'; + return t('WarningTips.IsNotLocalWorkspace'); } else { - return 'AFFiNE is under active development and the current version is UNSTABLE. Please DO NOT store information or data'; + return t('WarningTips.DoNotStore'); } - }, [isLocalWorkspace]); + }, [isLocalWorkspace, t]); return ( @@ -43,7 +45,7 @@ export const LayoutHeader = () => { - Share + {t('Share')}
{ const { activeCommentId, resolveComment } = useActiveComment(); + const { t } = useTranslation(); const getSettingsTabsData = useCallback((): SettingsTabItemType[] => { return [ { type: 'layout', - text: 'Layout', + text: t('Layout'), icon: ( @@ -31,7 +33,7 @@ export const ContainerTabs = () => { }, { type: 'comment', - text: 'Comment', + text: t('Comment'), icon: ( @@ -48,7 +50,7 @@ export const ContainerTabs = () => { }, { type: 'settings', - text: 'Settings', + text: t('Settings'), icon: ( @@ -57,7 +59,7 @@ export const ContainerTabs = () => { panel: , }, ]; - }, [activeCommentId, resolveComment]); + }, [activeCommentId, resolveComment, t]); const settingsTabsData = useMemo(() => { return getSettingsTabsData(); @@ -67,7 +69,6 @@ export const ContainerTabs = () => { _defaultTabsKeys as unknown as string[], 'settings' ); - return ( <> diff --git a/libs/components/layout/src/settings-sidebar/Settings/SettingsList.tsx b/libs/components/layout/src/settings-sidebar/Settings/SettingsList.tsx index 57e4a2fbf0..a4c99c6fe0 100644 --- a/libs/components/layout/src/settings-sidebar/Settings/SettingsList.tsx +++ b/libs/components/layout/src/settings-sidebar/Settings/SettingsList.tsx @@ -1,8 +1,21 @@ -import { Divider, ListItem, styled, Switch } from '@toeverything/components/ui'; +import { + Divider, + ListItem, + Option, + Select, + styled, + Switch, +} from '@toeverything/components/ui'; +import { useTranslation } from 'react-i18next'; +import { options } from '../../i18n'; import { useSettings } from './use-settings'; export const SettingsList = () => { const settings = useSettings(); + const { i18n } = useTranslation(); + const changeLanguage = event => { + i18n.changeLanguage(event); + }; return ( @@ -15,7 +28,7 @@ export const SettingsList = () => { if (type === 'switch') { return ( { item.onChange(!item.value); }} @@ -31,8 +44,25 @@ export const SettingsList = () => { } return ( - item.onClick()}> + item.onClick()}> {item.name} + {item.key === 'Language' ? ( +
+ +
+ ) : null}
); })} diff --git a/libs/components/layout/src/settings-sidebar/Settings/footer/LastModified.tsx b/libs/components/layout/src/settings-sidebar/Settings/footer/LastModified.tsx index 1bcf8a83be..0c0b5c2efd 100644 --- a/libs/components/layout/src/settings-sidebar/Settings/footer/LastModified.tsx +++ b/libs/components/layout/src/settings-sidebar/Settings/footer/LastModified.tsx @@ -1,6 +1,7 @@ import { styled, Typography } from '@toeverything/components/ui'; import { useUserAndSpaces } from '@toeverything/datasource/state'; import format from 'date-fns/format'; +import { useTranslation } from 'react-i18next'; import { usePageLastUpdated, useWorkspaceAndPageId } from '../util'; export const LastModified = () => { @@ -9,12 +10,12 @@ export const LastModified = () => { const { workspaceId, pageId } = useWorkspaceAndPageId(); const lastModified = usePageLastUpdated({ workspaceId, pageId }); const formatLastModified = format(lastModified, 'MM/dd/yyyy hh:mm'); + const { t } = useTranslation(); return (
- Last edited by - {username} + {t('Last edited by', { name: username })}
diff --git a/libs/components/layout/src/settings-sidebar/Settings/footer/Logout.tsx b/libs/components/layout/src/settings-sidebar/Settings/footer/Logout.tsx index a168f25b6d..3dfc7dffb9 100644 --- a/libs/components/layout/src/settings-sidebar/Settings/footer/Logout.tsx +++ b/libs/components/layout/src/settings-sidebar/Settings/footer/Logout.tsx @@ -2,6 +2,7 @@ import { MoveToIcon } from '@toeverything/components/icons'; import { ListItem, styled, Typography } from '@toeverything/components/ui'; import { LOGOUT_COOKIES, LOGOUT_LOCAL_STORAGE } from '@toeverything/utils'; import { getAuth, signOut } from 'firebase/auth'; +import { useTranslation } from 'react-i18next'; const logout = () => { LOGOUT_LOCAL_STORAGE.forEach(name => localStorage.removeItem(name)); @@ -16,10 +17,11 @@ const logout = () => { }; export const Logout = () => { + const { t } = useTranslation(); return ( - Logout + {t('Logout')} ); }; diff --git a/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts b/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts index 0dd9413c89..dfd8918c49 100644 --- a/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts +++ b/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts @@ -1,9 +1,10 @@ import { message } from '@toeverything/components/ui'; import { copyToClipboard } from '@toeverything/utils'; +import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { useSettingFlags, type SettingFlags } from './use-setting-flags'; + import { - // useReadingMode, clearWorkspace, duplicatePage, exportHtml, @@ -16,6 +17,7 @@ import { interface BaseSettingItem { flag?: keyof SettingFlags; + key: string; } interface SwitchItem extends BaseSettingItem { @@ -64,6 +66,7 @@ export const useSettings = (): SettingItem[] => { const { workspaceId, pageId } = useWorkspaceAndPageId(); const navigate = useNavigate(); const settingFlags = useSettingFlags(); + const { t } = useTranslation(); // const { toggleReadingMode, readingMode } = useReadingMode(); const settings: SettingItem[] = [ @@ -80,7 +83,8 @@ export const useSettings = (): SettingItem[] => { // }, { type: 'button', - name: 'Duplicate Page', + name: t('Duplicate Page'), + key: 'Duplicate Page', onClick: async () => { const newPageInfo = await duplicatePage({ workspaceId, @@ -91,18 +95,29 @@ export const useSettings = (): SettingItem[] => { }, { type: 'button', - name: 'Copy Page Link', + name: t('Copy Page Link'), + key: 'Copy Page Link', onClick: () => { copyToClipboard(window.location.href); message.success('Page link copied successfully'); }, }, + { + type: 'button', + name: t('Language'), + key: 'Language', + onClick: () => { + // Do noting + }, + }, { type: 'separator', + key: 'separator1', }, { type: 'button', - name: 'Export As Markdown', + name: t('Export As Markdown'), + key: 'Export As Markdown', onClick: async () => { const title = await getPageTitle({ workspaceId, pageId }); exportMarkdown({ workspaceId, rootBlockId: pageId, title }); @@ -111,7 +126,8 @@ export const useSettings = (): SettingItem[] => { }, { type: 'button', - name: 'Export As HTML', + name: t('Export As HTML'), + key: 'Export As HTML', onClick: async () => { const title = await getPageTitle({ workspaceId, pageId }); exportHtml({ workspaceId, rootBlockId: pageId, title }); @@ -120,28 +136,33 @@ export const useSettings = (): SettingItem[] => { }, { type: 'button', - name: 'Export As PDF (Unsupported)', + name: t('Export As PDF (Unsupported)'), + key: 'Export As PDF (Unsupported)', onClick: () => console.log('Export As PDF'), flag: 'booleanExportPdf', }, { type: 'separator', + key: 'separator2', }, { type: 'button', - name: 'Import Workspace', + name: t('Import Workspace'), + key: 'Import Workspace', onClick: () => importWorkspace(workspaceId), flag: 'booleanImportWorkspace', }, { type: 'button', - name: 'Export Workspace', + name: t('Export Workspace'), + key: 'Export Workspace', onClick: () => exportWorkspace(), flag: 'booleanExportWorkspace', }, { type: 'button', - name: 'Clear Workspace', + name: t('Clear Workspace'), + key: 'Clear Workspace', onClick: () => clearWorkspace(workspaceId), flag: 'booleanClearWorkspace', }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d023ebe9e..5ed408b554 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -196,10 +196,14 @@ importers: specifiers: '@mui/icons-material': ^5.8.4 firebase: ^9.9.3 + i18next: ^21.9.1 mini-css-extract-plugin: ^2.6.1 + react-i18next: ^11.18.4 webpack: ^5.74.0 dependencies: '@mui/icons-material': 5.8.4 + i18next: 21.9.1 + react-i18next: 11.18.4_i18next@21.9.1 devDependencies: firebase: 9.9.3 mini-css-extract-plugin: 2.6.1_webpack@5.74.0 @@ -501,7 +505,9 @@ importers: '@types/turndown': ^5.0.1 clsx: ^1.2.1 date-fns: ^2.29.2 + i18next: ^21.9.1 jotai: ^1.8.1 + react-i18next: ^11.18.4 tinycolor2: ^1.4.2 turndown: 7.1.1 dependencies: @@ -513,7 +519,9 @@ importers: '@mui/icons-material': 5.8.4 clsx: 1.2.1 date-fns: 2.29.2 + i18next: 21.9.1 jotai: 1.8.1 + react-i18next: 11.18.4_i18next@21.9.1 tinycolor2: 1.4.2 turndown: 7.1.1 devDependencies: @@ -7383,10 +7391,8 @@ packages: indent-string: 4.0.0 dev: true - /ajv-formats/2.1.1_ajv@8.11.0: + /ajv-formats/2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 peerDependenciesMeta: ajv: optional: true @@ -16953,7 +16959,7 @@ packages: dependencies: '@types/json-schema': 7.0.11 ajv: 8.11.0 - ajv-formats: 2.1.1_ajv@8.11.0 + ajv-formats: 2.1.1 ajv-keywords: 5.1.0_ajv@8.11.0 dev: true