diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index f80d334591..98be545ece 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,2 +1,3 @@
**/project.json @darkskygit
**/pnpm-lock.yaml @darkskygit
+**/en.json @JimmFly
\ No newline at end of file
diff --git a/.github/workflows/languages-sync.yml b/.github/workflows/languages-sync.yml
new file mode 100644
index 0000000000..3cacc35749
--- /dev/null
+++ b/.github/workflows/languages-sync.yml
@@ -0,0 +1,63 @@
+name: Languages Sync
+
+on:
+ push:
+ branches: ['master']
+ paths:
+ - 'packages/i18n/**'
+ - '.github/workflows/languages-sync.yml'
+ pull_request:
+ branches: ['master']
+ paths:
+ - 'packages/i18n/**'
+ - '.github/workflows/languages-sync.yml'
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for pull requests that have not completed.
+# See https://docs.github.com/en/actions/using-jobs/using-concurrency
+concurrency:
+ # The concurrency group contains the workflow name and the branch name for
+ # pull requests or the commit hash for any other events.
+ group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
+ cancel-in-progress: true
+
+jobs:
+ main:
+ strategy:
+ matrix:
+ node-version: [18]
+ os: [ubuntu-latest]
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Use pnpm
+ uses: pnpm/action-setup@v2
+ with:
+ version: 7
+
+ - name: Use Node.js ${{ matrix.node-version }}
+ # https://github.com/actions/setup-node
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'pnpm'
+
+ - name: Install node modules
+ run: pnpm install
+
+ - name: Check Language Key
+ if: github.ref != 'refs/heads/master'
+ working-directory: ./packages/i18n
+ run: pnpm run sync-languages:check
+ env:
+ TOLGEE_API_KEY: ${{ secrets.TOLGEE_API_KEY }}
+
+ - name: Sync Languages
+ if: github.ref == 'refs/heads/master'
+ working-directory: ./packages/i18n
+ run: pnpm run sync-languages
+ env:
+ TOLGEE_API_KEY: ${{ secrets.TOLGEE_API_KEY }}
diff --git a/packages/app/src/components/contact-modal/index.tsx b/packages/app/src/components/contact-modal/index.tsx
index 3cd4d04f85..6ec64fc144 100644
--- a/packages/app/src/components/contact-modal/index.tsx
+++ b/packages/app/src/components/contact-modal/index.tsx
@@ -76,6 +76,8 @@ export const ContactModal = ({
link: 'https://community.affine.pro',
},
];
+ const date = new Date();
+ const year = date.getFullYear();
return (
- Copyright © 2022 Toeverything
+ Copyright © {year} Toeverything
diff --git a/packages/app/src/styles/theme.ts b/packages/app/src/styles/theme.ts
index 1412e00285..f317397fae 100644
--- a/packages/app/src/styles/theme.ts
+++ b/packages/app/src/styles/theme.ts
@@ -20,6 +20,7 @@ export const getLightTheme = (
popoverBackground: '#fff',
tooltipBackground: '#6880FF',
codeBackground: '#f2f5f9',
+ codeBlockBackground: '#fafbfd',
warningBackground: '#FFF9C7',
errorBackground: '#FFDED8',
@@ -40,6 +41,7 @@ export const getLightTheme = (
disableColor: '#C0C0C0',
warningColor: '#906616',
errorColor: '#EB4335',
+ lineNumberColor: '#888A9E',
},
font: {
xs: '12px',
@@ -96,6 +98,7 @@ export const getDarkTheme = (
editorMode === 'edgeless'
? lightTheme.colors.codeBackground
: '#505662',
+ codeBlockBackground: '#36383D',
warningBackground: '#FFF9C7',
errorBackground: '#FFDED8',
@@ -117,6 +120,7 @@ export const getDarkTheme = (
disableColor: '#4b4b4b',
warningColor: '#906616',
errorColor: '#EB4335',
+ lineNumberColor: '#888A9E',
},
shadow: {
popover:
@@ -154,12 +158,14 @@ export const globalThemeVariables: (
'--affine-popover-color': theme.colors.popoverColor,
'--affine-input-color': theme.colors.inputColor,
'--affine-code-color': theme.colors.codeColor,
+ '--affine-code-block-background': theme.colors.codeBlockBackground,
'--affine-quote-color': theme.colors.quoteColor,
'--affine-selected-color': theme.colors.selectedColor,
'--affine-placeholder-color': theme.colors.placeHolderColor,
'--affine-border-color': theme.colors.borderColor,
'--affine-disable-color': theme.colors.disableColor,
'--affine-tooltip-color': theme.colors.tooltipColor,
+ '--affine-line-number-color': theme.colors.lineNumberColor,
'--affine-modal-shadow': theme.shadow.modal,
'--affine-popover-shadow': theme.shadow.popover,
diff --git a/packages/app/src/styles/types.ts b/packages/app/src/styles/types.ts
index 85b12df586..81816ad611 100644
--- a/packages/app/src/styles/types.ts
+++ b/packages/app/src/styles/types.ts
@@ -25,6 +25,7 @@ export interface AffineTheme {
hoverBackground: string;
innerHoverBackground: string;
codeBackground: string;
+ codeBlockBackground: string;
warningBackground: string;
errorBackground: string;
// Use for the page`s text
@@ -47,6 +48,7 @@ export interface AffineTheme {
disableColor: string;
warningColor: string;
errorColor: string;
+ lineNumberColor: string;
};
font: {
xs: string; // tiny
@@ -90,6 +92,8 @@ export interface AffineThemeCSSVariables {
'--affine-popover-background': AffineTheme['colors']['popoverBackground'];
'--affine-hover-background': AffineTheme['colors']['hoverBackground'];
'--affine-code-background': AffineTheme['colors']['codeBackground'];
+
+ '--affine-code-block-background': AffineTheme['colors']['codeBlockBackground'];
'--affine-tooltip-background': AffineTheme['colors']['tooltipBackground'];
'--affine-text-color': AffineTheme['colors']['textColor'];
@@ -107,6 +111,7 @@ export interface AffineThemeCSSVariables {
'--affine-border-color': AffineTheme['colors']['borderColor'];
'--affine-disable-color': AffineTheme['colors']['disableColor'];
'--affine-tooltip-color': AffineTheme['colors']['tooltipColor'];
+ '--affine-line-number-color': AffineTheme['colors']['lineNumberColor'];
'--affine-modal-shadow': AffineTheme['shadow']['modal'];
'--affine-popover-shadow': AffineTheme['shadow']['popover'];
diff --git a/packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md b/packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md
index 130b2918b8..91e2fe48c0 100644
--- a/packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md
+++ b/packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md
@@ -39,7 +39,7 @@ docker run -it --name affine -d -v [YOUR_PATH]:/app/data -p 3000:3000 ghcr.io/to
[] What about a code block? `````
-```javascript
+```JavaScript
console.log('Hello world');
```
diff --git a/packages/i18n/README.md b/packages/i18n/README.md
new file mode 100644
index 0000000000..ec443f6fc8
--- /dev/null
+++ b/packages/i18n/README.md
@@ -0,0 +1,62 @@
+# i18n
+
+## Usages
+
+- Update missing translations into the base resources, a.k.a the `src/resources/en.json`
+- Replace literal text with translation keys
+
+```tsx
+import { useTranslation } from '@affine/i18n';
+
+// src/resources/en.json
+// {
+// 'Text': 'some text',
+// 'Switch to language': 'Switch to {{language}}', // <- you can interpolation by curly brackets
+// };
+
+const App = () => {
+ const { t } = useTranslation();
+
+ const changeLanguage = (language: string) => {
+ i18n.changeLanguage(language);
+ };
+
+ return (
+
+
{t('Text')}
+
+
+
+
+ );
+};
+```
+
+## How the i18n workflow works?
+
+- When the `src/resources/en.json`(base language) updated and merged to the develop branch, will trigger the `languages-sync` action.
+- The `languages-sync` action will check the base language and add missing translations to the Tolgee platform.
+- This way, partners from the community can update the translations.
+
+## How to sync translations manually
+
+- Set token as environment variable
+
+```shell
+export TOLGEE_API_KEY=tgpak_XXXXXXX
+```
+
+- Run the `sync-languages:check` to check all languages
+- Run the `sync-languages` script to add new keys to the Tolgee platform
+- Run the `download-resources` script to download the latest full-translation translation resources from the Tolgee platform
+
+## References
+
+- [AFFiNE | Tolgee](https://i18n.affine.pro/)
+- [Tolgee Documentation](https://tolgee.io/docs/)
+- [i18next](https://www.i18next.com/)
+- [react-i18next](https://react.i18next.com/)
diff --git a/packages/i18n/package.json b/packages/i18n/package.json
index cc559528e3..eb6c30a1a1 100644
--- a/packages/i18n/package.json
+++ b/packages/i18n/package.json
@@ -10,7 +10,10 @@
".": "./dist/src/index.js"
},
"scripts": {
- "build": "tsc --project ./tsconfig.json"
+ "build": "tsc --project ./tsconfig.json",
+ "sync-languages": "NODE_OPTIONS=--experimental-fetch ts-node-esm src/scripts/sync.ts",
+ "sync-languages:check": "pnpm run sync-languages --check",
+ "download-resources": "NODE_OPTIONS=--experimental-fetch ts-node-esm src/scripts/download.ts"
},
"keywords": [],
"repository": {
@@ -24,6 +27,7 @@
},
"devDependencies": {
"@types/prettier": "^2.7.2",
+ "ts-node": "^10.9.1",
"typescript": "^4.8.4"
}
}
diff --git a/packages/i18n/src/resources/en.json b/packages/i18n/src/resources/en.json
index 2831a8aafe..97766c8050 100644
--- a/packages/i18n/src/resources/en.json
+++ b/packages/i18n/src/resources/en.json
@@ -15,7 +15,6 @@
"Add to favourites": "Add to favourites",
"Paper": "Paper",
"Edgeless": "Edgeless",
- "Jump to": "Jump to",
"Convert to ": "Convert to ",
"Page": "Page",
"Export": "Export",
@@ -57,6 +56,12 @@
"Reduce indent": "Reduce indent",
"Markdown Syntax": "Markdown Syntax",
"Divider": "Divider",
+ "Once deleted, you can't undo this action": {
+ "": "Once deleted, you can't undo this action."
+ },
+ "Remove from favourites": "Remove from favourites",
+ "Removed from Favourites": "Removed from Favourites",
+ "Jump to": "Jump to",
"404 - Page Not Found": "404 - Page Not Found",
"Once deleted, you can't undo this action": {
"": "Once deleted, you can't undo this action."
@@ -141,5 +146,7 @@
"Sign in": "Sign in to AFFiNE Cloud",
"Sync Description": "{{workspaceName}} is a Local Workspace. All data is stored on the current device. You can enable AFFiNE Cloud for this workspace to keep data in sync with the cloud.",
"Sync Description2": "<1>{{workspaceName}}1> is a Cloud Workspace. All data will be synchronised and saved to AFFiNE Cloud.",
- "Delete Workspace Description": "Deleting (<1>{{workspace}}1>) cannot be undone, please proceed with caution. All contents will be lost."
+ "Delete Workspace Description": "Deleting (<1>{{workspace}}1>) cannot be undone, please proceed with caution. All contents will be lost.",
+ "core": "core",
+ "all": "all"
}
diff --git a/packages/i18n/src/resources/index.ts b/packages/i18n/src/resources/index.ts
index 2081ad8aaf..cf5c5fc729 100644
--- a/packages/i18n/src/resources/index.ts
+++ b/packages/i18n/src/resources/index.ts
@@ -2,6 +2,7 @@
// Run `pnpm run download-resources` to regenerate.
// To overwrite this, please overwrite download.ts script.
import en from './en.json';
+import zh_Hans from './zh-Hans.json';
export const LOCALES = [
{
@@ -14,4 +15,14 @@ export const LOCALES = [
completeRate: 1,
res: en,
},
+ {
+ id: 1000040004,
+ name: 'Simplified Chinese',
+ tag: 'zh-Hans',
+ originalName: '简体中文',
+ flagEmoji: '🇨🇳',
+ base: false,
+ completeRate: 1,
+ res: zh_Hans,
+ },
] as const;
diff --git a/packages/i18n/src/resources/zh-Hans.json b/packages/i18n/src/resources/zh-Hans.json
new file mode 100644
index 0000000000..2a2e17c660
--- /dev/null
+++ b/packages/i18n/src/resources/zh-Hans.json
@@ -0,0 +1,148 @@
+{
+ "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "",
+ "Quick search": "快速搜索",
+ "All pages": "全部页面",
+ "Favourites": "收藏夹",
+ "No item": "无项目",
+ "Import": "导入",
+ "Trash": "垃圾箱",
+ "New Page": "新建页面",
+ "New Keyword Page": "新建 “{{query}}“ 为标题的页面 ",
+ "Find 0 result": "找到 0 个结果",
+ "Find results": "找到 {{number}} 个结果",
+ "Collapse sidebar": "折叠侧边栏",
+ "Expand sidebar": "展开侧边栏",
+ "Added to Favourites": "已收藏",
+ "Add to favourites": "加入收藏",
+ "Paper": "文档",
+ "Edgeless": "无界",
+ "Convert to ": "转换为",
+ "Page": "页面",
+ "Export": "导出",
+ "Export to HTML": "导出为 HTML",
+ "Export to Markdown": "导出为 Markdown",
+ "Delete": "删除",
+ "Title": "标题",
+ "Untitled": "未命名",
+ "Created": "已创建",
+ "Updated": "已更新",
+ "Open in new tab": "在新标签页打开",
+ "Favourite": "收藏",
+ "Favourited": "已收藏",
+ "Delete page?": "确定要删除页面?",
+ "Delete permanently?": "是否永久删除?",
+ "will be moved to Trash": "{{title}} 将被移到垃圾箱",
+ "Once deleted, you can't undo this action": {
+ "": "一旦删除,将无法撤销!"
+ },
+ "Moved to Trash": "已移到垃圾箱",
+ "Permanently deleted": "已永久删除",
+ "restored": "{{title}} 已恢复",
+ "Cancel": "取消",
+ "Keyboard Shortcuts": "键盘快捷键",
+ "Contact Us": "联系我们",
+ "Official Website": "官网",
+ "Get in touch!": "保持联络!",
+ "AFFiNE Community": "AFFiNE 社区",
+ "How is AFFiNE Alpha different?": "AFFiNE Alpha有何不同?",
+ "Shortcuts": "快捷键",
+ "Undo": "撤销",
+ "Redo": "重做",
+ "Bold": "粗体",
+ "Italic": "斜体",
+ "Underline": "下划线",
+ "Strikethrough": "删除线",
+ "Inline code": "行内代码",
+ "Code block": "代码块",
+ "Link": "超链接(选定文本)",
+ "Body text": "正文",
+ "Heading": "标题 {{number}}",
+ "Increase indent": "增加缩进",
+ "Reduce indent": "减少缩进",
+ "Markdown Syntax": "Markdown 语法",
+ "Divider": "分割线",
+ "404 - Page Not Found": "404 - 页面不见了",
+ "Remove from favourites": "从收藏中移除",
+ "Removed from Favourites": "已从收藏中移除",
+ "New Workspace": "新建工作区",
+ "Workspace description": "工作区是为个人和团队进行引用、创建和规划的虚拟空间。",
+ "Create": "创建",
+ "Select": "选择",
+ "Text": "文本(即将上线)",
+ "Shape": "图形",
+ "Sticky": "便利贴(即将上线)",
+ "Pen": "笔(即将上线)",
+ "Connector": "链接(即将上线)",
+ "Upload": "上传",
+ "Restore it": "恢复TA",
+ "TrashButtonGroupTitle": "永久删除",
+ "TrashButtonGroupDescription": "一旦删除,将无法撤消此操作。确定吗?",
+ "Delete permanently": "永久删除",
+ "Quick search placeholder": "快速搜索...",
+ "Quick search placeholder2": "在{{workspace}} 中搜索",
+ "Settings": "设置",
+ "recommendBrowser": "建议使用 <1>Chrome1> 浏览器以获得最佳体验。",
+ "upgradeBrowser": "请升级到最新版本的 Chrome 以获得最佳体验。",
+ "Invite Members": "邀请成员",
+ "Invite placeholder": "搜索邮件(仅支持Gmail)",
+ "Non-Gmail": "不支持非Gmail邮箱",
+ "Invite": "邀请",
+ "Loading": "加载中...",
+ "NotLoggedIn": "当前未登录",
+ "ClearData": "清除本地数据",
+ "Continue with Google": "谷歌登录以继续",
+ "Set up an AFFiNE account to sync data": "设置AFFiNE帐户以同步数据",
+ "Stay logged out": "保持登出状态",
+ "All changes are saved locally": "所有改动已保存到本地",
+ "Ooops!": "啊哦!",
+ "mobile device": "貌似你正在移动设备上浏览。",
+ "mobile device description": "我们仍在进行移动端的支持工作,建议使用桌面设备。",
+ "Got it": "知道了",
+ "emptyAllPages": "此工作区为空。创建新页面并开始编辑。",
+ "emptyFavourite": "单击“添加到收藏夹”,页面将显示在此处。",
+ "emptyTrash": "单击“添加到垃圾箱”,页面将显示在此处。",
+ "still designed": "(此页面仍在设计中。)",
+ "My Workspaces": "我的工作区",
+ "Create Or Import": "创建或导入",
+ "Tips": "提示:",
+ "login success": "登录成功",
+ "Sign in": "登录 AFFiNE 云",
+ "Sign out": "登出 AFFiNE 云",
+ "Delete Workspace": "删除工作空间",
+ "Delete Workspace Description": "正在删除 (<1>{{workspace}}1>) ,此操作无法撤销,所有内容将会丢失。",
+ "Delete Workspace Description2": "正在删除(<1>{{workspace}}1>),将同时删除本地和云端数据。此操作无法撤消,请谨慎操作。",
+ "Delete Workspace placeholder": "请输入”Delete“以确认",
+ "Leave Workspace": "退出工作区",
+ "Leave Workspace Description": "退出后,您将无法再访问此工作区的内容。",
+ "Jump to": "跳转到",
+ "Leave": "退出",
+ "Workspace Icon": "工作区图标",
+ "Workspace Name": "工作区名称",
+ "Workspace Type": "工作区类型",
+ "Export Workspace": "导出工作区 <1>{{workspace}}1> 即将上线",
+ "Users": "用户",
+ "Access level": "访问权限",
+ "Pending": "待定",
+ "Collaboration Description": "与其他成员协作需要AFFiNE云服务支持。",
+ "Enable AFFiNE Cloud": "启用 AFFiNE 云服务",
+ "Enable AFFiNE Cloud Description": "如启用,此工作区中的数据将通过AFFiNE Cloud进行备份和同步。",
+ "Enable": "启动",
+ "Sign in and Enable": "登录并启用",
+ "Skip": "跳过",
+ "Publishing": "发布到web需要AFFiNE云服务。",
+ "Share with link": "通过链接分享",
+ "Copy Link": "复制链接",
+ "Publishing Description": "发布到web后,所有人都可以通过链接查看此工作区的内容。",
+ "Stop publishing": "中止发布",
+ "Publish to web": "发布到web",
+ "Sync Description": "{{workspaceName}}是本地工作区,所有数据都存储在当前设备上。您可以为此工作区启用AFFiNE Cloud,以使数据与云端保持同步。",
+ "Sync Description2": "<1>{{workspaceName}}1>是云端工作区。所有数据将同步并保存到AFFiNE Cloud。",
+ "Download data to device": "下载{{CoreOrAll}}数据到设备",
+ "General": "常规",
+ "Sync": "同步",
+ "Collaboration": "协作",
+ "Publish": "发布",
+ "Workspace Settings": "工作区设置",
+ "core": "核心",
+ "all": "全部"
+}
diff --git a/packages/i18n/src/script/api.ts b/packages/i18n/src/scripts/api.ts
similarity index 99%
rename from packages/i18n/src/script/api.ts
rename to packages/i18n/src/scripts/api.ts
index ae286fe584..657e3c245c 100644
--- a/packages/i18n/src/script/api.ts
+++ b/packages/i18n/src/scripts/api.ts
@@ -1,5 +1,5 @@
// cSpell:ignore Tolgee
-import { fetchTolgee } from './request';
+import { fetchTolgee } from './request.js';
/**
* Returns all project languages
diff --git a/packages/i18n/src/script/download.ts b/packages/i18n/src/scripts/download.ts
similarity index 95%
rename from packages/i18n/src/script/download.ts
rename to packages/i18n/src/scripts/download.ts
index a43e84bf22..7aa0740c6e 100644
--- a/packages/i18n/src/script/download.ts
+++ b/packages/i18n/src/scripts/download.ts
@@ -2,8 +2,8 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { format } from 'prettier';
-import { getAllProjectLanguages, getRemoteTranslations } from './api';
-import type { TranslationRes } from './utils';
+import { getAllProjectLanguages, getRemoteTranslations } from './api.js';
+import type { TranslationRes } from './utils.js';
const RES_DIR = path.resolve(process.cwd(), 'src', 'resources');
@@ -66,7 +66,7 @@ const main = async () => {
);
const availableLanguages = languagesWithTranslations.filter(
- language => language.completeRate > 0
+ language => language.completeRate === 1
);
availableLanguages
@@ -90,7 +90,7 @@ const main = async () => {
console.log('Generating meta data...');
const code = `// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
// Run \`pnpm run download-resources\` to regenerate.
- // To overwrite this, please overwrite ${path.basename(__filename)}
+ // To overwrite this, please overwrite download.ts script.
${availableLanguages
.map(
language =>
diff --git a/packages/i18n/src/script/request.ts b/packages/i18n/src/scripts/request.ts
similarity index 100%
rename from packages/i18n/src/script/request.ts
rename to packages/i18n/src/scripts/request.ts
diff --git a/packages/i18n/src/script/sync.ts b/packages/i18n/src/scripts/sync.ts
similarity index 97%
rename from packages/i18n/src/script/sync.ts
rename to packages/i18n/src/scripts/sync.ts
index 80b16a5024..b89734e4c4 100644
--- a/packages/i18n/src/script/sync.ts
+++ b/packages/i18n/src/scripts/sync.ts
@@ -1,8 +1,8 @@
// cSpell:ignore Tolgee
import { readFile } from 'fs/promises';
import path from 'path';
-import { createsNewKey, getRemoteTranslations } from './api';
-import type { TranslationRes } from './utils';
+import { createsNewKey, getRemoteTranslations } from './api.js';
+import type { TranslationRes } from './utils.js';
const BASE_JSON_PATH = path.resolve(
process.cwd(),
diff --git a/packages/i18n/src/script/utils.ts b/packages/i18n/src/scripts/utils.ts
similarity index 100%
rename from packages/i18n/src/script/utils.ts
rename to packages/i18n/src/scripts/utils.ts