// ───────────────────────────────────────────────────────────────────────────── // i18n.ts — i18next configuration // // Import this file once at the application entry point (main.tsx) BEFORE any // component is rendered. The side-effect initializes i18next synchronously so // that `useTranslation()` is ready on first render. // // Locale layout (split for maintainability — merged at boot into a single // `translation` namespace so all existing `t('home.welcome')`-style calls // keep working without changes): // // locales//{common,nav,errors,languageSelector,themeToggle}.json // locales//pages/{home,templates,login,resetPassword,verifyEmail}.json // locales//components/{assistant,sidebar,media,projectCard, // profileCard,notificationCard,activityCard, // stats,team}.json // // The chunks for each language are gathered via `import.meta.glob` (resolved // statically at build time by Vite — no runtime fetch, fully tree-shakable), // so adding a new JSON file under `locales//...` is picked up // automatically without touching this file. // // Adding a language — three options: // // Option A) Drop-in via LanguageProvider (recommended for app consumers): // // The provider calls i18n.addResourceBundle() automatically. // // Option B) Manual registration via the helper exported below: // import { registerLanguageResource } from 'xertica-ui'; // registerLanguageResource('fr', frJson); // // Option C) Add it here statically (only for the library itself): // 1. Create the new locales// folder (mirror an existing language) // 2. Add one more `import.meta.glob` call below + an entry in `resources`. // ───────────────────────────────────────────────────────────────────────────── import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; // ───────────────────────────────────────────────────────────────────────────── // Build a single { key: contents } bundle from a Vite glob result. // // Input (Vite glob output): { './locales/pt-BR/common.json': {...}, // './locales/pt-BR/pages/home.json': {...}, // './locales/pt-BR/components/assistant.json': {...}, // ... } // Output (bundle for i18n) : { common: {...}, home: {...}, assistant: {...}, ... } // // Keys are derived from the file basename — folder (pages/components) is // discarded because the legacy flat JSON had a single namespace. // ───────────────────────────────────────────────────────────────────────────── function bundleLang(chunks: Record): Record { const out: Record = {}; for (const [filePath, value] of Object.entries(chunks)) { const base = filePath.split('/').pop(); if (!base) continue; const key = base.replace(/\.json$/, ''); out[key] = value; } return out; } // `import.meta.glob` requires a literal pattern — we expand it once per // language. `eager: true` inlines the JSON at build time. `import: 'default'` // returns the JSON value directly instead of `{ default: ... }`. const ptBR = bundleLang( import.meta.glob('./locales/pt-BR/**/*.json', { eager: true, import: 'default' }) ); const en = bundleLang( import.meta.glob('./locales/en/**/*.json', { eager: true, import: 'default' }) ); const es = bundleLang( import.meta.glob('./locales/es/**/*.json', { eager: true, import: 'default' }) ); const STORAGE_KEY = 'xertica_language'; const DEFAULT_FALLBACK = 'pt-BR'; const savedLanguage = typeof window !== 'undefined' ? (localStorage.getItem(STORAGE_KEY) ?? DEFAULT_FALLBACK) : DEFAULT_FALLBACK; i18n.use(initReactI18next).init({ resources: { 'pt-BR': { translation: ptBR }, en: { translation: en }, es: { translation: es }, }, lng: savedLanguage, fallbackLng: DEFAULT_FALLBACK, interpolation: { // React already escapes values — no need for i18next to double-escape escapeValue: false, }, }); /** * Register an additional locale at runtime. Useful when an app wants to add a * language without editing the i18n.ts file itself. Safe to call multiple * times — a bundle is only added the first time it appears for a given * `(code, namespace)` pair. * * @param code BCP-47 language code (e.g. `'fr'`, `'ja'`, `'pt-PT'`) * @param json Translation object — same shape as the bundled `` object * above (top-level keys `common`, `nav`, `home`, etc.) * @param ns Translation namespace (defaults to `'translation'`, which is * the only namespace used by Xertica UI) */ export function registerLanguageResource( code: string, json: Record, ns: string = 'translation' ): void { if (!i18n.hasResourceBundle(code, ns)) { i18n.addResourceBundle(code, ns, json, true, true); } } export default i18n;