@lastbrain/app 2.0.18 → 2.0.21

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 (254) hide show
  1. package/dist/app-shell/not-found.js +2 -2
  2. package/dist/components/LanguageSwitcher.d.ts +7 -0
  3. package/dist/components/LanguageSwitcher.d.ts.map +1 -0
  4. package/dist/components/LanguageSwitcher.js +62 -0
  5. package/dist/config/version.d.ts.map +1 -1
  6. package/dist/config/version.js +19 -21
  7. package/dist/i18n/LanguageProvider.d.ts +4 -0
  8. package/dist/i18n/LanguageProvider.d.ts.map +1 -0
  9. package/dist/i18n/LanguageProvider.js +6 -0
  10. package/dist/i18n/TranslationsScript.d.ts +8 -0
  11. package/dist/i18n/TranslationsScript.d.ts.map +1 -0
  12. package/dist/i18n/TranslationsScript.js +10 -0
  13. package/dist/i18n/cookies.d.ts +6 -0
  14. package/dist/i18n/cookies.d.ts.map +1 -0
  15. package/dist/i18n/cookies.js +24 -0
  16. package/dist/i18n/langHrefHelper.d.ts +36 -0
  17. package/dist/i18n/langHrefHelper.d.ts.map +1 -0
  18. package/dist/i18n/langHrefHelper.js +41 -0
  19. package/dist/i18n/server-helpers.d.ts +33 -0
  20. package/dist/i18n/server-helpers.d.ts.map +1 -0
  21. package/dist/i18n/server-helpers.js +39 -0
  22. package/dist/i18n/server-lang.d.ts +10 -0
  23. package/dist/i18n/server-lang.d.ts.map +1 -0
  24. package/dist/i18n/server-lang.js +42 -0
  25. package/dist/i18n/server.d.ts +10 -0
  26. package/dist/i18n/server.d.ts.map +1 -0
  27. package/dist/i18n/server.js +8 -0
  28. package/dist/i18n/types.d.ts +38 -0
  29. package/dist/i18n/types.d.ts.map +1 -0
  30. package/dist/i18n/types.js +4 -0
  31. package/dist/i18n/useLangRouter.d.ts +12 -0
  32. package/dist/i18n/useLangRouter.d.ts.map +1 -0
  33. package/dist/i18n/useLangRouter.js +18 -0
  34. package/dist/i18n/useLink.d.ts +34 -0
  35. package/dist/i18n/useLink.d.ts.map +1 -0
  36. package/dist/i18n/useLink.js +58 -0
  37. package/dist/i18n/useModuleTranslation.d.ts +11 -0
  38. package/dist/i18n/useModuleTranslation.d.ts.map +1 -0
  39. package/dist/i18n/useModuleTranslation.js +23 -0
  40. package/dist/index.d.ts +7 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +7 -0
  43. package/dist/layouts/AdminLayout.js +1 -1
  44. package/dist/layouts/AppProviders.d.ts +4 -1
  45. package/dist/layouts/AppProviders.d.ts.map +1 -1
  46. package/dist/layouts/AppProviders.js +18 -8
  47. package/dist/layouts/AuthLayout.js +1 -1
  48. package/dist/layouts/AuthLayoutWithSidebar.d.ts.map +1 -1
  49. package/dist/layouts/AuthLayoutWithSidebar.js +5 -3
  50. package/dist/layouts/RootLayout.d.ts +4 -1
  51. package/dist/layouts/RootLayout.d.ts.map +1 -1
  52. package/dist/layouts/RootLayout.js +2 -2
  53. package/dist/scripts/i18n-build.d.ts +7 -0
  54. package/dist/scripts/i18n-build.d.ts.map +1 -0
  55. package/dist/scripts/i18n-build.js +100 -0
  56. package/dist/scripts/init-app.js +12 -12
  57. package/dist/scripts/module-build.d.ts.map +1 -1
  58. package/dist/scripts/module-build.js +384 -81
  59. package/dist/styles.css +1 -1
  60. package/dist/templates/DefaultDoc.d.ts.map +1 -1
  61. package/dist/templates/DefaultDoc.js +90 -2
  62. package/dist/templates/middleware-i18n.example.d.ts +16 -0
  63. package/dist/templates/middleware-i18n.example.d.ts.map +1 -0
  64. package/dist/templates/middleware-i18n.example.js +72 -0
  65. package/package.json +14 -3
  66. package/src/app-shell/not-found.tsx +2 -2
  67. package/src/components/LanguageSwitcher.tsx +156 -0
  68. package/src/config/version.ts +19 -21
  69. package/src/i18n/LanguageProvider.tsx +7 -0
  70. package/src/i18n/README_LANG_HELPERS.md +187 -0
  71. package/src/i18n/TranslationsScript.tsx +17 -0
  72. package/src/i18n/cookies.ts +24 -0
  73. package/src/i18n/langHrefHelper.ts +51 -0
  74. package/src/i18n/server-helpers.ts +48 -0
  75. package/src/i18n/server-lang.ts +51 -0
  76. package/src/i18n/server.ts +13 -0
  77. package/src/i18n/types.ts +39 -0
  78. package/src/i18n/useLangRouter.ts +21 -0
  79. package/src/i18n/useLink.ts +60 -0
  80. package/src/i18n/useModuleTranslation.ts +27 -0
  81. package/src/index.ts +20 -0
  82. package/src/layouts/AdminLayout.tsx +1 -1
  83. package/src/layouts/AppProviders.tsx +35 -16
  84. package/src/layouts/AuthLayout.tsx +1 -1
  85. package/src/layouts/AuthLayoutWithSidebar.tsx +5 -3
  86. package/src/layouts/RootLayout.tsx +12 -5
  87. package/src/scripts/i18n-build.ts +122 -0
  88. package/src/scripts/init-app.ts +12 -12
  89. package/src/scripts/module-build.ts +476 -94
  90. package/src/templates/DefaultDoc.tsx +511 -1
  91. package/src/templates/middleware-i18n.example.ts +92 -0
  92. package/dist/app-shell/(admin)/dashboard/page.d.ts +0 -2
  93. package/dist/app-shell/(admin)/dashboard/page.d.ts.map +0 -1
  94. package/dist/app-shell/(admin)/dashboard/page.js +0 -5
  95. package/dist/app-shell/(admin)/page.d.ts +0 -2
  96. package/dist/app-shell/(admin)/page.d.ts.map +0 -1
  97. package/dist/app-shell/(admin)/page.js +0 -5
  98. package/dist/app-shell/(auth)/dashboard/page.d.ts +0 -2
  99. package/dist/app-shell/(auth)/dashboard/page.d.ts.map +0 -1
  100. package/dist/app-shell/(auth)/dashboard/page.js +0 -5
  101. package/dist/app-shell/(auth)/page.d.ts +0 -2
  102. package/dist/app-shell/(auth)/page.d.ts.map +0 -1
  103. package/dist/app-shell/(auth)/page.js +0 -5
  104. package/dist/components/TableStructure.d.ts +0 -8
  105. package/dist/components/TableStructure.d.ts.map +0 -1
  106. package/dist/components/TableStructure.js +0 -37
  107. package/dist/hooks/useNotificationsSimple.d.ts +0 -20
  108. package/dist/hooks/useNotificationsSimple.d.ts.map +0 -1
  109. package/dist/hooks/useNotificationsSimple.js +0 -37
  110. package/dist/module-build.d.ts +0 -2
  111. package/dist/module-build.d.ts.map +0 -1
  112. package/dist/module-build.js +0 -50
  113. package/dist/modules/index.d.ts +0 -3
  114. package/dist/modules/index.d.ts.map +0 -1
  115. package/dist/modules/index.js +0 -2
  116. package/dist/src/__tests__/module-registry.test.d.ts +0 -2
  117. package/dist/src/__tests__/module-registry.test.d.ts.map +0 -1
  118. package/dist/src/__tests__/module-registry.test.js +0 -53
  119. package/dist/src/app-shell/(admin)/layout.d.ts +0 -4
  120. package/dist/src/app-shell/(admin)/layout.d.ts.map +0 -1
  121. package/dist/src/app-shell/(admin)/layout.js +0 -5
  122. package/dist/src/app-shell/(auth)/layout.d.ts +0 -4
  123. package/dist/src/app-shell/(auth)/layout.d.ts.map +0 -1
  124. package/dist/src/app-shell/(auth)/layout.js +0 -5
  125. package/dist/src/app-shell/(public)/page.d.ts +0 -2
  126. package/dist/src/app-shell/(public)/page.d.ts.map +0 -1
  127. package/dist/src/app-shell/(public)/page.js +0 -5
  128. package/dist/src/app-shell/layout.d.ts +0 -3
  129. package/dist/src/app-shell/layout.d.ts.map +0 -1
  130. package/dist/src/app-shell/layout.js +0 -3
  131. package/dist/src/app-shell/not-found.d.ts +0 -2
  132. package/dist/src/app-shell/not-found.d.ts.map +0 -1
  133. package/dist/src/app-shell/not-found.js +0 -10
  134. package/dist/src/auth/authHelpers.d.ts +0 -7
  135. package/dist/src/auth/authHelpers.d.ts.map +0 -1
  136. package/dist/src/auth/authHelpers.js +0 -19
  137. package/dist/src/auth/useAuthSession.d.ts +0 -7
  138. package/dist/src/auth/useAuthSession.d.ts.map +0 -1
  139. package/dist/src/auth/useAuthSession.js +0 -49
  140. package/dist/src/cli.d.ts +0 -3
  141. package/dist/src/cli.d.ts.map +0 -1
  142. package/dist/src/cli.js +0 -143
  143. package/dist/src/components/NotificationContainer.d.ts +0 -2
  144. package/dist/src/components/NotificationContainer.d.ts.map +0 -1
  145. package/dist/src/components/NotificationContainer.js +0 -8
  146. package/dist/src/hooks/useNotifications.d.ts +0 -30
  147. package/dist/src/hooks/useNotifications.d.ts.map +0 -1
  148. package/dist/src/hooks/useNotifications.js +0 -165
  149. package/dist/src/index.d.ts +0 -22
  150. package/dist/src/index.d.ts.map +0 -1
  151. package/dist/src/index.js +0 -22
  152. package/dist/src/layouts/AdminLayout.d.ts +0 -4
  153. package/dist/src/layouts/AdminLayout.d.ts.map +0 -1
  154. package/dist/src/layouts/AdminLayout.js +0 -4
  155. package/dist/src/layouts/AdminLayoutWithSidebar.d.ts +0 -10
  156. package/dist/src/layouts/AdminLayoutWithSidebar.d.ts.map +0 -1
  157. package/dist/src/layouts/AdminLayoutWithSidebar.js +0 -62
  158. package/dist/src/layouts/AppProviders.d.ts +0 -27
  159. package/dist/src/layouts/AppProviders.d.ts.map +0 -1
  160. package/dist/src/layouts/AppProviders.js +0 -48
  161. package/dist/src/layouts/AuthLayout.d.ts +0 -4
  162. package/dist/src/layouts/AuthLayout.d.ts.map +0 -1
  163. package/dist/src/layouts/AuthLayout.js +0 -4
  164. package/dist/src/layouts/AuthLayoutWithSidebar.d.ts +0 -12
  165. package/dist/src/layouts/AuthLayoutWithSidebar.d.ts.map +0 -1
  166. package/dist/src/layouts/AuthLayoutWithSidebar.js +0 -60
  167. package/dist/src/layouts/PublicLayout.d.ts +0 -8
  168. package/dist/src/layouts/PublicLayout.d.ts.map +0 -1
  169. package/dist/src/layouts/PublicLayout.js +0 -6
  170. package/dist/src/layouts/PublicLayoutWithSidebar.d.ts +0 -9
  171. package/dist/src/layouts/PublicLayoutWithSidebar.d.ts.map +0 -1
  172. package/dist/src/layouts/PublicLayoutWithSidebar.js +0 -60
  173. package/dist/src/layouts/RootLayout.d.ts +0 -6
  174. package/dist/src/layouts/RootLayout.d.ts.map +0 -1
  175. package/dist/src/layouts/RootLayout.js +0 -9
  176. package/dist/src/modules/module-loader.d.ts +0 -5
  177. package/dist/src/modules/module-loader.d.ts.map +0 -1
  178. package/dist/src/modules/module-loader.js +0 -10
  179. package/dist/src/scripts/db-init.d.ts +0 -2
  180. package/dist/src/scripts/db-init.d.ts.map +0 -1
  181. package/dist/src/scripts/db-init.js +0 -300
  182. package/dist/src/scripts/db-migrations-sync.d.ts +0 -2
  183. package/dist/src/scripts/db-migrations-sync.d.ts.map +0 -1
  184. package/dist/src/scripts/db-migrations-sync.js +0 -84
  185. package/dist/src/scripts/dev-sync.d.ts +0 -2
  186. package/dist/src/scripts/dev-sync.d.ts.map +0 -1
  187. package/dist/src/scripts/dev-sync.js +0 -194
  188. package/dist/src/scripts/init-app.d.ts +0 -12
  189. package/dist/src/scripts/init-app.d.ts.map +0 -1
  190. package/dist/src/scripts/init-app.js +0 -2175
  191. package/dist/src/scripts/module-add.d.ts +0 -2
  192. package/dist/src/scripts/module-add.d.ts.map +0 -1
  193. package/dist/src/scripts/module-add.js +0 -232
  194. package/dist/src/scripts/module-build.d.ts +0 -2
  195. package/dist/src/scripts/module-build.d.ts.map +0 -1
  196. package/dist/src/scripts/module-build.js +0 -1280
  197. package/dist/src/scripts/module-create.d.ts +0 -28
  198. package/dist/src/scripts/module-create.d.ts.map +0 -1
  199. package/dist/src/scripts/module-create.js +0 -1429
  200. package/dist/src/scripts/module-delete.d.ts +0 -6
  201. package/dist/src/scripts/module-delete.d.ts.map +0 -1
  202. package/dist/src/scripts/module-delete.js +0 -147
  203. package/dist/src/scripts/module-list.d.ts +0 -2
  204. package/dist/src/scripts/module-list.d.ts.map +0 -1
  205. package/dist/src/scripts/module-list.js +0 -61
  206. package/dist/src/scripts/module-remove.d.ts +0 -2
  207. package/dist/src/scripts/module-remove.d.ts.map +0 -1
  208. package/dist/src/scripts/module-remove.js +0 -311
  209. package/dist/src/scripts/readme-build.d.ts +0 -2
  210. package/dist/src/scripts/readme-build.d.ts.map +0 -1
  211. package/dist/src/scripts/readme-build.js +0 -39
  212. package/dist/src/scripts/script-runner.d.ts +0 -5
  213. package/dist/src/scripts/script-runner.d.ts.map +0 -1
  214. package/dist/src/scripts/script-runner.js +0 -25
  215. package/dist/src/templates/AuthGuidePage.d.ts +0 -2
  216. package/dist/src/templates/AuthGuidePage.d.ts.map +0 -1
  217. package/dist/src/templates/AuthGuidePage.js +0 -9
  218. package/dist/src/templates/DefaultDoc.d.ts +0 -2
  219. package/dist/src/templates/DefaultDoc.d.ts.map +0 -1
  220. package/dist/src/templates/DefaultDoc.js +0 -240
  221. package/dist/src/templates/DocPage.d.ts +0 -17
  222. package/dist/src/templates/DocPage.d.ts.map +0 -1
  223. package/dist/src/templates/DocPage.js +0 -193
  224. package/dist/src/templates/DocsPageWithModules.d.ts +0 -2
  225. package/dist/src/templates/DocsPageWithModules.d.ts.map +0 -1
  226. package/dist/src/templates/DocsPageWithModules.js +0 -8
  227. package/dist/src/templates/MigrationsGuidePage.d.ts +0 -2
  228. package/dist/src/templates/MigrationsGuidePage.d.ts.map +0 -1
  229. package/dist/src/templates/MigrationsGuidePage.js +0 -11
  230. package/dist/src/templates/ModuleGuidePage.d.ts +0 -2
  231. package/dist/src/templates/ModuleGuidePage.d.ts.map +0 -1
  232. package/dist/src/templates/ModuleGuidePage.js +0 -14
  233. package/dist/src/templates/SimpleDocPage.d.ts +0 -2
  234. package/dist/src/templates/SimpleDocPage.d.ts.map +0 -1
  235. package/dist/src/templates/SimpleDocPage.js +0 -28
  236. package/dist/src/templates/SimpleHomePage.d.ts +0 -6
  237. package/dist/src/templates/SimpleHomePage.d.ts.map +0 -1
  238. package/dist/src/templates/SimpleHomePage.js +0 -7
  239. package/dist/src/types/menu.d.ts +0 -23
  240. package/dist/src/types/menu.d.ts.map +0 -1
  241. package/dist/src/types/menu.js +0 -1
  242. package/dist/templates/HomePage.d.ts +0 -6
  243. package/dist/templates/HomePage.d.ts.map +0 -1
  244. package/dist/templates/HomePage.js +0 -6
  245. package/dist/templates/components/AppAside.d.ts +0 -6
  246. package/dist/templates/components/AppAside.d.ts.map +0 -1
  247. package/dist/templates/components/AppAside.js +0 -9
  248. package/dist/templates/layouts/admin-layout.d.ts +0 -4
  249. package/dist/templates/layouts/admin-layout.d.ts.map +0 -1
  250. package/dist/templates/layouts/admin-layout.js +0 -6
  251. package/dist/templates/layouts/auth-layout.d.ts +0 -4
  252. package/dist/templates/layouts/auth-layout.d.ts.map +0 -1
  253. package/dist/templates/layouts/auth-layout.js +0 -6
  254. package/dist/templates/migrations/20201010100000_init.sql +0 -123
