diff --git a/apps/venus/src/app/common/Header.tsx b/apps/venus/src/app/common/Header.tsx
index 9a2e8d0173..2dc7c50ff0 100644
--- a/apps/venus/src/app/common/Header.tsx
+++ b/apps/venus/src/app/common/Header.tsx
@@ -13,7 +13,7 @@ import AFFiNETextLogo from './affine-text-logo.png';
import { HoverMenu } from './HoverMenu';
import { MobileHeader } from './MobileHeader';
-import { options } from '../i18n';
+import { LOCALES } from '../i18n';
export const AFFiNEHeader = () => {
const matches = useMediaQuery('(max-width: 1024px)');
@@ -140,15 +140,15 @@ export const AFFiNEHeader = () => {
}}
/>
diff --git a/apps/venus/src/app/i18n/index.ts b/apps/venus/src/app/i18n/index.ts
index 9ddaa8330d..f4428a9b09 100644
--- a/apps/venus/src/app/i18n/index.ts
+++ b/apps/venus/src/app/i18n/index.ts
@@ -1,25 +1,63 @@
-import i18next from 'i18next';
-import { initReactI18next } from 'react-i18next';
-import en_US from './resources/en.json';
-import zh_CN from './resources/zh.json';
+import i18next, { Resource } from 'i18next';
+import {
+ I18nextProvider,
+ initReactI18next,
+ useTranslation,
+} from 'react-i18next';
+import { LOCALES } from './resources';
+import type en_US from './resources/en.json';
-const resources = {
- en: en_US,
- zh: zh_CN,
-} as const;
+// See https://react.i18next.com/latest/typescript
+declare module 'react-i18next' {
+ interface CustomTypeOptions {
+ // custom namespace type if you changed it
+ // defaultNS: 'ns1';
+ // custom resources type
+ resources: {
+ en: typeof en_US;
+ };
+ }
+}
+
+const STORAGE_KEY = 'i18n_lng';
+
+export { i18n, useTranslation, I18nProvider, LOCALES };
+
+const resources = LOCALES.reduce(
+ (acc, { tag, res }) => ({ ...acc, [tag]: { translation: res } }),
+ {}
+);
+
+const fallbackLng = LOCALES[0].tag;
+const standardizeLocale = (language: string) => {
+ if (LOCALES.find(locale => locale.tag === language)) return language;
+ if (
+ LOCALES.find(
+ locale => locale.tag === language.slice(0, 2).toLowerCase()
+ )
+ )
+ return language;
+ return fallbackLng;
+};
+
+const language = standardizeLocale(
+ localStorage.getItem(STORAGE_KEY) ?? navigator.language
+);
+
+const i18n = i18next.createInstance();
+i18n.use(initReactI18next).init({
+ lng: language,
+ fallbackLng,
+ debug: process.env['NODE_ENV'] === 'development',
-i18next.use(initReactI18next).init({
- lng: 'en',
- fallbackLng: 'en',
resources,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
});
-export const options = [
- { value: 'en', text: 'English' },
- { value: 'zh', text: '简体中文' },
-];
+i18n.on('languageChanged', lng => {
+ localStorage.setItem(STORAGE_KEY, lng);
+});
-export { i18next };
+const I18nProvider = I18nextProvider;