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 => (