xertica-ui 2.1.11 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +24 -3
  2. package/README.md +1 -1
  3. package/bin/cli.ts +7 -4
  4. package/bin/language-config.ts +79 -19
  5. package/components/pages/template-content/TemplateContent.tsx +204 -218
  6. package/dist/ThemeContext-BblcjQup.cjs +1855 -0
  7. package/dist/ThemeContext-BgclCB35.js +1856 -0
  8. package/dist/ThemeContext-Bo-W2WZH.js +1856 -0
  9. package/dist/ThemeContext-CP3a0jxy.cjs +1855 -0
  10. package/dist/ThemeContext-DQUOeziy.cjs +1855 -0
  11. package/dist/ThemeContext-GeEBTJ3q.cjs +1621 -0
  12. package/dist/ThemeContext-JyLK9B1o.js +1622 -0
  13. package/dist/ThemeContext-ept8jhXI.js +1856 -0
  14. package/dist/VerifyEmailPage-BIBOKV7Z.js +3214 -0
  15. package/dist/VerifyEmailPage-BiRm7Nh4.cjs +3213 -0
  16. package/dist/VerifyEmailPage-Bvfv8HVQ.js +3214 -0
  17. package/dist/VerifyEmailPage-D-FRj5TU.cjs +3213 -0
  18. package/dist/VerifyEmailPage-RrUApqBN.js +3214 -0
  19. package/dist/VerifyEmailPage-VoMI7MYH.cjs +3213 -0
  20. package/dist/VerifyEmailPage-hdB8JQGv.cjs +3213 -0
  21. package/dist/VerifyEmailPage-vYHbYK3q.js +3214 -0
  22. package/dist/XerticaProvider-AChwphCO.cjs +48 -0
  23. package/dist/XerticaProvider-AbWlr7Af.cjs +48 -0
  24. package/dist/XerticaProvider-BSyFrmC0.js +49 -0
  25. package/dist/XerticaProvider-CUYJZc32.js +49 -0
  26. package/dist/XerticaProvider-CWs6EwNa.js +49 -0
  27. package/dist/XerticaProvider-CiNKjMx1.cjs +48 -0
  28. package/dist/XerticaProvider-D5lLumH-.js +49 -0
  29. package/dist/XerticaProvider-qQUDop71.cjs +48 -0
  30. package/dist/XerticaXLogo-8TTzBjHw.cjs +251 -0
  31. package/dist/XerticaXLogo-B2svDGZh.cjs +251 -0
  32. package/dist/XerticaXLogo-BWaag64t.js +252 -0
  33. package/dist/XerticaXLogo-CFuIlYFH.js +252 -0
  34. package/dist/XerticaXLogo-ChryA6xj.js +252 -0
  35. package/dist/XerticaXLogo-CowGv7BC.js +252 -0
  36. package/dist/XerticaXLogo-DTee_y8X.cjs +251 -0
  37. package/dist/XerticaXLogo-kslQ8Tk_.cjs +251 -0
  38. package/dist/brand.cjs.js +2 -2
  39. package/dist/brand.es.js +2 -2
  40. package/dist/cli.js +54 -12
  41. package/dist/hooks.cjs.js +1 -1
  42. package/dist/hooks.es.js +1 -1
  43. package/dist/i18n.d.ts +2 -1
  44. package/dist/index.cjs.js +5 -5
  45. package/dist/index.es.js +5 -5
  46. package/dist/layout.cjs.js +1 -1
  47. package/dist/layout.es.js +1 -1
  48. package/dist/pages.cjs.js +1 -1
  49. package/dist/pages.es.js +1 -1
  50. package/dist/sidebar-B3EYhli0.cjs +800 -0
  51. package/dist/sidebar-B9NR0lCe.cjs +800 -0
  52. package/dist/sidebar-BvF5I2Ue.cjs +800 -0
  53. package/dist/sidebar-CRMiBtAi.js +801 -0
  54. package/dist/sidebar-CZ2mWaMM.cjs +800 -0
  55. package/dist/sidebar-CplprZpM.js +801 -0
  56. package/dist/sidebar-KIS0C2JH.js +801 -0
  57. package/dist/sidebar-OTO_up7Z.js +801 -0
  58. package/dist/xertica-ui.css +1 -1
  59. package/docs/architecture-improvements.md +11 -31
  60. package/docs/architecture.md +15 -4
  61. package/docs/i18n.md +112 -64
  62. package/docs/llms.md +1 -1
  63. package/guidelines/Guidelines.md +15 -8
  64. package/llms-compact.txt +1 -1
  65. package/llms-full.txt +5 -2
  66. package/llms.txt +1 -1
  67. package/package.json +1 -1
  68. package/templates/CLAUDE.md +165 -180
  69. package/templates/guidelines/Guidelines.md +17 -7
  70. package/templates/package.json +2 -2
  71. package/templates/src/app/App.tsx +1 -1
  72. package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +9 -7
  73. package/templates/src/features/auth/ui/LoginContent.tsx +10 -8
  74. package/templates/src/features/auth/ui/ResetPasswordContent.tsx +179 -177
  75. package/templates/src/features/auth/ui/SocialLoginButtons.tsx +9 -4
  76. package/templates/src/features/auth/ui/VerifyEmailContent.tsx +84 -82
  77. package/templates/src/features/template/ui/CrudTemplate.tsx +115 -100
  78. package/templates/src/features/template/ui/DashboardTemplate.tsx +110 -94
  79. package/templates/src/features/template/ui/FormTemplate.tsx +117 -117
  80. package/templates/src/features/template/ui/LoginTemplate.tsx +59 -52
  81. package/templates/src/features/template/ui/TemplateContent.tsx +1314 -1193
  82. package/templates/src/i18n.ts +54 -7
  83. package/templates/src/locales/en/common.json +21 -0
  84. package/templates/src/locales/en/components/activityCard.json +10 -0
  85. package/templates/src/locales/en/components/assistant.json +119 -0
  86. package/templates/src/locales/en/components/media.json +29 -0
  87. package/templates/src/locales/en/components/notificationCard.json +5 -0
  88. package/templates/src/locales/en/components/profileCard.json +8 -0
  89. package/templates/src/locales/en/components/projectCard.json +10 -0
  90. package/templates/src/locales/en/components/sidebar.json +14 -0
  91. package/templates/src/locales/en/components/stats.json +8 -0
  92. package/templates/src/locales/en/components/team.json +14 -0
  93. package/templates/src/locales/en/errors.json +9 -0
  94. package/templates/src/locales/en/languageSelector.json +7 -0
  95. package/templates/src/locales/en/nav.json +6 -0
  96. package/templates/src/locales/en/pages/crudTemplate.json +25 -0
  97. package/templates/src/locales/en/pages/dashboardTemplate.json +20 -0
  98. package/templates/src/locales/en/pages/forgotPassword.json +10 -0
  99. package/templates/src/locales/en/pages/formTemplate.json +16 -0
  100. package/templates/src/locales/en/pages/home.json +7 -0
  101. package/templates/src/locales/en/pages/login.json +15 -0
  102. package/templates/src/locales/en/pages/loginTemplate.json +9 -0
  103. package/templates/src/locales/en/pages/resetPassword.json +18 -0
  104. package/templates/src/locales/en/pages/templates.json +317 -0
  105. package/templates/src/locales/en/pages/verifyEmail.json +12 -0
  106. package/templates/src/locales/en/themeToggle.json +6 -0
  107. package/templates/src/locales/es/common.json +21 -0
  108. package/templates/src/locales/es/components/activityCard.json +10 -0
  109. package/templates/src/locales/es/components/assistant.json +119 -0
  110. package/templates/src/locales/es/components/media.json +29 -0
  111. package/templates/src/locales/es/components/notificationCard.json +5 -0
  112. package/templates/src/locales/es/components/profileCard.json +8 -0
  113. package/templates/src/locales/es/components/projectCard.json +10 -0
  114. package/templates/src/locales/es/components/sidebar.json +14 -0
  115. package/templates/src/locales/es/components/stats.json +8 -0
  116. package/templates/src/locales/es/components/team.json +14 -0
  117. package/templates/src/locales/es/errors.json +9 -0
  118. package/templates/src/locales/es/languageSelector.json +7 -0
  119. package/templates/src/locales/es/nav.json +6 -0
  120. package/templates/src/locales/es/pages/crudTemplate.json +25 -0
  121. package/templates/src/locales/es/pages/dashboardTemplate.json +20 -0
  122. package/templates/src/locales/es/pages/forgotPassword.json +10 -0
  123. package/templates/src/locales/es/pages/formTemplate.json +16 -0
  124. package/templates/src/locales/es/pages/home.json +7 -0
  125. package/templates/src/locales/es/pages/login.json +15 -0
  126. package/templates/src/locales/es/pages/loginTemplate.json +9 -0
  127. package/templates/src/locales/es/pages/resetPassword.json +18 -0
  128. package/templates/src/locales/es/pages/templates.json +317 -0
  129. package/templates/src/locales/es/pages/verifyEmail.json +12 -0
  130. package/templates/src/locales/es/themeToggle.json +6 -0
  131. package/templates/src/locales/pt-BR/common.json +21 -0
  132. package/templates/src/locales/pt-BR/components/activityCard.json +10 -0
  133. package/templates/src/locales/pt-BR/components/assistant.json +119 -0
  134. package/templates/src/locales/pt-BR/components/media.json +29 -0
  135. package/templates/src/locales/pt-BR/components/notificationCard.json +5 -0
  136. package/templates/src/locales/pt-BR/components/profileCard.json +8 -0
  137. package/templates/src/locales/pt-BR/components/projectCard.json +10 -0
  138. package/templates/src/locales/pt-BR/components/sidebar.json +14 -0
  139. package/templates/src/locales/pt-BR/components/stats.json +8 -0
  140. package/templates/src/locales/pt-BR/components/team.json +14 -0
  141. package/templates/src/locales/pt-BR/errors.json +9 -0
  142. package/templates/src/locales/pt-BR/languageSelector.json +7 -0
  143. package/templates/src/locales/pt-BR/nav.json +6 -0
  144. package/templates/src/locales/pt-BR/pages/crudTemplate.json +25 -0
  145. package/templates/src/locales/pt-BR/pages/dashboardTemplate.json +20 -0
  146. package/templates/src/locales/pt-BR/pages/forgotPassword.json +10 -0
  147. package/templates/src/locales/pt-BR/pages/formTemplate.json +16 -0
  148. package/templates/src/locales/pt-BR/pages/home.json +7 -0
  149. package/templates/src/locales/pt-BR/pages/login.json +15 -0
  150. package/templates/src/locales/pt-BR/pages/loginTemplate.json +9 -0
  151. package/templates/src/locales/pt-BR/pages/resetPassword.json +18 -0
  152. package/templates/src/locales/pt-BR/pages/templates.json +317 -0
  153. package/templates/src/locales/pt-BR/pages/verifyEmail.json +12 -0
  154. package/templates/src/locales/pt-BR/themeToggle.json +6 -0
  155. package/templates/src/pages/AssistantPage.tsx +464 -463
  156. package/templates/vite.config.ts +35 -5
  157. package/templates/src/locales/en.json +0 -306
  158. package/templates/src/locales/es.json +0 -306
  159. package/templates/src/locales/pt-BR.json +0 -306
