From 3b06d4bff56202cee182caeb8e56f8e99cc670dd Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Tue, 6 Sep 2022 14:25:15 +0800 Subject: [PATCH 1/7] feat: add new i18n package --- libs/datasource/i18n/.babelrc | 12 +++++++ libs/datasource/i18n/.eslintrc.json | 18 ++++++++++ libs/datasource/i18n/README.md | 45 +++++++++++++++++++++++ libs/datasource/i18n/jest.config.ts | 10 ++++++ libs/datasource/i18n/package.json | 9 +++++ libs/datasource/i18n/project.json | 45 +++++++++++++++++++++++ libs/datasource/i18n/resources/en.json | 24 +++++++++++++ libs/datasource/i18n/resources/zh.json | 24 +++++++++++++ libs/datasource/i18n/src/index.ts | 47 +++++++++++++++++++++++++ libs/datasource/i18n/tsconfig.json | 26 ++++++++++++++ libs/datasource/i18n/tsconfig.lib.json | 23 ++++++++++++ libs/datasource/i18n/tsconfig.spec.json | 20 +++++++++++ tsconfig.base.json | 3 ++ workspace.json | 1 + 14 files changed, 307 insertions(+) create mode 100644 libs/datasource/i18n/.babelrc create mode 100644 libs/datasource/i18n/.eslintrc.json create mode 100644 libs/datasource/i18n/README.md create mode 100644 libs/datasource/i18n/jest.config.ts create mode 100644 libs/datasource/i18n/package.json create mode 100644 libs/datasource/i18n/project.json create mode 100644 libs/datasource/i18n/resources/en.json create mode 100644 libs/datasource/i18n/resources/zh.json create mode 100644 libs/datasource/i18n/src/index.ts create mode 100644 libs/datasource/i18n/tsconfig.json create mode 100644 libs/datasource/i18n/tsconfig.lib.json create mode 100644 libs/datasource/i18n/tsconfig.spec.json diff --git a/libs/datasource/i18n/.babelrc b/libs/datasource/i18n/.babelrc new file mode 100644 index 0000000000..ac4757ac35 --- /dev/null +++ b/libs/datasource/i18n/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/datasource/i18n/.eslintrc.json b/libs/datasource/i18n/.eslintrc.json new file mode 100644 index 0000000000..bd2c1567c7 --- /dev/null +++ b/libs/datasource/i18n/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/datasource/i18n/README.md b/libs/datasource/i18n/README.md new file mode 100644 index 0000000000..e31ddf7292 --- /dev/null +++ b/libs/datasource/i18n/README.md @@ -0,0 +1,45 @@ +# i18n + +## Usages + +```tsx +import { useTranslation } from '@toeverything/datasource/i18n'; + +// base.json +// { +// 'Text': 'some text', +// 'Switch to language': 'Switch to {language}', +// }; + +const App = () => { + const { t } = useTranslation(); + + const changeLanguage = (language: string) => { + i18n.changeLanguage(language); + }; + + return ( +
+
{t('Text')}
+ + + +
+ ); +}; +``` + +## TODO + +- [ ] language detection +- [ ] storage + +## References + +- [i18next](https://www.i18next.com/) +- [react-i18next](https://react.i18next.com/) +- [Tolgee](https://tolgee.io/docs/) diff --git a/libs/datasource/i18n/jest.config.ts b/libs/datasource/i18n/jest.config.ts new file mode 100644 index 0000000000..fb29cab884 --- /dev/null +++ b/libs/datasource/i18n/jest.config.ts @@ -0,0 +1,10 @@ +/* eslint-disable */ +export default { + displayName: 'datasource-i18n', + preset: '../../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../../coverage/libs/datasource/i18n', +}; diff --git a/libs/datasource/i18n/package.json b/libs/datasource/i18n/package.json new file mode 100644 index 0000000000..170fd2691e --- /dev/null +++ b/libs/datasource/i18n/package.json @@ -0,0 +1,9 @@ +{ + "name": "@toeverything/datasource/i18n", + "version": "0.0.1", + "dependencies": { + "i18next": "^21.9.1", + "jotai": "^1.8.1", + "react-i18next": "^11.18.4" + } +} diff --git a/libs/datasource/i18n/project.json b/libs/datasource/i18n/project.json new file mode 100644 index 0000000000..bac263e222 --- /dev/null +++ b/libs/datasource/i18n/project.json @@ -0,0 +1,45 @@ +{ + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/datasource/i18n/src", + "projectType": "library", + "tags": ["datasource"], + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/datasource/i18n", + "tsConfig": "libs/datasource/i18n/tsconfig.lib.json", + "project": "libs/datasource/i18n/package.json", + "entryFile": "libs/datasource/i18n/src/index.ts", + "external": ["react/jsx-runtime"], + "rollupConfig": "@nrwl/react/plugins/bundle-rollup", + "compiler": "babel", + "assets": [ + { + "glob": "libs/datasource/i18n/README.md", + "input": ".", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "libs/datasource/i18n/**/*.{ts,tsx,js,jsx}" + ] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/libs/datasource/i18n"], + "options": { + "jestConfig": "libs/datasource/i18n/jest.config.ts", + "passWithNoTests": true + } + } + } +} diff --git a/libs/datasource/i18n/resources/en.json b/libs/datasource/i18n/resources/en.json new file mode 100644 index 0000000000..e243d71db1 --- /dev/null +++ b/libs/datasource/i18n/resources/en.json @@ -0,0 +1,24 @@ +{ + "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" + }, + "Layout": "Layout", + "Comment": "Comment", + "Settings": "Settings", + "ComingSoon": "Layout Settings Coming Soon...", + "Duplicate Page": "Duplicate Page", + "Copy Page Link": "Copy Page Link", + "Language": "Language", + "Clear Workspace": "Clear Workspace", + "Export As Markdown": "Export As Markdown", + "Export As HTML": "Export As HTML", + "Export As PDF (Unsupported)": "Export As PDF (Unsupported)", + "Import Workspace": "Import Workspace", + "Export Workspace": "Export Workspace", + "Last edited by": "Last edited by {{name}}", + "Logout": "Logout" +} diff --git a/libs/datasource/i18n/resources/zh.json b/libs/datasource/i18n/resources/zh.json new file mode 100644 index 0000000000..fe72ae542d --- /dev/null +++ b/libs/datasource/i18n/resources/zh.json @@ -0,0 +1,24 @@ +{ + "Sync to Disk": "同步到磁盘", + "Share": "分享", + "WarningTips": { + "IsNotfsApiSupported": "欢迎来到AFFiNE 的演示界面。您可以使用最新版本的基于Chrome的浏览器(如Chrome/Edge)将数据同步到磁盘来进行保存", + "IsNotLocalWorkspace": "欢迎来到AFFiNE 的演示界面,您可以同步到磁盘来进行保存操作。", + "DoNotStore": "AFFINE 正在积极开发中,当前版本不稳定。请不要存储信息或数据。" + }, + "ComingSoon": "布局设置即将到来", + "Layout": "布局", + "Comment": "评论", + "Settings": "设置", + "Duplicate Page": "复制页面", + "Copy Page Link": "复制页面链接", + "Language": "当前语言", + "Clear Workspace": "清空工作区域", + "Export As Markdown": "导出 markdown", + "Export As HTML": "导出 HTML", + "Export As PDF (Unsupported)": "导出 PDF (暂不支持)", + "Import Workspace": "导入 Workspace", + "Export Workspace": "导出 Workspace", + "Last edited by": "最后编辑者为 {{name}}", + "Logout": "退出登录" +} diff --git a/libs/datasource/i18n/src/index.ts b/libs/datasource/i18n/src/index.ts new file mode 100644 index 0000000000..77dc273f02 --- /dev/null +++ b/libs/datasource/i18n/src/index.ts @@ -0,0 +1,47 @@ +import i18next, { Resource } from 'i18next'; +import { + I18nextProvider, + initReactI18next, + useTranslation, +} from 'react-i18next'; +import en_US from '../resources/en.json'; +import zh_CN from '../resources/zh.json'; + +// See https://react.i18next.com/latest/typescript +declare module 'react-i18next' { + interface CustomTypeOptions { + // custom namespace type if you changed it + // defaultNS: 'ns1'; + // custom resources type + resources: { + en: typeof en_US; + }; + } +} + +const LOCALES = [ + { value: 'en', text: 'English', res: en_US }, + { value: 'zh', text: '简体中文', res: zh_CN }, +] as const; + +const resources = LOCALES.reduce( + (acc, { value, res }) => ({ ...acc, [value]: { translation: res } }), + {} +); + +const i18n = i18next.createInstance(); +i18n.use(initReactI18next).init({ + // TODO auto detect + lng: LOCALES[0].value, + fallbackLng: LOCALES[0].value, + debug: process.env['NODE_ENV'] === 'development', + + resources, + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + }, +}); + +const I18nProvider = I18nextProvider; + +export { i18n, useTranslation, I18nProvider, LOCALES }; diff --git a/libs/datasource/i18n/tsconfig.json b/libs/datasource/i18n/tsconfig.json new file mode 100644 index 0000000000..7e4c7a4ad6 --- /dev/null +++ b/libs/datasource/i18n/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "strictNullChecks": true, + "importsNotUsedAsValues": "error", + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/datasource/i18n/tsconfig.lib.json b/libs/datasource/i18n/tsconfig.lib.json new file mode 100644 index 0000000000..6a43787407 --- /dev/null +++ b/libs/datasource/i18n/tsconfig.lib.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "jest.config.ts", + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/datasource/i18n/tsconfig.spec.json b/libs/datasource/i18n/tsconfig.spec.json new file mode 100644 index 0000000000..24b3ac659d --- /dev/null +++ b/libs/datasource/i18n/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 5a45715ab6..4e97d65dfd 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -74,6 +74,9 @@ "@toeverything/datasource/feature-flags": [ "libs/datasource/feature-flags/src/index.ts" ], + "@toeverything/datasource/i18n": [ + "libs/datasource/i18n/src/index.ts" + ], "@toeverything/datasource/jwt": [ "libs/datasource/jwt/src/index.ts" ], diff --git a/workspace.json b/workspace.json index f4fa14ed86..75da226c68 100644 --- a/workspace.json +++ b/workspace.json @@ -21,6 +21,7 @@ "datasource-commands": "libs/datasource/commands", "datasource-db-service": "libs/datasource/db-service", "datasource-feature-flags": "libs/datasource/feature-flags", + "datasource-i18n": "libs/datasource/i18n", "datasource-jwt": "libs/datasource/jwt", "datasource-jwt-rpc": "libs/datasource/jwt-rpc", "datasource-remote-kv": "libs/datasource/remote-kv", From 7398d318cd40980dbde0208abc6168e8bcd67608 Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Tue, 6 Sep 2022 14:28:29 +0800 Subject: [PATCH 2/7] refactor: use API from new i18n package --- libs/components/layout/src/header/FileSystem.tsx | 3 ++- libs/components/layout/src/header/LayoutHeader.tsx | 3 +-- .../src/settings-sidebar/ContainerTabs/ContainerTabs.tsx | 2 +- .../layout/src/settings-sidebar/Settings/SettingsList.tsx | 5 ++--- .../src/settings-sidebar/Settings/footer/LastModified.tsx | 2 +- .../layout/src/settings-sidebar/Settings/footer/Logout.tsx | 2 +- .../layout/src/settings-sidebar/Settings/use-settings.ts | 2 +- 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/libs/components/layout/src/header/FileSystem.tsx b/libs/components/layout/src/header/FileSystem.tsx index 30c2b68816..5e8440b4ce 100644 --- a/libs/components/layout/src/header/FileSystem.tsx +++ b/libs/components/layout/src/header/FileSystem.tsx @@ -3,8 +3,9 @@ import { useCallback, useMemo, useState } from 'react'; import { CloseIcon } from '@toeverything/components/common'; import { IconButton, MuiSnackbar, styled } from '@toeverything/components/ui'; import { services } from '@toeverything/datasource/db-service'; +import { useTranslation } from '@toeverything/datasource/i18n'; import { useLocalTrigger } from '@toeverything/datasource/state'; -import { useTranslation } from 'react-i18next'; + const cleanupWorkspace = (workspace: string) => new Promise((resolve, reject) => { const req = indexedDB.deleteDatabase(workspace); diff --git a/libs/components/layout/src/header/LayoutHeader.tsx b/libs/components/layout/src/header/LayoutHeader.tsx index 9b104b4442..963843e0a0 100644 --- a/libs/components/layout/src/header/LayoutHeader.tsx +++ b/libs/components/layout/src/header/LayoutHeader.tsx @@ -5,13 +5,12 @@ import { SideBarViewIcon, } from '@toeverything/components/icons'; import { IconButton, styled } from '@toeverything/components/ui'; +import { useTranslation } from '@toeverything/datasource/i18n'; import { useCurrentEditors, useLocalTrigger, useShowSettingsSidebar, } from '@toeverything/datasource/state'; -import { useCallback, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; import { EditorBoardSwitcher } from './EditorBoardSwitcher'; import { fsApiSupported } from './FileSystem'; import { CurrentPageTitle } from './Title'; diff --git a/libs/components/layout/src/settings-sidebar/ContainerTabs/ContainerTabs.tsx b/libs/components/layout/src/settings-sidebar/ContainerTabs/ContainerTabs.tsx index 4503482d05..3aab18642c 100644 --- a/libs/components/layout/src/settings-sidebar/ContainerTabs/ContainerTabs.tsx +++ b/libs/components/layout/src/settings-sidebar/ContainerTabs/ContainerTabs.tsx @@ -4,8 +4,8 @@ import { SettingsIcon, } from '@toeverything/components/icons'; import { styled } from '@toeverything/components/ui'; +import { useTranslation } from '@toeverything/datasource/i18n'; 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'; diff --git a/libs/components/layout/src/settings-sidebar/Settings/SettingsList.tsx b/libs/components/layout/src/settings-sidebar/Settings/SettingsList.tsx index f462ccf572..a308614d13 100644 --- a/libs/components/layout/src/settings-sidebar/Settings/SettingsList.tsx +++ b/libs/components/layout/src/settings-sidebar/Settings/SettingsList.tsx @@ -6,8 +6,7 @@ import { styled, Switch, } from '@toeverything/components/ui'; -import { useTranslation } from 'react-i18next'; -import { options } from '../../i18n'; +import { LOCALES, useTranslation } from '@toeverything/datasource/i18n'; import { useSettings } from './use-settings'; export const SettingsList = () => { @@ -52,7 +51,7 @@ export const SettingsList = () => { defaultValue={i18n.language} onChange={changeLanguage} > - {options.map(option => ( + {LOCALES.map(option => (