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/EditorBoardSwitcher/Switcher.tsx b/libs/components/layout/src/header/EditorBoardSwitcher/Switcher.tsx index 6c8ab70372..d1b5cc2acc 100644 --- a/libs/components/layout/src/header/EditorBoardSwitcher/Switcher.tsx +++ b/libs/components/layout/src/header/EditorBoardSwitcher/Switcher.tsx @@ -1,4 +1,5 @@ import { styled } from '@toeverything/components/ui'; +import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { StatusText } from './StatusText'; import { StatusTrack } from './StatusTrack'; @@ -10,6 +11,7 @@ export const Switcher = () => { const navigate = useNavigate(); const params = useParams(); const { pathname } = useLocation(); + const { t } = useTranslation(); const pageViewMode = isBoard(pathname) ? DocMode.board : DocMode.doc; const switchToPageView = (targetViewMode: DocMode) => { @@ -36,7 +38,7 @@ export const Switcher = () => { active={pageViewMode === DocMode.doc} onClick={() => switchToPageView(DocMode.doc)} > - Paper + {t('Paper')} { active={pageViewMode === DocMode.board} onClick={() => switchToPageView(DocMode.board)} > - Edgeless + {t('Edgeless')} ); 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..8583b7ac76 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')}
+// {options.map(option => ( +// +// ))} +// + +// const { t, i18n } = useTranslation(); +// const changeLanguage = (event: any) => { +// i18n.changeLanguage(event); +// }; diff --git a/libs/components/layout/src/i18n/resources/en.json b/libs/components/layout/src/i18n/resources/en.json new file mode 100644 index 0000000000..d12f3bc040 --- /dev/null +++ b/libs/components/layout/src/i18n/resources/en.json @@ -0,0 +1,30 @@ +{ + "translation": { + "Paper": "Paper", + "Edgeless": "Edgeless", + "Sync to Disk": "Sync to Disk", + "Share": "Share", + "warningTips": { + "isNotfsApiSupported": "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", + "isNotLocalWorkspace": "Welcome to the AFFiNE demo. To begin saving changes you can SYNC TO DISK.", + "DoNotStore": "AFFiNE is under active development and the current version is UNSTABLE. Please DO NOT store information or data" + }, + "stetting": { + "Layout": { + "title": "Layout" + }, + "Comment": { + "title": "Comment" + }, + "Settings": { + "title": "Settings", + "Duplicate Page": "Duplicate Page", + "Copy Page Link": "Copy Page Link", + "Language": "Language", + "Clear Workspace": "Clear Workspace", + "Last edited by": "Last edited by ", + "Logout": "Logout" + } + } + } +} diff --git a/libs/components/layout/src/i18n/resources/zh.json b/libs/components/layout/src/i18n/resources/zh.json new file mode 100644 index 0000000000..90847e4610 --- /dev/null +++ b/libs/components/layout/src/i18n/resources/zh.json @@ -0,0 +1,30 @@ +{ + "translation": { + "Paper": "页面", + "Edgeless": "无边缘", + "Sync to Disk": "同步到磁盘", + "Share": "分享", + "warningTips": { + "isNotfsApiSupported": "欢迎来到AFFiNE 的演示界面。您可以使用最新版本的基于Chrome的浏览器(如Chrome/Edge)将数据同步到磁盘来进行保存", + "isNotLocalWorkspace": "欢迎来到AFFiNE 的演示界面,您可以同步到磁盘来进行保存操作。", + "DoNotStore": "AFFINE 正在积极开发中,当前版本不稳定。请不要存储信息或数据。" + }, + "stetting": { + "Layout": { + "title": "布局" + }, + "Comment": { + "title": "评论" + }, + "Settings": { + "title": "设置", + "Duplicate Page": "复制页面", + "Copy Page Link": "拷贝页面链接", + "Language": "当前语言", + "Clear Workspace": "清空工作区域", + "Last edited by": "最后编辑者为 ", + "Logout": "登出" + } + } + } +} diff --git a/libs/components/layout/src/index.ts b/libs/components/layout/src/index.ts index 69a6d3ff72..1aee5a687f 100644 --- a/libs/components/layout/src/index.ts +++ b/libs/components/layout/src/index.ts @@ -1,3 +1,4 @@ export * from './header'; +export * from './i18n'; export * from './settings-sidebar'; export * from './workspace-sidebar'; diff --git a/libs/components/layout/src/settings-sidebar/ContainerTabs/ContainerTabs.tsx b/libs/components/layout/src/settings-sidebar/ContainerTabs/ContainerTabs.tsx index dc5320c19d..9019063f30 100644 --- a/libs/components/layout/src/settings-sidebar/ContainerTabs/ContainerTabs.tsx +++ b/libs/components/layout/src/settings-sidebar/ContainerTabs/ContainerTabs.tsx @@ -5,6 +5,7 @@ import { } from '@toeverything/components/icons'; import { styled } from '@toeverything/components/ui'; import { cloneElement, useCallback, useMemo, type ReactElement } from 'react'; +import { useTranslation } from 'react-i18next'; import { Comments } from '../Comments'; import { useActiveComment } from '../Comments/use-comments'; import { LayoutSettings } from '../Layout'; @@ -67,7 +68,7 @@ export const ContainerTabs = () => { _defaultTabsKeys as unknown as string[], 'settings' ); - + const { t } = useTranslation(); return ( <> @@ -75,7 +76,7 @@ export const ContainerTabs = () => { const { type, text, icon } = tab; 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..a0d93aae23 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 { t, i18n } = useTranslation(); + const changeLanguage = (event: any) => { + i18n.changeLanguage(event); + }; return ( @@ -32,7 +45,24 @@ export const SettingsList = () => { return ( item.onClick()}> - {item.name} + {t(`stetting.Settings.${item.name}`)} + {item.name === '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..2d37a73717 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,11 +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 + {t('stetting.Settings.Last edited by')} {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..1960a74626 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,13 @@ const logout = () => { }; export const Logout = () => { + const { t } = useTranslation(); return ( - Logout + + {t('stetting.Settings.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..fdd1a1fb53 100644 --- a/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts +++ b/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts @@ -97,6 +97,13 @@ export const useSettings = (): SettingItem[] => { message.success('Page link copied successfully'); }, }, + { + type: 'button', + name: 'Language', + onClick: () => { + console.log('Language is change'); + }, + }, { type: 'separator', }, 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