feat(i18n): initialize i18n infrastructure

This commit is contained in:
ZhenYi 2026-04-27 18:19:10 +08:00
parent 9be635eaf3
commit 74f38d0d42
5 changed files with 92 additions and 0 deletions

0
i18n/en.json Normal file
View File

0
i18n/zh.json Normal file
View File

32
i18next.config.ts Normal file
View File

@ -0,0 +1,32 @@
export default {
// supported languages
locales: ["en", "zh", "de", "fr"],
// extraction config (for i18next-cli)
extract: {
input: "src/**/*.{js,jsx,ts,tsx}",
output: "public/locales/{{language}}/{{ns}}.json",
namespaceSeparator: false,
},
// default namespace
defaultNamespace: "translation",
// output path pattern
resource: {
jsonIndent: 2,
lineEnding: "\n",
},
// sort keys in output
sort: true,
// fail on missing keys during dev
// quiet: false,
// metadata for translation files
metadata: {
description: "Generated by i18next-cli",
generatedAt: new Date().toISOString(),
},
};

28
src/lib/i18n.ts Normal file
View File

@ -0,0 +1,28 @@
import i18n from 'i18next';
import {initReactI18next} from 'react-i18next';
import HttpBackend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(HttpBackend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
supportedLngs: ['en', 'zh', 'de', 'fr'],
defaultNS: 'translation',
ns: ['translation'],
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
detection: {
order: ['localStorage', 'navigator'],
caches: ['localStorage'],
lookupLocalStorage: 'i18nextLng',
},
interpolation: {
escapeValue: false,
},
});
export default i18n;

View File

@ -0,0 +1,32 @@
import {useQuery} from '@tanstack/react-query';
import {type ReactNode, useEffect} from 'react';
import {getPreferences} from '@/client';
import {useUser} from '@/contexts';
import i18n from '@/lib/i18n';
/**
* Syncs the user's server-side language preference to i18n after login.
* This ensures the API-stored preference takes precedence over browser detection.
*/
export function LanguageProvider({children}: {children: ReactNode}) {
const {isAuthenticated} = useUser();
const {data: preferences} = useQuery({
queryKey: ['userPreferences'],
queryFn: async () => {
const resp = await getPreferences();
return resp.data?.data ?? null;
},
enabled: isAuthenticated,
staleTime: 5 * 60 * 1000,
retry: false,
});
useEffect(() => {
if (preferences?.language && preferences.language !== i18n.language) {
i18n.changeLanguage(preferences.language);
}
}, [preferences]);
return children;
}