Upgrade UI i18n wrapper to i18next

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-05-15 10:34:20 -05:00
1 parent 24fb5147b6
commit e8f3e9764c
3 files changed
+38 -19

No files matched your search

+1
View File
@@ -20,6 +20,7 @@
"build-storybook": "storybook build -c storybook/.storybook -o storybook-static",
"preview": "vite preview",
"typecheck": "tsc -b",
"test": "vitest run",
"clean": "rm -rf dist storybook-static tsconfig.tsbuildinfo",
"prepack": "rm -f package.dev.json && cp package.json package.dev.json && node ../scripts/generate-ui-package-json.mjs",
"postpack": "if [ -f package.dev.json ]; then mv package.dev.json package.json; fi"
+18 -19
View File
@@ -1,25 +1,24 @@
import { DEFAULT_LOCALE, localeMessages } from "./locales";
import i18n, { type InitOptions, type TOptions } from "i18next";
import { initReactI18next, useTranslation as useReactI18nextTranslation } from "react-i18next";
type TranslationOptions = {
defaultValue?: string;
import { DEFAULT_LOCALE, i18nextResources, supportedLocales } from "./locales";
const i18nextOptions: InitOptions = {
resources: i18nextResources,
lng: DEFAULT_LOCALE,
fallbackLng: DEFAULT_LOCALE,
supportedLngs: supportedLocales,
defaultNS: "translation",
interpolation: { escapeValue: false },
returnObjects: false,
initAsync: false,
};
function resolveLocaleValue(key: string) {
const path = key.split(".");
let value: unknown = localeMessages[DEFAULT_LOCALE];
i18n.use(initReactI18next).init(i18nextOptions);
for (const segment of path) {
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
value = (value as Record<string, unknown>)[segment];
}
return typeof value === "string" ? value : null;
export function t(key: string, options: TOptions = {}) {
return i18n.t(key, options);
}
export function t(key: string, options: TranslationOptions = {}) {
return resolveLocaleValue(key) ?? options.defaultValue ?? key;
}
export function useTranslation() {
return { t };
}
export const useTranslation = useReactI18nextTranslation;
export { i18n };
+19
View File
@@ -1,3 +1,7 @@
import type { Resource } from "i18next";
import { assertValidLocaleMessages } from "./locale-validation";
export const DEFAULT_LOCALE = "en" as const;
const localeModules = import.meta.glob("./locales/*.json", {
@@ -19,4 +23,19 @@ if (!(DEFAULT_LOCALE in localeMessages)) {
throw new Error(`Missing default locale messages for ${DEFAULT_LOCALE}`);
}
for (const [locale, messages] of Object.entries(localeMessages)) {
try {
assertValidLocaleMessages(messages);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Invalid ${locale} locale messages: ${message}`);
}
}
export const supportedLocales = Object.keys(localeMessages);
export const i18nextResources: Resource = Object.fromEntries(
Object.entries(localeMessages).map(([locale, messages]) => [locale, { translation: messages }]),
) as Resource;
export type SupportedLocale = keyof typeof localeMessages;