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}} is a Cloud Workspace. All data will be synchronised and saved to AFFiNE Cloud.", - "Delete Workspace Description": "Deleting (<1>{{workspace}}) cannot be undone, please proceed with caution. All contents will be lost." + "Delete Workspace Description": "Deleting (<1>{{workspace}}) 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>Chrome 浏览器以获得最佳体验。", + "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}}) ,此操作无法撤销,所有内容将会丢失。", + "Delete Workspace Description2": "正在删除(<1>{{workspace}}),将同时删除本地和云端数据。此操作无法撤消,请谨慎操作。", + "Delete Workspace placeholder": "请输入”Delete“以确认", + "Leave Workspace": "退出工作区", + "Leave Workspace Description": "退出后,您将无法再访问此工作区的内容。", + "Jump to": "跳转到", + "Leave": "退出", + "Workspace Icon": "工作区图标", + "Workspace Name": "工作区名称", + "Workspace Type": "工作区类型", + "Export Workspace": "导出工作区 <1>{{workspace}} 即将上线", + "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}}是云端工作区。所有数据将同步并保存到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