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:
@@ -1,16 +1,22 @@
|
|||||||
import React, { createContext, useContext, useState, ReactNode, useCallback } from "react";
|
import React, { createContext, useContext, useState, ReactNode, useCallback } from "react";
|
||||||
import { translations, Language, TranslationModule, TranslationKey } from "@/translations";
|
import { translations, Language, TranslationModule, TranslationKey } from "@/translations";
|
||||||
|
|
||||||
|
type TranslationVars = Record<string, string | number>;
|
||||||
|
|
||||||
type LanguageContextType = {
|
type LanguageContextType = {
|
||||||
language: Language;
|
language: Language;
|
||||||
setLanguage: (language: Language) => void;
|
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);
|
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
|
||||||
|
|
||||||
// ✅ Stable custom hook
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
export const useLanguage = () => {
|
export const useLanguage = () => {
|
||||||
const context = useContext(LanguageContext);
|
const context = useContext(LanguageContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
@@ -23,33 +29,76 @@ export const LanguageProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
const [language, setLanguage] = useState<Language>("en");
|
const [language, setLanguage] = useState<Language>("en");
|
||||||
const fallbackLanguage: 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 langPack = translations[language];
|
||||||
const fallbackPack = translations[fallbackLanguage];
|
const fallbackPack = translations[fallbackLanguage];
|
||||||
|
let translatedText = "";
|
||||||
|
|
||||||
if (module) {
|
if (module) {
|
||||||
const valCur = langPack?.[module]?.[key as TranslationKey<M>];
|
const moduleKey = key as TranslationKey<typeof module>;
|
||||||
if (typeof valCur === "string") return valCur;
|
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>];
|
for (const mod of Object.keys(langPack) as TranslationModule[]) {
|
||||||
if (typeof valEn === "string") return valEn;
|
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) {
|
return variables ? interpolate(translatedText, variables) : translatedText;
|
||||||
const m = mod as TranslationModule;
|
}) as LanguageContextType['t'], [language]);
|
||||||
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 (
|
return (
|
||||||
<LanguageContext.Provider value={{ language, setLanguage, t }}>
|
<LanguageContext.Provider value={{ language, setLanguage, t }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user