package/CHANGELOG.md CHANGED
@@ -9,20 +9,41 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ---
13
+
14
+ ## [2.2.1] — 2026-05-21
15
+
12
16
  ### Added
13
17
 
18
+ - **i18n — Cobertura completa na página de Template** — `TemplateContent.tsx` e os 4 starter templates (`LoginTemplate`, `FormTemplate`, `DashboardTemplate`, `CrudTemplate`) estão 100% traduzidos (pt-BR, en, es). Anteriormente a maioria dos textos era hard-coded em PT-BR.
19
+ - **i18n — Namespace `templates.*` completo** — novo namespace com 539 chaves cobrindo todos os textos visíveis: seções, alertas, abas, formulários, tabela de dados, configurações, botões (variantes/tamanhos), badges, dialogs, sidebar, componentes avançados (Pagination, Stepper, TreeView, RichTextEditor) e rodapé. Suporte completo para `<Trans>` com interpolação de `<code>` nas descrições técnicas.
20
+ - **i18n — Novos namespaces de starter templates** — 4 novos arquivos de locale por idioma: `loginTemplate`, `formTemplate`, `dashboardTemplate`, `crudTemplate` (total de 12 novos JSONs × 2 mirrors = 24 arquivos).
21
+ - **i18n — Estrutura de locales reorganizada** — os monolitos `locales/<lang>.json` foram divididos em pastas por categoria:
22
+ - `locales/<lang>/{common,nav,errors,languageSelector,themeToggle}.json` (transversal)
23
+ - `locales/<lang>/pages/{home,templates,login,resetPassword,verifyEmail,loginTemplate,formTemplate,dashboardTemplate,crudTemplate}.json`
24
+ - `locales/<lang>/components/{assistant,sidebar,media,projectCard,profileCard,notificationCard,activityCard,stats,team}.json`
25
+ - **i18n — Loader via `import.meta.glob`** — `i18n.ts` (base e templates) substituiu os 57 imports estáticos por 3 chamadas `import.meta.glob('./locales/<lang>/**/*.json', { eager: true })` + função `bundleLang()`. Adicionar um novo arquivo JSON é suficiente — sem tocar no `i18n.ts`.
26
+ - **CLI — Locale em pastas** — `syncLocaleFiles` e `generateI18nFile` em `bin/language-config.ts` atualizados para copiar/gerar a nova estrutura de pastas. Projetos legados com `<lang>.json` flat são migrados automaticamente no próximo `update`.
27
+ - **`templates/vite.config.ts` — aliases de monorepo** — aliases automáticos detectam se o `vite.config.ts` está dentro do monorepo (`../components/index.ts` existe) e apontam `xertica-ui/*` diretamente para o source TypeScript, permitindo HMR sem rebuild da lib. Em projetos gerados pelo CLI, o check retorna `false` e `xertica-ui` resolve do `node_modules` normalmente.
14
28
  - **CLI — Language selection on `init`** — `npx xertica-ui init` now prompts for the languages the project should support (multi-select with `pt-BR`, `en`, `es`; all selected by default; minimum 1). The CLI:
