Merge pull request #719 from toeverything/feat/datacenter-i18n
feat: merge master for update i18n
This commit is contained in:
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -1,2 +1,3 @@
|
|||||||
**/project.json @darkskygit
|
**/project.json @darkskygit
|
||||||
**/pnpm-lock.yaml @darkskygit
|
**/pnpm-lock.yaml @darkskygit
|
||||||
|
**/en.json @JimmFly
|
||||||
63
.github/workflows/languages-sync.yml
vendored
Normal file
63
.github/workflows/languages-sync.yml
vendored
Normal file
@@ -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 }}
|
||||||
@@ -76,6 +76,8 @@ export const ContactModal = ({
|
|||||||
link: 'https://community.affine.pro',
|
link: 'https://community.affine.pro',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const date = new Date();
|
||||||
|
const year = date.getFullYear();
|
||||||
return (
|
return (
|
||||||
<Modal open={open} onClose={onClose} data-testid="contact-us-modal-content">
|
<Modal open={open} onClose={onClose} data-testid="contact-us-modal-content">
|
||||||
<ModalWrapper
|
<ModalWrapper
|
||||||
@@ -135,7 +137,7 @@ export const ContactModal = ({
|
|||||||
{t('How is AFFiNE Alpha different?')}
|
{t('How is AFFiNE Alpha different?')}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p>Copyright © 2022 Toeverything</p>
|
<p>Copyright © {year} Toeverything</p>
|
||||||
</StyledModalFooter>
|
</StyledModalFooter>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ import { toast } from '@/ui/toast';
|
|||||||
import useMembers from '@/hooks/use-members';
|
import useMembers from '@/hooks/use-members';
|
||||||
import Loading from '@/components/loading';
|
import Loading from '@/components/loading';
|
||||||
import { Wrapper } from '@/ui/layout';
|
import { Wrapper } from '@/ui/layout';
|
||||||
|
import { useTranslation } from '@affine/i18n';
|
||||||
|
|
||||||
export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||||
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
||||||
const { members, removeMember, loaded } = useMembers();
|
const { members, removeMember, loaded } = useMembers();
|
||||||
|
const { t } = useTranslation();
|
||||||
// FIXME: DELETE THIS
|
// FIXME: DELETE THIS
|
||||||
const { user, login, updateWorkspaceMeta } = useTemporaryHelper();
|
const { user, login, updateWorkspaceMeta } = useTemporaryHelper();
|
||||||
const { confirm } = useConfirm();
|
const { confirm } = useConfirm();
|
||||||
@@ -51,10 +52,10 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
|||||||
<>
|
<>
|
||||||
<StyledMemberTitleContainer>
|
<StyledMemberTitleContainer>
|
||||||
<StyledMemberNameContainer>
|
<StyledMemberNameContainer>
|
||||||
Users({members.length})
|
{t('Users')} ({members.length})
|
||||||
</StyledMemberNameContainer>
|
</StyledMemberNameContainer>
|
||||||
<StyledMemberRoleContainer>
|
<StyledMemberRoleContainer>
|
||||||
Access level
|
{t('Access level')}
|
||||||
</StyledMemberRoleContainer>
|
</StyledMemberRoleContainer>
|
||||||
</StyledMemberTitleContainer>
|
</StyledMemberTitleContainer>
|
||||||
{members.map((member, index) => {
|
{members.map((member, index) => {
|
||||||
@@ -138,7 +139,7 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
|||||||
type="primary"
|
type="primary"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
>
|
>
|
||||||
Invite Members
|
{t('Invite Members')}
|
||||||
</Button>
|
</Button>
|
||||||
<InviteMemberModal
|
<InviteMemberModal
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
@@ -158,17 +159,17 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledMemberWarp>
|
<StyledMemberWarp>
|
||||||
<>Collaborating with other members requires AFFiNE Cloud service.</>
|
<>{t('Collaboration Description')}</>
|
||||||
<StyledPublishExplanation>
|
<StyledPublishExplanation>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
confirm({
|
confirm({
|
||||||
title: 'Enable AFFiNE Cloud?',
|
title: `${t('Enable AFFiNE Cloud')}?`,
|
||||||
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
content: t('Enable AFFiNE Cloud Description'),
|
||||||
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
confirmText: user ? t('Enable') : t('Sign in and Enable'),
|
||||||
cancelText: 'Skip',
|
cancelText: t('Skip'),
|
||||||
}).then(confirm => {
|
}).then(confirm => {
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
if (user) {
|
if (user) {
|
||||||
@@ -181,7 +182,7 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Enable AFFiNE Cloud
|
{t('Enable AFFiNE Cloud')}
|
||||||
</Button>
|
</Button>
|
||||||
</StyledPublishExplanation>
|
</StyledPublishExplanation>
|
||||||
</StyledMemberWarp>
|
</StyledMemberWarp>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export const WorkSpaceSliderBar = () => {
|
|||||||
const { currentWorkspace } = useAppState();
|
const { currentWorkspace } = useAppState();
|
||||||
const { openPage, createPage } = usePageHelper();
|
const { openPage, createPage } = usePageHelper();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const [showTip, setShowTip] = useState(false);
|
const [showTip, setShowTip] = useState(false);
|
||||||
const [show, setShow] = useLocalStorage('AFFiNE_SLIDE_BAR', false, true);
|
const [show, setShow] = useLocalStorage('AFFiNE_SLIDE_BAR', false, true);
|
||||||
const currentWorkspaceId = currentWorkspace?.id;
|
const currentWorkspaceId = currentWorkspace?.id;
|
||||||
@@ -84,7 +84,9 @@ export const WorkSpaceSliderBar = () => {
|
|||||||
? `/workspace/${currentWorkspaceId}/setting`
|
? `/workspace/${currentWorkspaceId}/setting`
|
||||||
: '',
|
: '',
|
||||||
};
|
};
|
||||||
|
const changeLanguage = (event: string) => {
|
||||||
|
i18n.changeLanguage(event);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledSliderBar show={show}>
|
<StyledSliderBar show={show}>
|
||||||
@@ -176,6 +178,20 @@ export const WorkSpaceSliderBar = () => {
|
|||||||
<TrashIcon /> {t('Trash')}
|
<TrashIcon /> {t('Trash')}
|
||||||
</StyledListItem>
|
</StyledListItem>
|
||||||
</Link>
|
</Link>
|
||||||
|
<StyledListItem
|
||||||
|
onClick={() => {
|
||||||
|
changeLanguage('en');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingsIcon /> change to English
|
||||||
|
</StyledListItem>
|
||||||
|
<StyledListItem
|
||||||
|
onClick={() => {
|
||||||
|
changeLanguage('zh-Hans');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingsIcon /> 切换到中文
|
||||||
|
</StyledListItem>
|
||||||
<StyledNewPageButton
|
<StyledNewPageButton
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const pageId = await createPage();
|
const pageId = await createPage();
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import WorkspaceLayout from '@/components/workspace-layout';
|
import WorkspaceLayout from '@/components/workspace-layout';
|
||||||
import { WorkspaceUnit } from '@affine/datacenter';
|
import { WorkspaceUnit } from '@affine/datacenter';
|
||||||
|
import { useTranslation } from '@affine/i18n';
|
||||||
|
|
||||||
type TabNames = 'general' | 'members' | 'publish' | 'sync' | 'export';
|
type TabNames = 'general' | 'members' | 'publish' | 'sync' | 'export';
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ const tabMap: {
|
|||||||
|
|
||||||
const WorkspaceSetting = () => {
|
const WorkspaceSetting = () => {
|
||||||
const { currentWorkspace } = useAppState();
|
const { currentWorkspace } = useAppState();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [activeTab, setActiveTab] = useState<TabNames>(tabMap[0].name);
|
const [activeTab, setActiveTab] = useState<TabNames>(tabMap[0].name);
|
||||||
const handleTabChange = (tab: TabNames) => {
|
const handleTabChange = (tab: TabNames) => {
|
||||||
setActiveTab(tab);
|
setActiveTab(tab);
|
||||||
@@ -75,7 +76,7 @@ const WorkspaceSetting = () => {
|
|||||||
<StyledSettingContainer>
|
<StyledSettingContainer>
|
||||||
<StyledSettingSidebar>
|
<StyledSettingSidebar>
|
||||||
<StyledSettingSidebarHeader>
|
<StyledSettingSidebarHeader>
|
||||||
Workspace Settings
|
{t('Workspace Settings')}
|
||||||
</StyledSettingSidebarHeader>
|
</StyledSettingSidebarHeader>
|
||||||
<StyledSettingTabContainer>
|
<StyledSettingTabContainer>
|
||||||
{tabMap.map(({ icon, name }) => {
|
{tabMap.map(({ icon, name }) => {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const getLightTheme = (
|
|||||||
popoverBackground: '#fff',
|
popoverBackground: '#fff',
|
||||||
tooltipBackground: '#6880FF',
|
tooltipBackground: '#6880FF',
|
||||||
codeBackground: '#f2f5f9',
|
codeBackground: '#f2f5f9',
|
||||||
|
codeBlockBackground: '#fafbfd',
|
||||||
warningBackground: '#FFF9C7',
|
warningBackground: '#FFF9C7',
|
||||||
errorBackground: '#FFDED8',
|
errorBackground: '#FFDED8',
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ export const getLightTheme = (
|
|||||||
disableColor: '#C0C0C0',
|
disableColor: '#C0C0C0',
|
||||||
warningColor: '#906616',
|
warningColor: '#906616',
|
||||||
errorColor: '#EB4335',
|
errorColor: '#EB4335',
|
||||||
|
lineNumberColor: '#888A9E',
|
||||||
},
|
},
|
||||||
font: {
|
font: {
|
||||||
xs: '12px',
|
xs: '12px',
|
||||||
@@ -96,6 +98,7 @@ export const getDarkTheme = (
|
|||||||
editorMode === 'edgeless'
|
editorMode === 'edgeless'
|
||||||
? lightTheme.colors.codeBackground
|
? lightTheme.colors.codeBackground
|
||||||
: '#505662',
|
: '#505662',
|
||||||
|
codeBlockBackground: '#36383D',
|
||||||
warningBackground: '#FFF9C7',
|
warningBackground: '#FFF9C7',
|
||||||
errorBackground: '#FFDED8',
|
errorBackground: '#FFDED8',
|
||||||
|
|
||||||
@@ -117,6 +120,7 @@ export const getDarkTheme = (
|
|||||||
disableColor: '#4b4b4b',
|
disableColor: '#4b4b4b',
|
||||||
warningColor: '#906616',
|
warningColor: '#906616',
|
||||||
errorColor: '#EB4335',
|
errorColor: '#EB4335',
|
||||||
|
lineNumberColor: '#888A9E',
|
||||||
},
|
},
|
||||||
shadow: {
|
shadow: {
|
||||||
popover:
|
popover:
|
||||||
@@ -154,12 +158,14 @@ export const globalThemeVariables: (
|
|||||||
'--affine-popover-color': theme.colors.popoverColor,
|
'--affine-popover-color': theme.colors.popoverColor,
|
||||||
'--affine-input-color': theme.colors.inputColor,
|
'--affine-input-color': theme.colors.inputColor,
|
||||||
'--affine-code-color': theme.colors.codeColor,
|
'--affine-code-color': theme.colors.codeColor,
|
||||||
|
'--affine-code-block-background': theme.colors.codeBlockBackground,
|
||||||
'--affine-quote-color': theme.colors.quoteColor,
|
'--affine-quote-color': theme.colors.quoteColor,
|
||||||
'--affine-selected-color': theme.colors.selectedColor,
|
'--affine-selected-color': theme.colors.selectedColor,
|
||||||
'--affine-placeholder-color': theme.colors.placeHolderColor,
|
'--affine-placeholder-color': theme.colors.placeHolderColor,
|
||||||
'--affine-border-color': theme.colors.borderColor,
|
'--affine-border-color': theme.colors.borderColor,
|
||||||
'--affine-disable-color': theme.colors.disableColor,
|
'--affine-disable-color': theme.colors.disableColor,
|
||||||
'--affine-tooltip-color': theme.colors.tooltipColor,
|
'--affine-tooltip-color': theme.colors.tooltipColor,
|
||||||
|
'--affine-line-number-color': theme.colors.lineNumberColor,
|
||||||
|
|
||||||
'--affine-modal-shadow': theme.shadow.modal,
|
'--affine-modal-shadow': theme.shadow.modal,
|
||||||
'--affine-popover-shadow': theme.shadow.popover,
|
'--affine-popover-shadow': theme.shadow.popover,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export interface AffineTheme {
|
|||||||
hoverBackground: string;
|
hoverBackground: string;
|
||||||
innerHoverBackground: string;
|
innerHoverBackground: string;
|
||||||
codeBackground: string;
|
codeBackground: string;
|
||||||
|
codeBlockBackground: string;
|
||||||
warningBackground: string;
|
warningBackground: string;
|
||||||
errorBackground: string;
|
errorBackground: string;
|
||||||
// Use for the page`s text
|
// Use for the page`s text
|
||||||
@@ -47,6 +48,7 @@ export interface AffineTheme {
|
|||||||
disableColor: string;
|
disableColor: string;
|
||||||
warningColor: string;
|
warningColor: string;
|
||||||
errorColor: string;
|
errorColor: string;
|
||||||
|
lineNumberColor: string;
|
||||||
};
|
};
|
||||||
font: {
|
font: {
|
||||||
xs: string; // tiny
|
xs: string; // tiny
|
||||||
@@ -90,6 +92,8 @@ export interface AffineThemeCSSVariables {
|
|||||||
'--affine-popover-background': AffineTheme['colors']['popoverBackground'];
|
'--affine-popover-background': AffineTheme['colors']['popoverBackground'];
|
||||||
'--affine-hover-background': AffineTheme['colors']['hoverBackground'];
|
'--affine-hover-background': AffineTheme['colors']['hoverBackground'];
|
||||||
'--affine-code-background': AffineTheme['colors']['codeBackground'];
|
'--affine-code-background': AffineTheme['colors']['codeBackground'];
|
||||||
|
|
||||||
|
'--affine-code-block-background': AffineTheme['colors']['codeBlockBackground'];
|
||||||
'--affine-tooltip-background': AffineTheme['colors']['tooltipBackground'];
|
'--affine-tooltip-background': AffineTheme['colors']['tooltipBackground'];
|
||||||
|
|
||||||
'--affine-text-color': AffineTheme['colors']['textColor'];
|
'--affine-text-color': AffineTheme['colors']['textColor'];
|
||||||
@@ -107,6 +111,7 @@ export interface AffineThemeCSSVariables {
|
|||||||
'--affine-border-color': AffineTheme['colors']['borderColor'];
|
'--affine-border-color': AffineTheme['colors']['borderColor'];
|
||||||
'--affine-disable-color': AffineTheme['colors']['disableColor'];
|
'--affine-disable-color': AffineTheme['colors']['disableColor'];
|
||||||
'--affine-tooltip-color': AffineTheme['colors']['tooltipColor'];
|
'--affine-tooltip-color': AffineTheme['colors']['tooltipColor'];
|
||||||
|
'--affine-line-number-color': AffineTheme['colors']['lineNumberColor'];
|
||||||
|
|
||||||
'--affine-modal-shadow': AffineTheme['shadow']['modal'];
|
'--affine-modal-shadow': AffineTheme['shadow']['modal'];
|
||||||
'--affine-popover-shadow': AffineTheme['shadow']['popover'];
|
'--affine-popover-shadow': AffineTheme['shadow']['popover'];
|
||||||
|
|||||||
@@ -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? `````
|
[] What about a code block? `````
|
||||||
|
|
||||||
```javascript
|
```JavaScript
|
||||||
console.log('Hello world');
|
console.log('Hello world');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
62
packages/i18n/README.md
Normal file
62
packages/i18n/README.md
Normal file
@@ -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 (
|
||||||
|
<div>
|
||||||
|
<div>{t('Text')}</div>
|
||||||
|
|
||||||
|
<button onClick={() => changeLanguage('en')}>
|
||||||
|
{t('Switch to language', { language: 'en' })}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => changeLanguage('zh-Hans')}>
|
||||||
|
{t('Switch to language', { language: 'zh-Hans' })}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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/)
|
||||||
@@ -10,7 +10,10 @@
|
|||||||
".": "./dist/src/index.js"
|
".": "./dist/src/index.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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": [],
|
"keywords": [],
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -24,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/prettier": "^2.7.2",
|
"@types/prettier": "^2.7.2",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^4.8.4"
|
"typescript": "^4.8.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
"Add to favourites": "Add to favourites",
|
"Add to favourites": "Add to favourites",
|
||||||
"Paper": "Paper",
|
"Paper": "Paper",
|
||||||
"Edgeless": "Edgeless",
|
"Edgeless": "Edgeless",
|
||||||
"Jump to": "Jump to",
|
|
||||||
"Convert to ": "Convert to ",
|
"Convert to ": "Convert to ",
|
||||||
"Page": "Page",
|
"Page": "Page",
|
||||||
"Export": "Export",
|
"Export": "Export",
|
||||||
@@ -57,6 +56,12 @@
|
|||||||
"Reduce indent": "Reduce indent",
|
"Reduce indent": "Reduce indent",
|
||||||
"Markdown Syntax": "Markdown Syntax",
|
"Markdown Syntax": "Markdown Syntax",
|
||||||
"Divider": "Divider",
|
"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",
|
"404 - Page Not Found": "404 - Page Not Found",
|
||||||
"Once deleted, you can't undo this action": {
|
"Once deleted, you can't undo this action": {
|
||||||
"": "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",
|
"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 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.",
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Run `pnpm run download-resources` to regenerate.
|
// Run `pnpm run download-resources` to regenerate.
|
||||||
// To overwrite this, please overwrite download.ts script.
|
// To overwrite this, please overwrite download.ts script.
|
||||||
import en from './en.json';
|
import en from './en.json';
|
||||||
|
import zh_Hans from './zh-Hans.json';
|
||||||
|
|
||||||
export const LOCALES = [
|
export const LOCALES = [
|
||||||
{
|
{
|
||||||
@@ -14,4 +15,14 @@ export const LOCALES = [
|
|||||||
completeRate: 1,
|
completeRate: 1,
|
||||||
res: en,
|
res: en,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 1000040004,
|
||||||
|
name: 'Simplified Chinese',
|
||||||
|
tag: 'zh-Hans',
|
||||||
|
originalName: '简体中文',
|
||||||
|
flagEmoji: '🇨🇳',
|
||||||
|
base: false,
|
||||||
|
completeRate: 1,
|
||||||
|
res: zh_Hans,
|
||||||
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
148
packages/i18n/src/resources/zh-Hans.json
Normal file
148
packages/i18n/src/resources/zh-Hans.json
Normal file
@@ -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</1> 浏览器以获得最佳体验。",
|
||||||
|
"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": "全部"
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// cSpell:ignore Tolgee
|
// cSpell:ignore Tolgee
|
||||||
import { fetchTolgee } from './request';
|
import { fetchTolgee } from './request.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all project languages
|
* Returns all project languages
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { format } from 'prettier';
|
import { format } from 'prettier';
|
||||||
import { getAllProjectLanguages, getRemoteTranslations } from './api';
|
import { getAllProjectLanguages, getRemoteTranslations } from './api.js';
|
||||||
import type { TranslationRes } from './utils';
|
import type { TranslationRes } from './utils.js';
|
||||||
|
|
||||||
const RES_DIR = path.resolve(process.cwd(), 'src', 'resources');
|
const RES_DIR = path.resolve(process.cwd(), 'src', 'resources');
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ const main = async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const availableLanguages = languagesWithTranslations.filter(
|
const availableLanguages = languagesWithTranslations.filter(
|
||||||
language => language.completeRate > 0
|
language => language.completeRate === 1
|
||||||
);
|
);
|
||||||
|
|
||||||
availableLanguages
|
availableLanguages
|
||||||
@@ -90,7 +90,7 @@ const main = async () => {
|
|||||||
console.log('Generating meta data...');
|
console.log('Generating meta data...');
|
||||||
const code = `// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
const code = `// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
// Run \`pnpm run download-resources\` to regenerate.
|
// Run \`pnpm run download-resources\` to regenerate.
|
||||||
// To overwrite this, please overwrite ${path.basename(__filename)}
|
// To overwrite this, please overwrite download.ts script.
|
||||||
${availableLanguages
|
${availableLanguages
|
||||||
.map(
|
.map(
|
||||||
language =>
|
language =>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
// cSpell:ignore Tolgee
|
// cSpell:ignore Tolgee
|
||||||
import { readFile } from 'fs/promises';
|
import { readFile } from 'fs/promises';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { createsNewKey, getRemoteTranslations } from './api';
|
import { createsNewKey, getRemoteTranslations } from './api.js';
|
||||||
import type { TranslationRes } from './utils';
|
import type { TranslationRes } from './utils.js';
|
||||||
|
|
||||||
const BASE_JSON_PATH = path.resolve(
|
const BASE_JSON_PATH = path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
Reference in New Issue
Block a user