feat(i18n): initialize i18n infrastructure
This commit is contained in:
parent
9be635eaf3
commit
74f38d0d42
0
i18n/en.json
Normal file
0
i18n/en.json
Normal file
0
i18n/zh.json
Normal file
0
i18n/zh.json
Normal file
32
i18next.config.ts
Normal file
32
i18next.config.ts
Normal 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
28
src/lib/i18n.ts
Normal 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;
|
||||||
32
src/lib/language-provider.tsx
Normal file
32
src/lib/language-provider.tsx
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user