15
- - Copies **only** the JSON files for the selected languages into `src/locales/` (no orphan locales)
16
- - Generates `src/i18n.ts` with imports/resources for exactly those languages
29
+ - Copies **only** the locale folders for the selected languages into `src/locales/` (no orphan locales)
30
+ - Generates `src/i18n.ts` with `import.meta.glob` calls for exactly those languages
17
31
  - Injects the `availableLanguages` prop into the generated `src/app/App.tsx`
18
32
  - Persists the selection in `src/locales/.languages.json` (schema v1)
19
- - **CLI — `update` → Languages** — new option in the `update` command lets users add or remove languages later. Shows a diff (`+ es`, `- en`) before confirming. Regenerates `App.tsx`, `i18n.ts`, copies new locale JSONs, and prunes removed ones.
33
+ - **CLI — `update` → Languages** — new option in the `update` command lets users add or remove languages later. Shows a diff (`+ es`, `- en`) before confirming. Regenerates `App.tsx`, `i18n.ts`, copies new locale folders, and prunes removed ones.
20
34
  - **CLI — Monolingual auto-detection** — when only one language is selected, the generated `App.tsx` includes a banner comment documenting that the `LanguageSelector` will auto-hide.
21
35
  - **`bin/language-config.ts`** — new module encapsulating the supported-language registry (`SUPPORTED_LANGUAGES`), code generators (`generateI18nFile`, `generateAppTsx`), and persistence helpers (`readLanguagesConfig`, `writeLanguagesConfig`, `syncLocaleFiles`).