@@ -10,6 +10,19 @@ const _monorepoRoot = projectRoot.includes("/apps/")
10
10
  const appDirectory = path.join(projectRoot, "app");
11
11
  // Mode debug activé via --debug
12
12
  const isDebugMode = process.argv.includes("--debug");
13
+ // Helper pour écrire (générer ou mettre à jour) un fichier de scaffold
14
+ function ensureDirectory(dir) {
15
+ fs.mkdirSync(dir, { recursive: true });
16
+ }
17
+ function writeScaffoldFile(targetPath, content, label) {
18
+ const existed = fs.existsSync(targetPath);
19
+ ensureDirectory(path.dirname(targetPath));
20
+ fs.writeFileSync(targetPath, content, "utf-8");
21
+ if (isDebugMode) {
22
+ const status = existed ? "♻️ Updated" : "✅ Generated";
23
+ console.log(`${status} ${label}: ${targetPath}`);
24
+ }
25
+ }
13
26
  // Créer un require dans le contexte de l'application pour résoudre les modules installés dans l'app
14
27
  const projectRequire = createRequire(path.join(projectRoot, "package.json"));
15
28
  // Charger les modules depuis modules.json
@@ -70,10 +83,10 @@ async function loadModuleConfigs() {
70
83
  return moduleConfigs;
71
84
  }
