@lastbrain/app 2.0.18 → 2.0.23

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 +390 -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 +36 -23
  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 +485 -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,35 @@ 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
+ // Détecter si c'est un Server Component (entryPoint: "server")
266
+ const isServerComponent = page.entryPoint === "server";
267
+ // Les Server Components reçoivent params en props
268
+ // Les Client Components utilisent useParams() en interne
269
+ const componentProps = isServerComponent && hasDynamicParams
270
+ ? "params={params}"
271
+ : "";
272
+ // Si params existe mais n'est pas passé au composant, on doit le unwrap quand même
273
+ const awaitParams = hasDynamicParams && !isServerComponent
274
+ ? "await params; // Unwrap params for Next.js 15\n "
275
+ : "";
243
276
  content = `// GENERATED BY LASTBRAIN MODULE BUILD
244
277
  import { ${page.componentExport} } from "${importPath}";
245
278
  ${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}" : ""} />;
279
+ export default ${hasDynamicParams ? "async " : ""}function ${wrapperName}${propsSignature} {
280
+ ${awaitParams}return <${page.componentExport} ${componentProps} />;
248
281
  }
249
282
  `;
250
283
  }
@@ -345,7 +378,7 @@ function generateMenuConfig(moduleConfigs) {
345
378
  // Fonction pour préparer les menus avec les composants
346
379
  const prepareMenusForExport = (menus) => {
347
380
  return menus.map((menu) => {
348
- const { moduleName, __componentRef, ...cleanMenu } = menu;
381
+ const { moduleName: _moduleName, __componentRef, ...cleanMenu } = menu;
349
382
  if (__componentRef) {
350
383
  // Retourner une référence au composant au lieu de la sérialiser
351
384
  return `{ ...${JSON.stringify(cleanMenu)}, component: ${__componentRef} }`;
@@ -554,10 +587,12 @@ function buildGroupedApi(apis, routePath) {
554
587
  const exportsBySource = new Map();
555
588
  apis.forEach(({ moduleConfig, api }) => {
556
589
  const handler = `${moduleConfig.moduleName}/${api.entryPoint}`;
557
- if (!exportsBySource.has(handler)) {
558
- exportsBySource.set(handler, []);
590
+ const currentExports = exportsBySource.get(handler);
591
+ if (currentExports) {
592
+ currentExports.push(api.handlerExport);
593
+ return;
559
594
  }
560
- exportsBySource.get(handler).push(api.handlerExport);
595
+ exportsBySource.set(handler, [api.handlerExport]);
561
596
  });
562
597
  // Générer les exports - un export statement par source
563
598
  const exportStatements = [];
@@ -662,7 +697,7 @@ function cleanGeneratedFiles() {
662
697
  }
663
698
  };
664
699
  // Nettoyer les dossiers de sections
665
- const sectionsToClean = ["(public)", "auth", "admin", "api"];
700
+ const sectionsToClean = ["(public)", "auth", "admin", "api", "[lang]"];
666
701
  sectionsToClean.forEach((section) => {
667
702
  const sectionPath = path.join(appDirectory, section);
668
703
  if (fs.existsSync(sectionPath)) {
@@ -684,15 +719,55 @@ function cleanGeneratedFiles() {
684
719
  console.log("🧹 Cleanup completed");
685
720
  }
686
721
  }
722
+ /**
723
+ * Supprime les anciennes routes sans [lang] et les layouts générés
724
+ */
725
+ function cleanOldRoutesWithoutLang() {
726
+ const generatedComment = "// GENERATED BY LASTBRAIN MODULE BUILD";
727
+ // Supprimer les anciens dossiers de sections sans [lang]
728
+ const oldSections = ["(public)", "auth", "admin"];
729
+ oldSections.forEach((section) => {
730
+ const sectionPath = path.join(appDirectory, section);
731
+ if (fs.existsSync(sectionPath)) {
732
+ try {
733
+ // Supprimer récursivement tout le dossier
734
+ fs.rmSync(sectionPath, { recursive: true, force: true });
735
+ if (isDebugMode) {
736
+ console.log(`🗑️ Removed old section without [lang]: ${section}`);
737
+ }
738
+ }
739
+ catch (error) {
740
+ console.warn(`⚠️ Could not remove ${section}:`, error);
741
+ }
742
+ }
743
+ });
744
+ // Supprimer les layouts générés dans [lang]
745
+ const langLayoutsToRemove = [
746
+ path.join(appDirectory, "[lang]", "layout.tsx"),
747
+ path.join(appDirectory, "[lang]", "auth", "layout.tsx"),
748
+ path.join(appDirectory, "[lang]", "admin", "layout.tsx"),
749
+ ];
750
+ langLayoutsToRemove.forEach((layoutPath) => {
751
+ if (fs.existsSync(layoutPath)) {
752
+ try {
753
+ const content = fs.readFileSync(layoutPath, "utf-8");
754
+ // Supprimer si c'est un fichier généré
755
+ if (content.includes(generatedComment) ||
756
+ content.includes("AppProviders")) {
757
+ fs.unlinkSync(layoutPath);
758
+ if (isDebugMode) {
759
+ console.log(`🗑️ Removed generated layout: ${layoutPath}`);
760
+ }
761
+ }
762
+ }
763
+ catch (error) {
764
+ console.warn(`⚠️ Could not remove layout ${layoutPath}:`, error);
765
+ }
766
+ }
767
+ });
768
+ }
687
769
  function generateAppAside() {
688
770
  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
771
  const templateContent = `"use client";
697
772
 
698
773
  import { AppAside as UIAppAside } from "@lastbrain/app";
@@ -717,86 +792,236 @@ export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
717
792
  );
718
793
  }`;
719
794
  try {
720
- ensureDirectory(path.dirname(targetPath));
721
- fs.writeFileSync(targetPath, templateContent, "utf-8");
722
- if (isDebugMode) {
723
- console.log(`✅ Generated AppAside component: ${targetPath}`);
724
- }
795
+ writeScaffoldFile(targetPath, templateContent, "AppAside component");
725
796
  }
726
797
  catch (error) {
727
798
  console.error(`❌ Error generating AppAside component: ${error}`);
728
799
  }
729
800
  }
730
801
  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";
802
+ // Générer ClientLayout wrapper client
803
+ const clientLayoutPath = path.join(appDirectory, "components", "ClientLayout.tsx");
804
+ const clientLayoutContent = `"use client";
805
+
806
+ import { HeroUIProvider } from "@heroui/system";
807
+ import { ThemeProvider } from "next-themes";
808
+ import { AppProviders } from "./AppProviders";
809
+ import { AppHeader } from "./AppHeader";
810
+ import type { ReactNode } from "react";
811
+ import { useLocalizedRouter, type Language } from "@lastbrain/core";
812
+
813
+
814
+ export function ClientLayout({
815
+ children,
816
+ lang = "fr",
817
+ translations = {},
818
+ }: {
819
+ children: ReactNode;
820
+ lang?: Language;
821
+ translations?: Record<string, string>;
822
+ }) {
823
+ const router = useLocalizedRouter();
824
+
825
+ return (
826
+ <HeroUIProvider navigate={router.push}>
827
+ <ThemeProvider
828
+ attribute="class"
829
+ defaultTheme="dark"
830
+ enableSystem={false}
831
+ storageKey="lastbrain-theme"
832
+ >
833
+ <AppProviders lang={lang} translations={translations}>
834
+ <AppHeader />
835
+ <div className="min-h-screen text-foreground bg-background">
836
+ {children}
837
+ </div>
838
+ </AppProviders>
839
+ </ThemeProvider>
840
+ </HeroUIProvider>
841
+ );
842
+ }`;
843
+ try {
844
+ writeScaffoldFile(clientLayoutPath, clientLayoutContent, "ClientLayout component");
845
+ }
846
+ catch (error) {
847
+ console.error(`❌ Error generating ClientLayout component: ${error}`);
848
+ }
849
+ // Générer AppProviders wrapper client
850
+ const appProvidersPath = path.join(appDirectory, "components", "AppProviders.tsx");
851
+ const appProvidersContent = `"use client";
852
+
853
+ import { AppProviders as BaseAppProviders } from "@lastbrain/app";
854
+ import { realtimeConfig } from "../../config/realtime";
855
+ import type { ReactNode } from "react";
856
+ import type { Language } from "@lastbrain/core";
857
+
858
+ export function AppProviders({
859
+ children,
860
+ lang = "fr",
861
+ translations = {},
862
+ }: {
863
+ children: ReactNode;
864
+ lang?: Language;
865
+ translations?: Record<string, string>;
866
+ }) {
867
+ return (
868
+ <BaseAppProviders
869
+ realtimeConfig={realtimeConfig}
870
+ lang={lang}
871
+ translations={translations}
872
+ >
873
+ {children}
874
+ </BaseAppProviders>
875
+ );
876
+ }`;
877
+ try {
878
+ writeScaffoldFile(appProvidersPath, appProvidersContent, "AppProviders component");
879
+ }
880
+ catch (error) {
881
+ console.error(`❌ Error generating AppProviders component: ${error}`);
882
+ }
883
+ // Générer AppHeader component
884
+ const appHeaderPath = path.join(appDirectory, "components", "AppHeader.tsx");
885
+ const appHeaderContent = `"use client";
886
+
887
+ import { Header } from "@lastbrain/ui";
888
+ import { LanguageSwitcher } from "@lastbrain/app";
735
889
  import { menuConfig } from "../../config/menu";
736
890
 
737
- export default function SectionLayout({
891
+ export function AppHeader() {
892
+ return (
893
+ <Header
894
+ menuConfig={menuConfig}
895
+ languageSwitcher={<LanguageSwitcher variant="minimal" />}
896
+ />
897
+ );
898
+ }`;
899
+ try {
900
+ writeScaffoldFile(appHeaderPath, appHeaderContent, "AppHeader component");
901
+ }
902
+ catch (error) {
903
+ console.error(`❌ Error generating AppHeader component: ${error}`);
904
+ }
905
+ // Générer layout [lang] pour la gestion i18n
906
+ const langLayoutPath = path.join(appDirectory, "[lang]", "layout.tsx");
907
+ if (!fs.existsSync(langLayoutPath)) {
908
+ const langLayoutContent = `import { loadTranslations } from "@lastbrain/app/i18n/server-lang";
909
+ import { ClientLayout } from "../../components/ClientLayout";
910
+
911
+ export default async function LangLayout({
738
912
  children,
913
+ params,
739
914
  }: {
740
915
  children: React.ReactNode;
916
+ params: Promise<{ lang: string }>;
741
917
  }) {
918
+ const { lang } = await params;
919
+ const validLang = lang === "en" || lang === "fr" ? lang : "fr";
920
+
921
+ // Charger les traductions pour cette langue
922
+ const translations = await loadTranslations(validLang);
923
+
742
924
  return (
743
- <AuthLayoutWithSidebar menuConfig={menuConfig}>
744
- {children}
745
- </AuthLayoutWithSidebar>
925
+ <ClientLayout lang={validLang} translations={translations}>
926
+ <div className="min-h-screen pt-16">
927
+ {children}
928
+ </div>
929
+ </ClientLayout>
746
930
  );
747
931
  }`;
748
932
  try {
749
- ensureDirectory(path.dirname(authLayoutPath));
750
- fs.writeFileSync(authLayoutPath, authLayoutContent, "utf-8");
933
+ ensureDirectory(path.dirname(langLayoutPath));
934
+ fs.writeFileSync(langLayoutPath, langLayoutContent, "utf-8");
751
935
  if (isDebugMode) {
752
- console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
936
+ console.log(`✅ Generated [lang] layout: ${langLayoutPath}`);
753
937
  }
754
938
  }
755
939
  catch (error) {
756
- console.error(`❌ Error generating auth layout: ${error}`);
940
+ console.error(`❌ Error generating [lang] layout: ${error}`);
757
941
  }
758
942
  }
759
943
  else if (isDebugMode) {
760
- console.log(`⏭️ Auth layout already exists, skipping: ${authLayoutPath}`);
944
+ console.log(`⏭️ [lang] layout already exists, skipping: ${langLayoutPath}`);
945
+ }
946
+ // Générer layout auth avec sidebar
947
+ const authLayoutPath = path.join(appDirectory, "[lang]", "auth", "layout.tsx");
948
+ const authLayoutContent = `"use client";
949
+
950
+ import { AuthLayoutWithSidebar, langHref, useLanguage } from "@lastbrain/app";
951
+ import { menuConfig as fullMenuConfig } from "../../../config/menu";
952
+
953
+ export default function SectionLayout({
954
+ children,
955
+ }: {
956
+ children: React.ReactNode;
957
+ }) {
958
+ const { lang } = useLanguage();
959
+
960
+ // Transformer les paths du menu avec la langue
961
+ const menuConfig = {
962
+ ...fullMenuConfig,
963
+ auth: (fullMenuConfig.auth || []).map(item => ({
964
+ ...item,
965
+ path: item.path?.startsWith("/") && !item.path?.startsWith("/api/")
966
+ ? langHref(item.path, lang)
967
+ : item.path
968
+ })),
969
+ };
970
+
971
+ return (
972
+ <AuthLayoutWithSidebar menuConfig={menuConfig}>
973
+ {children}
974
+ </AuthLayoutWithSidebar>
975
+ );
976
+ }`;
977
+ try {
978
+ writeScaffoldFile(authLayoutPath, authLayoutContent, "auth layout with sidebar");
979
+ }
980
+ catch (error) {
981
+ console.error(`❌ Error generating auth layout: ${error}`);
761
982
  }
762
983
  // 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";
984
+ const adminLayoutPath = path.join(appDirectory, "[lang]", "admin", "layout.tsx");
985
+ const adminLayoutContent = `"use client";
986
+
987
+ import { AdminLayoutWithSidebar, langHref, useLanguage } from "@lastbrain/app";
988
+ import { menuConfig as fullMenuConfig } from "../../../config/menu";
767
989
 
768
990
  export default function AdminLayout({
769
991
  children,
770
992
  }: {
771
993
  children: React.ReactNode;
772
994
  }) {
995
+ const { lang } = useLanguage();
996
+
997
+ // Transformer les paths du menu avec la langue
998
+ const menuConfig = {
999
+ ...fullMenuConfig,
1000
+ admin: (fullMenuConfig.admin || []).map(item => ({
1001
+ ...item,
1002
+ path: item.path?.startsWith("/") && !item.path?.startsWith("/api/")
1003
+ ? langHref(item.path, lang)
1004
+ : item.path
1005
+ })),
1006
+ };
1007
+
773
1008
  return (
774
1009
  <AdminLayoutWithSidebar menuConfig={menuConfig}>
775
1010
  {children}
776
1011
  </AdminLayoutWithSidebar>
777
1012
  );
778
1013
  }`;
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
- }
1014
+ try {
1015
+ writeScaffoldFile(adminLayoutPath, adminLayoutContent, "admin layout with sidebar");
789
1016
  }
790
- else if (isDebugMode) {
791
- console.log(`⏭️ Admin layout already exists, skipping: ${adminLayoutPath}`);
1017
+ catch (error) {
1018
+ console.error(`❌ Error generating admin layout: ${error}`);
792
1019
  }
793
1020
  }
794
1021
  async function generateRealtimeConfig(moduleConfigs) {
795
1022
  try {
796
1023
  // Extraire les configurations realtime des modules
797
- const realtimeConfigs = moduleConfigs
798
- .filter((config) => config.realtime)
799
- .map((config) => config.realtime);
1024
+ const realtimeConfigs = moduleConfigs.flatMap((config) => (config.realtime ? [config.realtime] : []));
800
1025
  if (realtimeConfigs.length === 0) {
801
1026
  console.log("⏭️ No realtime configuration found in modules");
802
1027
  return;
@@ -846,8 +1071,7 @@ async function generateUserTabsConfig(moduleConfigs) {
846
1071
  try {
847
1072
  // Extraire les configurations user tabs des modules
848
1073
  const userTabsConfigs = moduleConfigs
849
- .filter((config) => config.userTabs && config.userTabs.length > 0)
850
- .flatMap((config) => config.userTabs.map((tab) => ({
1074
+ .flatMap((config) => (config.userTabs ?? []).map((tab) => ({
851
1075
  ...tab,
852
1076
  moduleName: config.moduleName,
853
1077
  })))
@@ -902,9 +1126,7 @@ async function generateUserTabsConfig(moduleConfigs) {
902
1126
  async function generateBucketsConfig(moduleConfigs) {
903
1127
  try {
904
1128
  // 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);
1129
+ const allBuckets = moduleConfigs.flatMap((config) => config.storage?.buckets ?? []);
908
1130
  if (allBuckets.length === 0) {
909
1131
  console.log("⏭️ No storage buckets configuration found in modules");
910
1132
  return;
@@ -1027,9 +1249,7 @@ export function isValidFileSize(bucketName: string, fileSize: number): boolean {
1027
1249
  async function generateStorageProxyApi(moduleConfigs) {
1028
1250
  try {
1029
1251
  // 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);
1252
+ const allBuckets = moduleConfigs.flatMap((config) => config.storage?.buckets ?? []);
1033
1253
  if (allBuckets.length === 0) {
1034
1254
  console.log("⏭️ No storage buckets found, skipping proxy API generation");
1035
1255
  return;
@@ -1159,9 +1379,7 @@ export async function GET(
1159
1379
  async function generateFooterConfig(moduleConfigs) {
1160
1380
  try {
1161
1381
  // 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);
1382
+ const allFooterLinks = moduleConfigs.flatMap((config) => config.footer ?? []);
1165
1383
  if (allFooterLinks.length === 0) {
1166
1384
  console.log("⏭️ No footer links found, skipping footer config generation");
1167
1385
  return;
@@ -1203,6 +1421,85 @@ export const footerConfig: FooterConfig = {
1203
1421
  console.error("❌ Error generating footer config:", error);
1204
1422
  }
1205
1423
  }
1424
+ /**
1425
+ * Génère les fichiers i18n en concaténant les traductions des modules
1426
+ */
1427
+ async function generateI18nFiles() {
1428
+ const appI18nDir = path.join(projectRoot, "i18n");
1429
+ const packagesDir = path.join(_monorepoRoot, "packages");
1430
+ const translations = {
1431
+ fr: {},
1432
+ en: {},
1433
+ };
1434
+ try {
1435
+ // Importer glob dynamiquement
1436
+ const { glob } = await import("glob");
1437
+ // Rechercher tous les fichiers i18n dans packages/**/src/i18n/*.json
1438
+ const pattern = path.join(packagesDir, "**/src/i18n/*.json");
1439
+ const files = await glob(pattern, {
1440
+ ignore: ["**/node_modules/**", "**/dist/**"],
1441
+ });
1442
+ if (isDebugMode) {
1443
+ console.log(`\n🔍 Found ${files.length} i18n files\n`);
1444
+ }
1445
+ for (const file of files) {
1446
+ const fileName = path.basename(file, ".json");
1447
+ const lang = fileName; // fr.json -> fr, en.json -> en
1448
+ if (!translations[lang]) {
1449
+ translations[lang] = {};
1450
+ }
1451
+ try {
1452
+ const content = JSON.parse(fs.readFileSync(file, "utf-8"));
1453
+ const moduleName = extractModuleNameFromPath(file);
1454
+ if (isDebugMode) {
1455
+ console.log(` ✓ ${moduleName} (${lang})`);
1456
+ }
1457
+ // Préfixer uniquement si la clé n'est pas déjà namespacée (avec ou sans suffixe -pro)
1458
+ const moduleNamespace = moduleName.replace(/-pro$/, "");
1459
+ for (const [key, value] of Object.entries(content)) {
1460
+ const keyRoot = key.split(".")[0];
1461
+ const isGenericNamespaced = keyRoot.startsWith("module-");
1462
+ const isAlreadyNamespaced = isGenericNamespaced ||
1463
+ key.startsWith(`${moduleName}.`) ||
1464
+ key.startsWith(`${moduleNamespace}.`);
1465
+ const normalizedKey = isAlreadyNamespaced
1466
+ ? key
1467
+ : `${moduleNamespace}.${key}`;
1468
+ translations[lang][normalizedKey] = value;
1469
+ }
1470
+ }
1471
+ catch (error) {
1472
+ console.warn(` ⚠️ Error reading ${file}:`, error);
1473
+ }
1474
+ }
1475
+ // Créer le dossier i18n s'il n'existe pas
1476
+ ensureDirectory(appI18nDir);
1477
+ // Écrire les fichiers de traduction
1478
+ for (const [lang, content] of Object.entries(translations)) {
1479
+ const filePath = path.join(appI18nDir, `${lang}.json`);
1480
+ fs.writeFileSync(filePath, JSON.stringify(content, null, 2), "utf-8");
1481
+ const keyCount = Object.keys(content).length;
1482
+ if (isDebugMode) {
1483
+ console.log(`✅ Generated ${filePath} (${keyCount} keys)`);
1484
+ }
1485
+ }
1486
+ }
1487
+ catch (error) {
1488
+ console.error("❌ Error generating i18n files:", error);
1489
+ }
1490
+ }
1491
+ /**
1492
+ * Extrait le nom du module depuis le chemin du fichier
1493
+ */
1494
+ function extractModuleNameFromPath(filePath) {
1495
+ // packages/module-auth/src/i18n/fr.json -> module-auth
1496
+ const parts = filePath.split(path.sep);
1497
+ const packagesIndex = parts.indexOf("packages");
1498
+ if (packagesIndex !== -1 && packagesIndex < parts.length - 1) {
1499
+ return parts[packagesIndex + 1];
1500
+ }
1501
+ return "unknown";
1502
+ }
1206
1503
  export async function runModuleBuild() {
1207
1504
  ensureDirectory(appDirectory);
1208
1505
  // Nettoyer les fichiers générés précédemment
@@ -1210,6 +1507,11 @@ export async function runModuleBuild() {
1210
1507
  console.log("🧹 Cleaning previously generated files...");
1211
1508
  }
1212
1509
  cleanGeneratedFiles();
1510
+ // Supprimer les anciennes routes sans [lang]
1511
+ if (isDebugMode) {
1512
+ console.log("🗑️ Removing old routes without [lang]...");
1513
+ }
1514
+ cleanOldRoutesWithoutLang();
1213
1515
  const moduleConfigs = await loadModuleConfigs();
1214
1516
  if (isDebugMode) {
1215
1517
  console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
@@ -1231,10 +1533,12 @@ export async function runModuleBuild() {
1231
1533
  const apisByPath = new Map();
1232
1534
  moduleConfigs.forEach((moduleConfig) => {
1233
1535
  moduleConfig.apis.forEach((api) => {
1234
- if (!apisByPath.has(api.path)) {
1235
- apisByPath.set(api.path, []);
1536
+ const apisForPath = apisByPath.get(api.path);
1537
+ if (apisForPath) {
1538
+ apisForPath.push({ moduleConfig, api });
1539
+ return;
1236
1540
  }
1237
- apisByPath.get(api.path).push({ moduleConfig, api });
1541
+ apisByPath.set(api.path, [{ moduleConfig, api }]);
1238
1542
  });
1239
1543
  });
1240
1544
  // Générer les fichiers de route groupés
@@ -1272,6 +1576,11 @@ export async function runModuleBuild() {
1272
1576
  console.log("🦶 Generating footer configuration...");
1273
1577
  }
1274
1578
  await generateFooterConfig(moduleConfigs);
1579
+ // Générer les fichiers i18n
1580
+ if (isDebugMode) {
1581
+ console.log("🌍 Building i18n files...");
1582
+ }
1583
+ await generateI18nFiles();
1275
1584
  // Message de succès final
1276
1585
  if (!isDebugMode) {
1277
1586
  console.log("\n✅ Module build completed successfully!");