22
36
 
37
+ ### Fixed
38
+
39
+ - **Dark mode toggle no CLI** — `XerticaProvider` no `templates/src/app/App.tsx` era gerado com `disableDarkMode` hard-coded, tornando o `ThemeToggle` um no-op. Removido de `App.tsx`, do template literal do CLI em `bin/language-config.ts` e do exemplo em `templates/CLAUDE.md`.
40
+
23
41
  ### Changed
24
42
 
25
43
  - **CLI — `update` → Project files (`app` branch)** — instead of blindly overwriting `App.tsx` and `i18n.ts` with the static template (which would erase the user's language selection), the update flow now reads the persisted selection from `src/locales/.languages.json` and regenerates these files honoring it. Projects scaffolded before this feature shipped get their selection inferred from the locale files present and the inferred config is written back.
44
+ - **`DashboardTemplate`** — `StatsCard` deixou de chamar `useTranslation()` internamente; o sufixo de tendência (`lastMonth`) é resolvido no pai e passado como prop, reduzindo o número de subscribers i18n de 5 para 1.
45
+ - **`TemplateContent` — dialog inputs controlados** — os `Input` do dialog "Editar Perfil" foram convertidos de `defaultValue` (uncontrolled) para `value`/`onChange` com lazy `useState`, garantindo que o valor reflita o idioma ativo no momento da montagem sem sobrescrever edições do usuário.
46
+ - **`TemplateContent` — `treeData` movido para dentro do componente** — os labels dos nós do TreeView (`Components`, `UI`, `Button`…) agora reagem ao switch de idioma em tempo real, pois `treeData` é construído após `const { t } = useTranslation()`.
26
47
 
27
48
  ---
28
49
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Enterprise-grade React design system** built on Tailwind CSS v4, Radix UI, and Lucide Icons — with a robust AI-first documentation layer for precise LLM-driven composition and autonomous agent interaction.
4
4
 
5
- [![npm version](https://img.shields.io/badge/npm-2.1.11-blue)](https://www.npmjs.com/package/xertica-ui)
5
+ [![npm version](https://img.shields.io/badge/npm-2.2.1-blue)](https://www.npmjs.com/package/xertica-ui)
6
6
  [![license](https://img.shields.io/badge/license-proprietary-red)](./LICENSE)
7
7
 
8
8
  ---
package/bin/cli.ts CHANGED
@@ -426,8 +426,10 @@ program
426
426
  const localesDir = path.join(targetDir, 'src', 'locales');
427
427
  if (await fs.pathExists(localesDir)) {
428
428
  const entries = await fs.readdir(localesDir);
429
- currentCodes = SUPPORTED_LANGUAGES.filter(l =>
430
- entries.includes(`${l.jsonFile}.json`)
429
+ // Accept both the new folder layout (locales/<code>/) and the legacy
430
+ // flat layout (locales/<code>.json) when inferring the current set.
431
+ currentCodes = SUPPORTED_LANGUAGES.filter(
432
+ l => entries.includes(l.jsonFile) || entries.includes(`${l.jsonFile}.json`)
431
433
  ).map(l => l.code);
432
434
  }
433
435
  if (currentCodes.length === 0) currentCodes = DEFAULT_SELECTION;
@@ -657,8 +659,9 @@ program
657
659
  const localesDir = path.join(targetDir, 'src', 'locales');
658
660
  if (await fs.pathExists(localesDir)) {
659
661
  const entries = await fs.readdir(localesDir);
660
- selectedCodes = SUPPORTED_LANGUAGES.filter(l =>
661
- entries.includes(`${l.jsonFile}.json`)
662
+ // Accept both the new folder layout and the legacy flat layout
663
+ selectedCodes = SUPPORTED_LANGUAGES.filter(
664
+ l => entries.includes(l.jsonFile) || entries.includes(`${l.jsonFile}.json`)
662
665
  ).map(l => l.code);
663
666
  }
664
667
  if (selectedCodes.length === 0) selectedCodes = DEFAULT_SELECTION;
@@ -84,10 +84,19 @@ export async function writeLanguagesConfig(
84
84
  // ── File-system helpers ──────────────────────────────────────────────────────
85
85
 
86
86
  /**
87
- * Copy only the locale JSON files matching the selected codes into the project,
87
+ * Copy only the locale folders matching the selected codes into the project,
88
88
  * leaving previously-copied locales for unselected languages in place ONLY if
89
89
  * `pruneOthers` is false. When `true` (typical for re-selection on update),
90
- * the unselected JSON files are deleted from the project.
90
+ * the unselected language folders are deleted from the project.
91
+ *
92
+ * Each language is shipped as a folder of split JSON files:
93
+ *
94
+ * templates/src/locales/<lang>/{common,nav,errors,languageSelector,themeToggle}.json
95
+ * templates/src/locales/<lang>/pages/<page>.json
96
+ * templates/src/locales/<lang>/components/<component>.json
97
+ *
98
+ * Also migrates legacy projects that still have flat `<lang>.json` files
99
+ * (deletes them so they don't shadow the new folder).
91
100
  */
92
101
  export async function syncLocaleFiles(
93
102
  templatesDir: string,
@@ -104,22 +113,36 @@ export async function syncLocaleFiles(
104
113
  const copied: string[] = [];
105
114
  const removed: string[] = [];
106
115
 
107
- // Copy selected language JSONs from the library templates
116
+ // Copy selected language folders from the library templates
108
117
  for (const lang of selectedLangs) {
109
- const src = path.join(templatesDir, 'src', 'locales', `${lang.jsonFile}.json`);
110
- const dest = path.join(targetLocalesDir, `${lang.jsonFile}.json`);
118
+ const src = path.join(templatesDir, 'src', 'locales', lang.jsonFile);
119
+ const dest = path.join(targetLocalesDir, lang.jsonFile);
111
120
  if (await fs.pathExists(src)) {
121
+ // Clear the target folder first so removed keys don't linger
122
+ await fs.remove(dest);
112
123
  await fs.copy(src, dest, { overwrite: true });
113
- copied.push(`${lang.jsonFile}.json`);
124
+ copied.push(`${lang.jsonFile}/`);
125
+ }
126
+
127
+ // Migrate legacy flat file if it exists alongside the new folder
128
+ const legacyFlat = path.join(targetLocalesDir, `${lang.jsonFile}.json`);
129
+ if (await fs.pathExists(legacyFlat)) {
130
+ await fs.remove(legacyFlat);
114
131
  }
115
132
  }
116
133
 
117
- // Remove unselected language JSONs from the project (when pruning)
134
+ // Remove unselected language folders from the project (when pruning)
118
135
  if (options.pruneOthers) {
119
136
  for (const lang of unselectedLangs) {
120
- const dest = path.join(targetLocalesDir, `${lang.jsonFile}.json`);
137
+ const dest = path.join(targetLocalesDir, lang.jsonFile);
121
138
  if (await fs.pathExists(dest)) {
122
139
  await fs.remove(dest);
140
+ removed.push(`${lang.jsonFile}/`);
141
+ }
142
+ // Also prune any legacy flat file
143
+ const legacyFlat = path.join(targetLocalesDir, `${lang.jsonFile}.json`);
144
+ if (await fs.pathExists(legacyFlat)) {
145
+ await fs.remove(legacyFlat);
123
146
  removed.push(`${lang.jsonFile}.json`);
124
147
  }
125
148
  }
@@ -131,9 +154,13 @@ export async function syncLocaleFiles(
131
154
  // ── Code generators ──────────────────────────────────────────────────────────
132
155
 
133
156
  /**
134
- * Generate the contents of `src/i18n.ts` with imports and `resources` for
135
- * exactly the selected languages — no orphan imports for locales the user
136
- * didn't pick.
157
+ * Generate the contents of `src/i18n.ts` for a freshly-scaffolded project.
158
+ *
159
+ * Each selected language is pulled via Vite's `import.meta.glob` (one glob per
160
+ * language) — resolved statically at build time so JSON gets inlined and
161
+ * tree-shaken. No per-chunk imports to maintain; adding a new
162
+ * `locales/<lang>/pages/foo.json` is picked up automatically on the next
163
+ * Vite build.
137
164
  */
138
165
  export function generateI18nFile(selectedCodes: string[]): string {
139
166
  const selectedLangs = SUPPORTED_LANGUAGES.filter(l => selectedCodes.includes(l.code));
@@ -141,16 +168,21 @@ export function generateI18nFile(selectedCodes: string[]): string {
141
168
  throw new Error('generateI18nFile: must include at least one language');
142
169
  }
143
170
 
144
- const importLines = selectedLangs
145
- .map(l => `import ${toJsIdent(l.code)} from './locales/${l.jsonFile}.json';`)
171
+ const fallback = selectedLangs[0].code;
172
+
173
+ const bundleLines = selectedLangs
174
+ .map(
175
+ lang =>
176
+ `const ${toJsIdent(lang.code)} = bundleLang(\n` +
177
+ ` import.meta.glob('./locales/${lang.jsonFile}/**/*.json', { eager: true, import: 'default' })\n` +
178
+ `);`
179
+ )
146
180
  .join('\n');
147
181
 
148
182
  const resourceEntries = selectedLangs
149
183
  .map(l => ` '${l.code}': { translation: ${toJsIdent(l.code)} },`)
150
184
  .join('\n');
151
185
 
152
- const fallback = selectedLangs[0].code;
153
-
154
186
  return `// ─────────────────────────────────────────────────────────────────────────────
155
187
  // i18n.ts — i18next configuration (generated by xertica-ui CLI)
156
188
  //
@@ -158,14 +190,43 @@ export function generateI18nFile(selectedCodes: string[]): string {
158
190
  // component is rendered. The side-effect initializes i18next synchronously so
159
191
  // that \`useTranslation()\` is ready on first render.
160
192
  //
193
+ // Locale layout (split for maintainability — merged at boot into a single
194
+ // \`translation\` namespace so all existing \`t('home.welcome')\`-style calls
195
+ // keep working without changes):
196
+ //
197
+ // locales/<lang>/{common,nav,errors,languageSelector,themeToggle}.json
198
+ // locales/<lang>/pages/{home,templates,login,resetPassword,verifyEmail}.json
199
+ // locales/<lang>/components/{assistant,sidebar,media,projectCard,
200
+ // profileCard,notificationCard,activityCard,
201
+ // stats,team}.json
202
+ //
161
203
  // To add or remove a language, run: npx xertica-ui update → Languages
162
- // The CLI will regenerate this file and copy/remove locale JSONs accordingly.
204
+ // The CLI will regenerate this file and copy/remove locale folders accordingly.
163
205
  // ─────────────────────────────────────────────────────────────────────────────
164
206
 
165
207
  import i18n from 'i18next';
166
208
  import { initReactI18next } from 'react-i18next';
167
209
 
168
- ${importLines}
210
+ /**
211
+ * Merge a Vite glob result into a single { key: contents } bundle. The key is
212
+ * derived from the file basename — folders (pages/components) are discarded
213
+ * because the legacy flat JSON layout used a single namespace.
214
+ */
215
+ function bundleLang(chunks: Record<string, unknown>): Record<string, unknown> {
216
+ const out: Record<string, unknown> = {};
217
+ for (const [filePath, value] of Object.entries(chunks)) {
218
+ const base = filePath.split('/').pop();
219
+ if (!base) continue;
220
+ const key = base.replace(/\\.json$/, '');
221
+ out[key] = value;
222
+ }
223
+ return out;
224
+ }
225
+
226
+ // One \`import.meta.glob\` call per language — Vite requires literal patterns,
227
+ // so we can't loop. \`eager: true\` inlines the JSON; \`import: 'default'\`
228
+ // returns the JSON value directly instead of \`{ default: ... }\`.
229
+ ${bundleLines}
169
230
 
170
231
  const STORAGE_KEY = 'xertica_language';
171
232
  const DEFAULT_FALLBACK = '${fallback}';
@@ -272,8 +333,7 @@ export default function App() {
272
333
  <QueryClientProvider client={queryClient}>
273
334
  <XerticaProvider
274
335
  apiKey={geminiApiKey}
275
- googleMapsApiKey={googleMapsApiKey}
276
- disableDarkMode${availableLanguagesProp}
336
+ googleMapsApiKey={googleMapsApiKey}${availableLanguagesProp}
277
337
  >
278
338
  <Router>
279
339
  {/* AuthProvider must be inside Router (needs useNavigate) */}