72
85
  const sectionDirectoryMap = {
73
- public: ["(public)"],
74
- auth: ["auth"],
75
- admin: ["admin"],
76
- user: ["auth", "user"],
86
+ public: ["[lang]", "(public)"],
87
+ auth: ["[lang]", "auth"],
88
+ admin: ["[lang]", "admin"],
89
+ user: ["[lang]", "auth", "user"],
77
90
  };
78
91
  const sectionLayoutMap = {
79
92
  auth: "AuthLayout",
@@ -87,9 +100,6 @@ const navigation = {
87
100
  admin: [],
88
101
  };
89
102
  const userMenu = [];
90
- function ensureDirectory(dir) {
91
- fs.mkdirSync(dir, { recursive: true });
92
- }
93
103
  function ensureSectionLayout(sectionPath) {
94
104
  const sectionDir = path.join(appDirectory, ...sectionPath);
95
105
  const sectionKey = sectionPath[0];
@@ -101,16 +111,13 @@ function ensureSectionLayout(sectionPath) {
101
111
  const layoutName = sectionLayoutMap[sectionKey];
102
112
  if (layoutName) {
103
113
  const layoutPath = path.join(sectionDir, "layout.tsx");
104
- if (!fs.existsSync(layoutPath)) {
105
- const layoutContent = `import { ${layoutName} } from "@lastbrain/app";
114
+ const layoutContent = `import { ${layoutName} } from "@lastbrain/app";
115
+ import type React from "react";
106
116
 
107
117
  export default function SectionLayout({ children }: { children: React.ReactNode }) {
108
118
  return <${layoutName}>{children}</${layoutName}>;
109
- }
110
- `;
111
- fs.writeFileSync(layoutPath, layoutContent);
112
- console.log(`📐 Generated section layout: ${layoutPath}`);
113
- }
119
+ }`;
120
+ writeScaffoldFile(layoutPath, layoutContent, `${sectionKey} layout`);
114
121
  }
115
122
  generatedSections.add(sectionKey);
116
123
  }
@@ -165,7 +172,9 @@ function buildPage(moduleConfig, page) {
165
172
  : "Root";
166
173
  const wrapperName = `${page.componentExport}${wrapperSuffix}Route`;
167
174
  // Vérifier si la route a des paramètres dynamiques (segments avec [])
168
- const hasDynamicParams = segments.some((seg) => seg.startsWith("[") && seg.endsWith("]"));
175
+ // Inclure les segments du sectionPath (ex: [lang]) ET les segments du module
176
+ const allSegments = [...sectionPath, ...segments];
177
+ const hasDynamicParams = allSegments.some((seg) => seg.startsWith("[") && seg.endsWith("]"));
169
178
  // Pour les pages publiques (signin, signup, etc.), utiliser dynamic import sans SSR
170
179
  // pour éviter les erreurs d'hydratation avec les IDs HeroUI/React Aria
171
180
  const isPublicAuthPage = page.section === "public" &&
@@ -193,7 +202,7 @@ interface UserPageProps { params: Promise<{ id: string }> }
193
202
  async function getModuleUserTabs() {
194
203
  try {
195
204
  // Depuis /app/admin/auth/users/[id]/ vers /apps/test-01/config/user-tabs
196
- const { moduleUserTabs } = await import("../../../../../config/user-tabs");
205
+ const { moduleUserTabs } = await import("../../../../../../config/user-tabs");
197
206
  return moduleUserTabs || [];
198
207
  } catch (e) {
199
208
  console.warn("[user-detail-wrapper] erreur chargement user-tabs", e);
@@ -240,11 +249,29 @@ export default function ${wrapperName}${hasDynamicParams ? "(props: Record<strin
240
249
  const importPath = page.entryPoint
241
250
  ? `${moduleConfig.moduleName}/${page.entryPoint}`
242
251
  : moduleConfig.moduleName;
252
+ // Extraire les noms des paramètres dynamiques pour le typage
253
+ // Inclure les segments du sectionPath (ex: [lang]) ET les segments du module
254
+ const allSegments = [...sectionPath, ...segments];
255
+ const dynamicParamNames = allSegments
256
+ .filter((seg) => seg.startsWith("[") && seg.endsWith("]"))
257
+ .map((seg) => seg.slice(1, -1)); // Enlève les []
258
+ // Générer le type des params si nécessaire
259
+ const paramsType = dynamicParamNames.length > 0
260
+ ? `{ ${dynamicParamNames.map((name) => `${name}: string`).join("; ")} }`
261
+ : "";
262
+ const propsSignature = hasDynamicParams && paramsType
263
+ ? `({\n params,\n}: {\n params: Promise<${paramsType}>;\n})`
264
+ : "()";
265
+ // Les composants utilisent useParams() en interne, on ne passe jamais params en props
266
+ // On fait juste l'unwrap pour Next.js 15 si des params existent
267
+ const awaitParams = hasDynamicParams
268
+ ? "await params; // Unwrap params for Next.js 15\n "
269
+ : "";
243
270
  content = `// GENERATED BY LASTBRAIN MODULE BUILD
244
271
  import { ${page.componentExport} } from "${importPath}";
245
272
  ${page.metadataExport ? `\nexport { ${page.metadataExport} as generateMetadata } from "${importPath}";\n` : ""}
246
- export default function ${wrapperName}${hasDynamicParams ? "(props: Record<string, unknown>)" : "()"} {
247
- return <${page.componentExport} ${hasDynamicParams ? "{...props}" : ""} />;
273
+ export default ${hasDynamicParams ? "async " : ""}function ${wrapperName}${propsSignature} {
274
+ ${awaitParams}return <${page.componentExport} />;
248
275
  }
249
276
  `;
250
277
  }
@@ -345,7 +372,7 @@ function generateMenuConfig(moduleConfigs) {
345
372
  // Fonction pour préparer les menus avec les composants
346
373
  const prepareMenusForExport = (menus) => {
347
374
  return menus.map((menu) => {
348
- const { moduleName, __componentRef, ...cleanMenu } = menu;
375
+ const { moduleName: _moduleName, __componentRef, ...cleanMenu } = menu;
349
376
  if (__componentRef) {
350
377
  // Retourner une référence au composant au lieu de la sérialiser
351
378
  return `{ ...${JSON.stringify(cleanMenu)}, component: ${__componentRef} }`;
@@ -554,10 +581,12 @@ function buildGroupedApi(apis, routePath) {
554
581
  const exportsBySource = new Map();
555
582
  apis.forEach(({ moduleConfig, api }) => {
556
583
  const handler = `${moduleConfig.moduleName}/${api.entryPoint}`;
557
- if (!exportsBySource.has(handler)) {
558
- exportsBySource.set(handler, []);
584
+ const currentExports = exportsBySource.get(handler);
585
+ if (currentExports) {
586
+ currentExports.push(api.handlerExport);
587
+ return;
559
588
  }
560
- exportsBySource.get(handler).push(api.handlerExport);
589
+ exportsBySource.set(handler, [api.handlerExport]);
561
590
  });
562
591
  // Générer les exports - un export statement par source
563
592
  const exportStatements = [];
@@ -662,7 +691,7 @@ function cleanGeneratedFiles() {
662
691
  }
663
692
  };
664
693
  // Nettoyer les dossiers de sections
665
- const sectionsToClean = ["(public)", "auth", "admin", "api"];
694
+ const sectionsToClean = ["(public)", "auth", "admin", "api", "[lang]"];
666
695
  sectionsToClean.forEach((section) => {
667
696
  const sectionPath = path.join(appDirectory, section);
668
697
  if (fs.existsSync(sectionPath)) {
@@ -684,15 +713,55 @@ function cleanGeneratedFiles() {
684
713
  console.log("🧹 Cleanup completed");
685
714
  }
686
715
  }
716
+ /**
717
+ * Supprime les anciennes routes sans [lang] et les layouts générés
718
+ */
719
+ function cleanOldRoutesWithoutLang() {
720
+ const generatedComment = "// GENERATED BY LASTBRAIN MODULE BUILD";
721
+ // Supprimer les anciens dossiers de sections sans [lang]
722
+ const oldSections = ["(public)", "auth", "admin"];
723
+ oldSections.forEach((section) => {
724
+ const sectionPath = path.join(appDirectory, section);
725
+ if (fs.existsSync(sectionPath)) {
726
+ try {
727
+ // Supprimer récursivement tout le dossier
728
+ fs.rmSync(sectionPath, { recursive: true, force: true });
729
+ if (isDebugMode) {
730
+ console.log(`🗑️ Removed old section without [lang]: ${section}`);
731
+ }
732
+ }
733
+ catch (error) {
734
+ console.warn(`⚠️ Could not remove ${section}:`, error);
735
+ }
736
+ }
737
+ });
738
+ // Supprimer les layouts générés dans [lang]
739
+ const langLayoutsToRemove = [
740
+ path.join(appDirectory, "[lang]", "layout.tsx"),
741
+ path.join(appDirectory, "[lang]", "auth", "layout.tsx"),
742
+ path.join(appDirectory, "[lang]", "admin", "layout.tsx"),
743
+ ];
744
+ langLayoutsToRemove.forEach((layoutPath) => {
745
+ if (fs.existsSync(layoutPath)) {
746
+ try {
747
+ const content = fs.readFileSync(layoutPath, "utf-8");
748
+ // Supprimer si c'est un fichier généré
749
+ if (content.includes(generatedComment) ||
750
+ content.includes("AppProviders")) {
751
+ fs.unlinkSync(layoutPath);
752
+ if (isDebugMode) {
753
+ console.log(`🗑️ Removed generated layout: ${layoutPath}`);
754
+ }
755
+ }
756
+ }
757
+ catch (error) {
758
+ console.warn(`⚠️ Could not remove layout ${layoutPath}:`, error);
759
+ }
760
+ }
761
+ });
762
+ }
687
763
  function generateAppAside() {
688
764
  const targetPath = path.join(appDirectory, "components", "AppAside.tsx");
689
- // Ne pas écraser si le fichier existe déjà
690
- if (fs.existsSync(targetPath)) {
691
- if (isDebugMode) {
692
- console.log(`⏭️ AppAside already exists, skipping: ${targetPath}`);
693
- }
694
- return;
695
- }
696
765
  const templateContent = `"use client";
697
766
 
698
767
  import { AppAside as UIAppAside } from "@lastbrain/app";
@@ -717,86 +786,236 @@ export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
717
786
  );
718
787
  }`;
719
788
  try {
720
- ensureDirectory(path.dirname(targetPath));
721
- fs.writeFileSync(targetPath, templateContent, "utf-8");
722
- if (isDebugMode) {
723
- console.log(`✅ Generated AppAside component: ${targetPath}`);
724
- }
789
+ writeScaffoldFile(targetPath, templateContent, "AppAside component");
725
790
  }
726
791
  catch (error) {
727
792
  console.error(`❌ Error generating AppAside component: ${error}`);
728
793
  }
729
794
  }
730
795
  function generateLayouts() {
731
- // Générer layout auth avec sidebar
732
- const authLayoutPath = path.join(appDirectory, "auth", "layout.tsx");
733
- if (!fs.existsSync(authLayoutPath)) {
734
- const authLayoutContent = `import { AuthLayoutWithSidebar } from "@lastbrain/app";
796
+ // Générer ClientLayout wrapper client
797
+ const clientLayoutPath = path.join(appDirectory, "components", "ClientLayout.tsx");
798
+ const clientLayoutContent = `"use client";
799
+
800
+ import { HeroUIProvider } from "@heroui/system";
801
+ import { ThemeProvider } from "next-themes";
802
+ import { AppProviders } from "./AppProviders";
803
+ import { AppHeader } from "./AppHeader";
804
+ import type { ReactNode } from "react";
805
+ import { useLocalizedRouter, type Language } from "@lastbrain/core";
806
+
807
+
808
+ export function ClientLayout({
809
+ children,
810
+ lang = "fr",
811
+ translations = {},
812
+ }: {
813
+ children: ReactNode;
814
+ lang?: Language;
815
+ translations?: Record<string, string>;
816
+ }) {
817
+ const router = useLocalizedRouter();
818
+
819
+ return (
820
+ <HeroUIProvider navigate={router.push}>
821
+ <ThemeProvider
822
+ attribute="class"
823
+ defaultTheme="dark"
824
+ enableSystem={false}
825
+ storageKey="lastbrain-theme"
826
+ >
827
+ <AppProviders lang={lang} translations={translations}>
828
+ <AppHeader />
829
+ <div className="min-h-screen text-foreground bg-background">
830
+ {children}
831
+ </div>
832
+ </AppProviders>
833
+ </ThemeProvider>
834
+ </HeroUIProvider>
835
+ );
836
+ }`;
837
+ try {
838
+ writeScaffoldFile(clientLayoutPath, clientLayoutContent, "ClientLayout component");
839
+ }
840
+ catch (error) {
841
+ console.error(`❌ Error generating ClientLayout component: ${error}`);
842
+ }
843
+ // Générer AppProviders wrapper client
844
+ const appProvidersPath = path.join(appDirectory, "components", "AppProviders.tsx");
845
+ const appProvidersContent = `"use client";
846
+
847
+ import { AppProviders as BaseAppProviders } from "@lastbrain/app";
848
+ import { realtimeConfig } from "../../config/realtime";
849
+ import type { ReactNode } from "react";
850
+ import type { Language } from "@lastbrain/core";
851
+
852
+ export function AppProviders({
853
+ children,
854
+ lang = "fr",
855
+ translations = {},
856
+ }: {
857
+ children: ReactNode;
858
+ lang?: Language;
859
+ translations?: Record<string, string>;
860
+ }) {
861
+ return (
862
+ <BaseAppProviders
863
+ realtimeConfig={realtimeConfig}
864
+ lang={lang}
865
+ translations={translations}
866
+ >
867
+ {children}
868
+ </BaseAppProviders>
869
+ );
870
+ }`;
871
+ try {
872
+ writeScaffoldFile(appProvidersPath, appProvidersContent, "AppProviders component");
873
+ }
874
+ catch (error) {
875
+ console.error(`❌ Error generating AppProviders component: ${error}`);
876
+ }
877
+ // Générer AppHeader component
878
+ const appHeaderPath = path.join(appDirectory, "components", "AppHeader.tsx");
879
+ const appHeaderContent = `"use client";
880
+
881
+ import { Header } from "@lastbrain/ui";
882
+ import { LanguageSwitcher } from "@lastbrain/app";
735
883
  import { menuConfig } from "../../config/menu";
736
884
 
737
- export default function SectionLayout({
885
+ export function AppHeader() {
886
+ return (
887
+ <Header
888
+ menuConfig={menuConfig}
889
+ languageSwitcher={<LanguageSwitcher variant="minimal" />}
890
+ />
891
+ );
892
+ }`;
893
+ try {
894
+ writeScaffoldFile(appHeaderPath, appHeaderContent, "AppHeader component");
895
+ }
896
+ catch (error) {
897
+ console.error(`❌ Error generating AppHeader component: ${error}`);
898
+ }
899
+ // Générer layout [lang] pour la gestion i18n
900
+ const langLayoutPath = path.join(appDirectory, "[lang]", "layout.tsx");
901
+ if (!fs.existsSync(langLayoutPath)) {
902
+ const langLayoutContent = `import { loadTranslations } from "@lastbrain/app/i18n/server-lang";
903
+ import { ClientLayout } from "../../components/ClientLayout";
904
+
905
+ export default async function LangLayout({
738
906
  children,
907
+ params,
739
908
  }: {
740
909
  children: React.ReactNode;
910
+ params: Promise<{ lang: string }>;
741
911
  }) {
912
+ const { lang } = await params;
913
+ const validLang = lang === "en" || lang === "fr" ? lang : "fr";
914
+
915
+ // Charger les traductions pour cette langue
916
+ const translations = await loadTranslations(validLang);
917
+
742
918
  return (
743
- <AuthLayoutWithSidebar menuConfig={menuConfig}>
744
- {children}
745
- </AuthLayoutWithSidebar>
919
+ <ClientLayout lang={validLang} translations={translations}>
920
+ <div className="min-h-screen pt-16">
921
+ {children}
922
+ </div>
923
+ </ClientLayout>
746
924
  );
747
925
  }`;
748
926
  try {
749
- ensureDirectory(path.dirname(authLayoutPath));
750
- fs.writeFileSync(authLayoutPath, authLayoutContent, "utf-8");
927
+ ensureDirectory(path.dirname(langLayoutPath));
928
+ fs.writeFileSync(langLayoutPath, langLayoutContent, "utf-8");
751
929
  if (isDebugMode) {
752
- console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
930
+ console.log(`✅ Generated [lang] layout: ${langLayoutPath}`);
753
931
  }
754
932
  }
755
933
  catch (error) {
756
- console.error(`❌ Error generating auth layout: ${error}`);
934
+ console.error(`❌ Error generating [lang] layout: ${error}`);
757
935
  }
758
936
  }
759
937
  else if (isDebugMode) {
760
- console.log(`⏭️ Auth layout already exists, skipping: ${authLayoutPath}`);
938
+ console.log(`⏭️ [lang] layout already exists, skipping: ${langLayoutPath}`);
939
+ }
940
+ // Générer layout auth avec sidebar
941
+ const authLayoutPath = path.join(appDirectory, "[lang]", "auth", "layout.tsx");
942
+ const authLayoutContent = `"use client";
943
+
944
+ import { AuthLayoutWithSidebar, langHref, useLanguage } from "@lastbrain/app";
945
+ import { menuConfig as fullMenuConfig } from "../../../config/menu";
946
+
947
+ export default function SectionLayout({
948
+ children,
949
+ }: {
950
+ children: React.ReactNode;
951
+ }) {
952
+ const { lang } = useLanguage();
953
+
954
+ // Transformer les paths du menu avec la langue
955
+ const menuConfig = {
956
+ ...fullMenuConfig,
957
+ auth: (fullMenuConfig.auth || []).map(item => ({
958
+ ...item,
959
+ path: item.path?.startsWith("/") && !item.path?.startsWith("/api/")
960
+ ? langHref(item.path, lang)
961
+ : item.path
962
+ })),
963
+ };
964
+
965
+ return (
966
+ <AuthLayoutWithSidebar menuConfig={menuConfig}>
967
+ {children}
968
+ </AuthLayoutWithSidebar>
969
+ );
970
+ }`;
971
+ try {
972
+ writeScaffoldFile(authLayoutPath, authLayoutContent, "auth layout with sidebar");
973
+ }
974
+ catch (error) {
975
+ console.error(`❌ Error generating auth layout: ${error}`);
761
976
  }
762
977
  // Générer layout admin avec sidebar
763
- const adminLayoutPath = path.join(appDirectory, "admin", "layout.tsx");
764
- if (!fs.existsSync(adminLayoutPath)) {
765
- const adminLayoutContent = `import { AdminLayoutWithSidebar } from "@lastbrain/app";
766
- import { menuConfig } from "../../config/menu";
978
+ const adminLayoutPath = path.join(appDirectory, "[lang]", "admin", "layout.tsx");
979
+ const adminLayoutContent = `"use client";
980
+
981
+ import { AdminLayoutWithSidebar, langHref, useLanguage } from "@lastbrain/app";
982
+ import { menuConfig as fullMenuConfig } from "../../../config/menu";
767
983
 
768
984
  export default function AdminLayout({
769
985
  children,
770
986
  }: {
771
987
  children: React.ReactNode;
772
988
  }) {
989
+ const { lang } = useLanguage();
990
+
991
+ // Transformer les paths du menu avec la langue
992
+ const menuConfig = {
993
+ ...fullMenuConfig,
994
+ admin: (fullMenuConfig.admin || []).map(item => ({
995
+ ...item,
996
+ path: item.path?.startsWith("/") && !item.path?.startsWith("/api/")
997
+ ? langHref(item.path, lang)
998
+ : item.path
999
+ })),
1000
+ };
1001
+
773
1002
  return (
774
1003
  <AdminLayoutWithSidebar menuConfig={menuConfig}>
775
1004
  {children}
776
1005
  </AdminLayoutWithSidebar>
777
1006
  );
778
1007
  }`;
779
- try {
780
- ensureDirectory(path.dirname(adminLayoutPath));
781
- fs.writeFileSync(adminLayoutPath, adminLayoutContent, "utf-8");
782
- if (isDebugMode) {
783
- console.log(`✅ Generated admin layout with sidebar: ${adminLayoutPath}`);
784
- }
785
- }
786
- catch (error) {
787
- console.error(`❌ Error generating admin layout: ${error}`);
788
- }
1008
+ try {
1009
+ writeScaffoldFile(adminLayoutPath, adminLayoutContent, "admin layout with sidebar");
789
1010
  }
790
- else if (isDebugMode) {
791
- console.log(`⏭️ Admin layout already exists, skipping: ${adminLayoutPath}`);
1011
+ catch (error) {
1012
+ console.error(`❌ Error generating admin layout: ${error}`);
792
1013
  }
793
1014
  }
794
1015
  async function generateRealtimeConfig(moduleConfigs) {
795
1016
  try {
796
1017
  // Extraire les configurations realtime des modules
797
- const realtimeConfigs = moduleConfigs
798
- .filter((config) => config.realtime)
799
- .map((config) => config.realtime);
1018
+ const realtimeConfigs = moduleConfigs.flatMap((config) => (config.realtime ? [config.realtime] : []));
800
1019
  if (realtimeConfigs.length === 0) {
801
1020
  console.log("⏭️ No realtime configuration found in modules");
802
1021
  return;
@@ -846,8 +1065,7 @@ async function generateUserTabsConfig(moduleConfigs) {
846
1065
  try {
847
1066
  // Extraire les configurations user tabs des modules
848
1067
  const userTabsConfigs = moduleConfigs
849
- .filter((config) => config.userTabs && config.userTabs.length > 0)
850
- .flatMap((config) => config.userTabs.map((tab) => ({
1068
+ .flatMap((config) => (config.userTabs ?? []).map((tab) => ({
851
1069
  ...tab,
852
1070
  moduleName: config.moduleName,
853
1071
  })))
@@ -902,9 +1120,7 @@ async function generateUserTabsConfig(moduleConfigs) {
902
1120
  async function generateBucketsConfig(moduleConfigs) {
903
1121
  try {
904
1122
  // Extraire les configurations storage des modules
905
- const allBuckets = moduleConfigs
906
- .filter((config) => config.storage?.buckets && config.storage.buckets.length > 0)
907
- .flatMap((config) => config.storage.buckets);
1123
+ const allBuckets = moduleConfigs.flatMap((config) => config.storage?.buckets ?? []);
908
1124
  if (allBuckets.length === 0) {
909
1125
  console.log("⏭️ No storage buckets configuration found in modules");
910
1126
  return;
@@ -1027,9 +1243,7 @@ export function isValidFileSize(bucketName: string, fileSize: number): boolean {
1027
1243
  async function generateStorageProxyApi(moduleConfigs) {
1028
1244
  try {
1029
1245
  // Extraire les configurations storage des modules
1030
- const allBuckets = moduleConfigs
1031
- .filter((config) => config.storage?.buckets && config.storage.buckets.length > 0)
1032
- .flatMap((config) => config.storage.buckets);
1246
+ const allBuckets = moduleConfigs.flatMap((config) => config.storage?.buckets ?? []);
1033
1247
  if (allBuckets.length === 0) {
1034
1248
  console.log("⏭️ No storage buckets found, skipping proxy API generation");
1035
1249
  return;
@@ -1159,9 +1373,7 @@ export async function GET(
1159
1373
  async function generateFooterConfig(moduleConfigs) {
1160
1374
  try {
1161
1375
  // Extraire tous les liens footer des modules
1162
- const allFooterLinks = moduleConfigs
1163
- .filter((config) => config.footer && config.footer.length > 0)
1164
- .flatMap((config) => config.footer);
1376
+ const allFooterLinks = moduleConfigs.flatMap((config) => config.footer ?? []);
1165
1377
  if (allFooterLinks.length === 0) {
1166
1378
  console.log("⏭️ No footer links found, skipping footer config generation");
1167
1379
  return;
@@ -1203,6 +1415,85 @@ export const footerConfig: FooterConfig = {
1203
1415
  console.error("❌ Error generating footer config:", error);
1204
1416
  }
1205
1417
  }
1418
+ /**
1419
+ * Génère les fichiers i18n en concaténant les traductions des modules
1420
+ */
1421
+ async function generateI18nFiles() {
1422
+ const appI18nDir = path.join(projectRoot, "i18n");
1423
+ const packagesDir = path.join(_monorepoRoot, "packages");
1424
+ const translations = {
1425
+ fr: {},
1426
+ en: {},
1427
+ };
1428
+ try {
1429
+ // Importer glob dynamiquement
1430
+ const { glob } = await import("glob");
1431
+ // Rechercher tous les fichiers i18n dans packages/**/src/i18n/*.json
1432
+ const pattern = path.join(packagesDir, "**/src/i18n/*.json");
1433
+ const files = await glob(pattern, {
1434
+ ignore: ["**/node_modules/**", "**/dist/**"],
1435
+ });
1436
+ if (isDebugMode) {
1437
+ console.log(`\n🔍 Found ${files.length} i18n files\n`);
1438
+ }
1439
+ for (const file of files) {
1440
+ const fileName = path.basename(file, ".json");
1441
+ const lang = fileName; // fr.json -> fr, en.json -> en
1442
+ if (!translations[lang]) {
1443
+ translations[lang] = {};
1444
+ }
1445
+ try {
1446
+ const content = JSON.parse(fs.readFileSync(file, "utf-8"));
1447
+ const moduleName = extractModuleNameFromPath(file);
1448
+ if (isDebugMode) {
1449
+ console.log(` ✓ ${moduleName} (${lang})`);
1450
+ }
1451
+ // Préfixer uniquement si la clé n'est pas déjà namespacée (avec ou sans suffixe -pro)
1452
+ const moduleNamespace = moduleName.replace(/-pro$/, "");
1453
+ for (const [key, value] of Object.entries(content)) {
1454
+ const keyRoot = key.split(".")[0];
1455
+ const isGenericNamespaced = keyRoot.startsWith("module-");
1456
+ const isAlreadyNamespaced = isGenericNamespaced ||
1457
+ key.startsWith(`${moduleName}.`) ||
1458
+ key.startsWith(`${moduleNamespace}.`);
1459
+ const normalizedKey = isAlreadyNamespaced
1460
+ ? key
1461
+ : `${moduleNamespace}.${key}`;
1462
+ translations[lang][normalizedKey] = value;
1463
+ }
1464
+ }
1465
+ catch (error) {
1466
+ console.warn(` ⚠️ Error reading ${file}:`, error);
1467
+ }
1468
+ }
1469
+ // Créer le dossier i18n s'il n'existe pas
1470
+ ensureDirectory(appI18nDir);
1471
+ // Écrire les fichiers de traduction
1472
+ for (const [lang, content] of Object.entries(translations)) {
1473
+ const filePath = path.join(appI18nDir, `${lang}.json`);
1474
+ fs.writeFileSync(filePath, JSON.stringify(content, null, 2), "utf-8");
1475
+ const keyCount = Object.keys(content).length;
1476
+ if (isDebugMode) {
1477
+ console.log(`✅ Generated ${filePath} (${keyCount} keys)`);
1478
+ }
1479
+ }
1480
+ }
1481
+ catch (error) {
1482
+ console.error("❌ Error generating i18n files:", error);
1483
+ }
1484
+ }
1485
+ /**
1486
+ * Extrait le nom du module depuis le chemin du fichier
1487
+ */
1488
+ function extractModuleNameFromPath(filePath) {
1489
+ // packages/module-auth/src/i18n/fr.json -> module-auth
1490
+ const parts = filePath.split(path.sep);
1491
+ const packagesIndex = parts.indexOf("packages");
1492
+ if (packagesIndex !== -1 && packagesIndex < parts.length - 1) {
1493
+ return parts[packagesIndex + 1];
1494
+ }
1495
+ return "unknown";
1496
+ }
1206
1497
  export async function runModuleBuild() {
1207
1498
  ensureDirectory(appDirectory);
1208
1499
  // Nettoyer les fichiers générés précédemment
@@ -1210,6 +1501,11 @@ export async function runModuleBuild() {
1210
1501
  console.log("🧹 Cleaning previously generated files...");
1211
1502
  }
1212
1503
  cleanGeneratedFiles();
1504
+ // Supprimer les anciennes routes sans [lang]
1505
+ if (isDebugMode) {
1506
+ console.log("🗑️ Removing old routes without [lang]...");
1507
+ }
1508
+ cleanOldRoutesWithoutLang();
1213
1509
  const moduleConfigs = await loadModuleConfigs();
1214
1510
  if (isDebugMode) {
1215
1511
  console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
@@ -1231,10 +1527,12 @@ export async function runModuleBuild() {
1231
1527
  const apisByPath = new Map();
1232
1528
  moduleConfigs.forEach((moduleConfig) => {
1233
1529
  moduleConfig.apis.forEach((api) => {
1234
- if (!apisByPath.has(api.path)) {
1235
- apisByPath.set(api.path, []);
1530
+ const apisForPath = apisByPath.get(api.path);
1531
+ if (apisForPath) {
1532
+ apisForPath.push({ moduleConfig, api });
1533
+ return;
1236
1534
  }
1237
- apisByPath.get(api.path).push({ moduleConfig, api });
1535
+ apisByPath.set(api.path, [{ moduleConfig, api }]);
1238
1536
  });
1239
1537
  });
1240
1538
  // Générer les fichiers de route groupés
@@ -1272,6 +1570,11 @@ export async function runModuleBuild() {
1272
1570
  console.log("🦶 Generating footer configuration...");
1273
1571
  }
1274
1572
  await generateFooterConfig(moduleConfigs);
1573
+ // Générer les fichiers i18n
1574
+ if (isDebugMode) {
1575
+ console.log("🌍 Building i18n files...");
1576
+ }
1577
+ await generateI18nFiles();
1275
1578
  // Message de succès final
1276
1579
  if (!isDebugMode) {
1277
1580
  console.log("\n✅ Module build completed successfully!");