feat(i18n): Enhanced internationalization support with variable interpolation

- Refactored the `t` function in LanguageContext to support multiple parameter combinations
- Added variable interpolation feature, allowing variables in translated texts
- Optimized translation logic to improve accuracy and performance
- Enhanced type safety by using TypeScript generics to ensure correct typing
This commit is contained in:
YiZixuan
2025-09-09 22:38:43 +08:00
parent b6d4ca5269
commit 1296cf6a32
+72 -23
View File
@@ -1,16 +1,22 @@
import React, { createContext, useContext, useState, ReactNode, useCallback } from "react";
import { translations, Language, TranslationModule, TranslationKey } from "@/translations";
type TranslationVars = Record<string, string | number>;
type LanguageContextType = {
language: Language;
setLanguage: (language: Language) => void;
t: <M extends TranslationModule>(key: string, module?: M) => string;
t: {
(key: string): string;
(key: string, vars: TranslationVars): string;
<M extends TranslationModule>(key: string, module: M): string;
<M extends TranslationModule>(key: string, module: M, vars: TranslationVars): string;
};
};
// ❗ Create the context with `undefined` to enforce provider usage
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
// ✅ Stable custom hook
// eslint-disable-next-line react-refresh/only-export-components
export const useLanguage = () => {
const context = useContext(LanguageContext);
if (!context) {
@@ -23,33 +29,76 @@ export const LanguageProvider = ({ children }: { children: ReactNode }) => {
const [language, setLanguage] = useState<Language>("en");
const fallbackLanguage: Language = "en";
const t = useCallback(<M extends TranslationModule>(key: string, module?: M): string => {
const interpolate = (template: string, vars: TranslationVars): string => {
return template.replace(/{(\w+)}/g, (match, key) => {
return vars[key] !== undefined ? String(vars[key]) : match;
});
};
const t = useCallback(((
key: string,
moduleOrVars?: TranslationModule | TranslationVars,
vars?: TranslationVars
): string => {
let module: TranslationModule | undefined;
let variables: TranslationVars | undefined;
if (typeof moduleOrVars === "string") {
module = moduleOrVars;
variables = vars;
} else if (typeof moduleOrVars === "object") {
variables = moduleOrVars;
}
const langPack = translations[language];
const fallbackPack = translations[fallbackLanguage];
let translatedText = "";
if (module) {
const valCur = langPack?.[module]?.[key as TranslationKey<M>];
if (typeof valCur === "string") return valCur;
const moduleKey = key as TranslationKey<typeof module>;
const valCur = langPack?.[module]?.[moduleKey];
if (typeof valCur === "string") {
translatedText = valCur;
} else {
const valEn = fallbackPack?.[module]?.[moduleKey];
translatedText = typeof valEn === "string" ? valEn : key;
}
} else {
let found = false;
const valEn = fallbackPack?.[module]?.[key as TranslationKey<M>];
if (typeof valEn === "string") return valEn;
for (const mod of Object.keys(langPack) as TranslationModule[]) {
const moduleTranslations = langPack[mod];
if (moduleTranslations && key in moduleTranslations) {
const val = moduleTranslations[key as keyof typeof moduleTranslations];
if (typeof val === "string") {
translatedText = val;
found = true;
break;
}
}
}
return key;
if (!found) {
for (const mod of Object.keys(fallbackPack) as TranslationModule[]) {
const moduleTranslations = fallbackPack[mod];
if (moduleTranslations && key in moduleTranslations) {
const val = moduleTranslations[key as keyof typeof moduleTranslations];
if (typeof val === "string") {
translatedText = val;
found = true;
break;
}
}
}
}
if (!found) {
translatedText = key;
}
}
for (const mod in langPack) {
const m = mod as TranslationModule;
const val = langPack[m]?.[key as any];
if (typeof val === "string") return val;
}
for (const mod in fallbackPack) {
const m = mod as TranslationModule;
const val = fallbackPack[m]?.[key as any];
if (typeof val === "string") return val;
}
return key;
}, [language]);
return variables ? interpolate(translatedText, variables) : translatedText;
}) as LanguageContextType['t'], [language]);
return (
<LanguageContext.Provider value={{ language, setLanguage, t }}>