mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-01 17:39:55 +08:00
refactor(i18n): new hook api (#7273)
# NEW HOOK API
`useI18n`: same as `useAFFiNEI18N`, with additional APIs
```ts
import { useI18n } from '@affine/i18n'
const i18n = useI18n()
i18n['hello world']() -> 你好世界
```
# NEW GLOBAL i18n Instance
`I18n`: use i18n capabilities outside of React
```ts
import { I18n } from '@affine/i18n'
I18n['hello world']() -> 你好世界
```
# NEW TYPES
`I18nKeys` -> all i18n keys
`I18nString` -> An i18n message (key&options)
transfer and store i18n text outside of React
```ts
const msg: I18nString = {
key: 'helloworld',
options: {
arg1: '123'
}
}
I18n.t(msg) -> 你好世界123
```
before:
```ts
registerCommand('open-page', {
name: t('command.open-page')
// ^- translation happens here,
})
```
after:
```ts
registerCommand('open-page', {
name: { key: 'command.open-page' }
// ^- store I18nString here, translate when the command render to UI
})
```
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { getI18n } from 'react-i18next';
|
||||
import { useMemo } from 'react';
|
||||
import { getI18n, useTranslation } from 'react-i18next';
|
||||
|
||||
import type { useAFFiNEI18N } from './i18n-generated';
|
||||
|
||||
@@ -22,33 +23,64 @@ export type I18nString =
|
||||
}[I18nKeys]
|
||||
| string;
|
||||
|
||||
const I18nMethod = {
|
||||
t(i18nStr: I18nString) {
|
||||
const i18n = getI18n();
|
||||
if (typeof i18nStr === 'object') {
|
||||
return i18n.t(i18nStr.key, 'options' in i18nStr ? i18nStr.options : {});
|
||||
}
|
||||
return i18nStr;
|
||||
},
|
||||
get language() {
|
||||
const i18n = getI18n();
|
||||
return i18n.language;
|
||||
},
|
||||
export const isI18nString = (value: any): value is I18nString => {
|
||||
return (
|
||||
typeof value === 'string' || (typeof value === 'object' && 'key' in value)
|
||||
);
|
||||
};
|
||||
|
||||
const I18nProxy = new Proxy(I18nMethod, {
|
||||
get(self, key) {
|
||||
const i18n = getI18n();
|
||||
if (typeof key === 'string' && i18n.exists(key)) {
|
||||
return i18n.t.bind(i18n, key as string);
|
||||
} else {
|
||||
return (self as any)[key as string] as any;
|
||||
}
|
||||
},
|
||||
});
|
||||
function createI18nWrapper(
|
||||
getI18nFn: () => ReturnType<typeof getI18n>,
|
||||
getI18nT: () => ReturnType<typeof getI18n>['t']
|
||||
) {
|
||||
const I18nMethod = {
|
||||
t(i18nStr: I18nString) {
|
||||
const i18n = getI18nFn();
|
||||
if (typeof i18nStr === 'object') {
|
||||
return i18n.t(i18nStr.key, 'options' in i18nStr ? i18nStr.options : {});
|
||||
}
|
||||
return i18nStr;
|
||||
},
|
||||
get language() {
|
||||
const i18n = getI18nFn();
|
||||
return i18n.language;
|
||||
},
|
||||
changeLanguage(lng?: string | undefined) {
|
||||
const i18n = getI18nFn();
|
||||
return i18n.changeLanguage(lng);
|
||||
},
|
||||
get on() {
|
||||
const i18n = getI18nFn();
|
||||
return i18n.on.bind(i18n);
|
||||
},
|
||||
};
|
||||
|
||||
return new Proxy(I18nMethod, {
|
||||
get(self, key) {
|
||||
const i18n = getI18nFn();
|
||||
if (typeof key === 'string' && i18n.exists(key)) {
|
||||
return getI18nT().bind(null, key as string);
|
||||
} else {
|
||||
return (self as any)[key as string] as any;
|
||||
}
|
||||
},
|
||||
}) as I18nFuncs & typeof I18nMethod;
|
||||
}
|
||||
|
||||
export const useI18n = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
return useMemo(
|
||||
() =>
|
||||
createI18nWrapper(
|
||||
() => i18n,
|
||||
() => t
|
||||
),
|
||||
[i18n, t]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* I18n['com.affine.xxx']({ arg1: 'hello' }) -> '中文 hello'
|
||||
*/
|
||||
export const I18n = I18nProxy as I18nFuncs & typeof I18nMethod;
|
||||
export const I18n = createI18nWrapper(getI18n, () => getI18n().t);
|
||||
export type I18n = typeof I18n;
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import type { i18n, Resource } from 'i18next';
|
||||
import i18next from 'i18next';
|
||||
import type { I18nextProviderProps } from 'react-i18next';
|
||||
import {
|
||||
I18nextProvider,
|
||||
initReactI18next,
|
||||
Trans,
|
||||
useTranslation as useRootTranslation,
|
||||
} from 'react-i18next';
|
||||
import { I18nextProvider, initReactI18next, Trans } from 'react-i18next';
|
||||
|
||||
import { LOCALES } from './resources';
|
||||
import type en_US from './resources/en.json';
|
||||
|
||||
export * from './i18n';
|
||||
export * from './i18n-generated';
|
||||
export * from './utils';
|
||||
|
||||
declare module 'i18next' {
|
||||
@@ -44,12 +38,6 @@ declare module 'react-i18next' {
|
||||
const STORAGE_KEY = 'i18n_lng';
|
||||
|
||||
export { I18nextProvider, LOCALES, Trans };
|
||||
export function useI18N() {
|
||||
const { i18n } = useRootTranslation();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export { getI18n } from 'react-i18next';
|
||||
|
||||
const resources = LOCALES.reduce<Resource>((acc, { tag, res }) => {
|
||||
return Object.assign(acc, { [tag]: { translation: res } });
|
||||
@@ -99,6 +87,7 @@ export const createI18n = (): I18nextProviderProps['i18n'] => {
|
||||
}
|
||||
return i18n;
|
||||
};
|
||||
|
||||
export function setUpLanguage(i: i18n) {
|
||||
let language;
|
||||
const localStorageLanguage = localStorage.getItem(STORAGE_KEY);
|
||||
@@ -109,5 +98,3 @@ export function setUpLanguage(i: i18n) {
|
||||
}
|
||||
return i.changeLanguage(language);
|
||||
}
|
||||
|
||||
// const I18nProvider = I18nextProvider;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { createI18n, getI18n } from '../../';
|
||||
import { createI18n, I18n } from '../../';
|
||||
import { i18nTime } from '../time';
|
||||
|
||||
// Intl api is not available in github action, skip the test
|
||||
describe.skip('humanTime', () => {
|
||||
describe('humanTime', () => {
|
||||
test('absolute', async () => {
|
||||
createI18n();
|
||||
expect(i18nTime('2024-10-10 13:30:28')).toBe('Oct 10, 2024, 1:30:28 PM');
|
||||
@@ -350,7 +350,7 @@ describe.skip('humanTime', () => {
|
||||
|
||||
test('chinese', () => {
|
||||
createI18n();
|
||||
getI18n().changeLanguage('zh-Hans');
|
||||
I18n.changeLanguage('zh-Hans');
|
||||
expect(i18nTime('2024-10-10 13:30:28.005')).toBe('2024年10月10日 13:30:28');
|
||||
expect(
|
||||
i18nTime('2024-10-10 13:30:28.005', {
|
||||
|
||||
Reference in New Issue
Block a user