mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 12:55:00 +00:00
162 lines
4.3 KiB
TypeScript
162 lines
4.3 KiB
TypeScript
import { DebugLogger } from '@affine/debug';
|
|
import type { BackendModule, i18n } from 'i18next';
|
|
import i18next from 'i18next';
|
|
import { initReactI18next } from 'react-i18next';
|
|
|
|
import type { useAFFiNEI18N } from './i18n-generated';
|
|
import type { Language } from './resources';
|
|
import { SUPPORTED_LANGUAGES } from './resources';
|
|
|
|
const logger = new DebugLogger('i18n');
|
|
|
|
const defaultLng: Language = 'en';
|
|
|
|
let _instance: i18n | null = null;
|
|
export const getOrCreateI18n = (): i18n => {
|
|
if (!_instance) {
|
|
_instance = i18next.createInstance();
|
|
_instance
|
|
.use(initReactI18next)
|
|
.use({
|
|
type: 'backend',
|
|
init: () => {},
|
|
read: (lng: Language, _ns: string, callback) => {
|
|
const resource = SUPPORTED_LANGUAGES[lng].resource;
|
|
if (typeof resource === 'function') {
|
|
resource()
|
|
.then(data => {
|
|
logger.info(`Loaded i18n ${lng} resource`);
|
|
callback(null, data.default);
|
|
})
|
|
.catch(err => {
|
|
logger.error(`Failed to load i18n ${lng} resource`, err);
|
|
callback(null, null);
|
|
});
|
|
} else {
|
|
callback(null, resource);
|
|
}
|
|
},
|
|
} as BackendModule)
|
|
.init({
|
|
lng: defaultLng,
|
|
fallbackLng: code => {
|
|
// always fallback to english
|
|
const fallbacks: string[] = [defaultLng];
|
|
const langPart = code.split('-')[0];
|
|
|
|
// fallback xx-YY to xx, e.g. es-AR to es
|
|
// fallback zh-Hant to zh-Hans
|
|
if (langPart === 'cn') {
|
|
fallbacks.push('zh-Hans');
|
|
} else if (
|
|
langPart !== code &&
|
|
SUPPORTED_LANGUAGES[code as Language]
|
|
) {
|
|
fallbacks.unshift(langPart);
|
|
}
|
|
|
|
return fallbacks;
|
|
},
|
|
supportedLngs: Object.keys(SUPPORTED_LANGUAGES),
|
|
debug: false,
|
|
partialBundledLanguages: true,
|
|
resources: {
|
|
[defaultLng]: {
|
|
translation: SUPPORTED_LANGUAGES[defaultLng].resource,
|
|
},
|
|
},
|
|
interpolation: {
|
|
escapeValue: false, // not needed for react as it escapes by default
|
|
},
|
|
})
|
|
.then(() => {
|
|
logger.info('i18n initialized');
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
return _instance;
|
|
};
|
|
|
|
declare module 'i18next' {
|
|
interface CustomTypeOptions {
|
|
// NOTE(@forehalo):
|
|
// DO NOT ENABLE THIS
|
|
// This could bring typecheck for <Trans /> component,
|
|
// but it will make typecheck of whole codebase so laggy!
|
|
// check [./react.ts]
|
|
// resources: {
|
|
// translation: LanguageResource;
|
|
// };
|
|
}
|
|
}
|
|
|
|
export type I18nFuncs = ReturnType<typeof useAFFiNEI18N>;
|
|
type KnownI18nKey = keyof I18nFuncs;
|
|
|
|
export type I18nString =
|
|
| KnownI18nKey
|
|
| string
|
|
| { i18nKey: string; options?: Record<string, any> };
|
|
|
|
export function isI18nString(value: unknown): value is I18nString {
|
|
if (typeof value === 'string') {
|
|
return true;
|
|
}
|
|
|
|
if (typeof value === 'object' && value !== null) {
|
|
return 'i18nKey' in value;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function createI18nWrapper(getI18nFn: () => i18n) {
|
|
const I18nMethod = {
|
|
t(key: I18nString, options?: Record<string, any>) {
|
|
if (typeof key === 'object' && 'i18nKey' in key) {
|
|
options = key.options;
|
|
key = key.i18nKey as string;
|
|
}
|
|
|
|
const i18n = getI18nFn();
|
|
if (i18n.exists(key)) {
|
|
return i18n.t(key, options);
|
|
} else {
|
|
// unknown translate key 'xxx.xxx' returns itself
|
|
return key;
|
|
}
|
|
},
|
|
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: string) {
|
|
if (key in self) {
|
|
// @ts-expect-error allow
|
|
return self[key];
|
|
}
|
|
|
|
return I18nMethod.t.bind(null, key);
|
|
},
|
|
}) as typeof I18nMethod &
|
|
ReturnType<typeof useAFFiNEI18N> & { [unknownKey: string]: () => string };
|
|
}
|
|
|
|
/**
|
|
* I18n['com.affine.xxx']({ arg1: 'hello' }) -> '中文 hello'
|
|
*/
|
|
export const I18n = createI18nWrapper(getOrCreateI18n);
|
|
export type I18nInstance = typeof I18n;
|