@lastbrain/app 2.0.15 → 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 (257) 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/PublicLayout.d.ts.map +1 -1
  51. package/dist/layouts/PublicLayout.js +1 -1
  52. package/dist/layouts/RootLayout.d.ts +4 -1
  53. package/dist/layouts/RootLayout.d.ts.map +1 -1
  54. package/dist/layouts/RootLayout.js +2 -2
  55. package/dist/scripts/i18n-build.d.ts +7 -0
  56. package/dist/scripts/i18n-build.d.ts.map +1 -0
  57. package/dist/scripts/i18n-build.js +100 -0
  58. package/dist/scripts/init-app.js +12 -12
  59. package/dist/scripts/module-build.d.ts.map +1 -1
  60. package/dist/scripts/module-build.js +390 -83
  61. package/dist/styles.css +1 -1
  62. package/dist/templates/DefaultDoc.d.ts.map +1 -1
  63. package/dist/templates/DefaultDoc.js +90 -2
  64. package/dist/templates/middleware-i18n.example.d.ts +16 -0
  65. package/dist/templates/middleware-i18n.example.d.ts.map +1 -0
  66. package/dist/templates/middleware-i18n.example.js +72 -0
  67. package/package.json +14 -3
  68. package/src/app-shell/not-found.tsx +2 -2
  69. package/src/components/LanguageSwitcher.tsx +156 -0
  70. package/src/config/version.ts +19 -21
  71. package/src/i18n/LanguageProvider.tsx +7 -0
  72. package/src/i18n/README_LANG_HELPERS.md +187 -0
  73. package/src/i18n/TranslationsScript.tsx +17 -0
  74. package/src/i18n/cookies.ts +24 -0
  75. package/src/i18n/langHrefHelper.ts +51 -0
  76. package/src/i18n/server-helpers.ts +48 -0
  77. package/src/i18n/server-lang.ts +51 -0
  78. package/src/i18n/server.ts +13 -0
  79. package/src/i18n/types.ts +39 -0
  80. package/src/i18n/useLangRouter.ts +21 -0
  81. package/src/i18n/useLink.ts +60 -0
  82. package/src/i18n/useModuleTranslation.ts +27 -0
  83. package/src/index.ts +20 -0
  84. package/src/layouts/AdminLayout.tsx +1 -1
  85. package/src/layouts/AppProviders.tsx +35 -16
  86. package/src/layouts/AuthLayout.tsx +1 -1
  87. package/src/layouts/AuthLayoutWithSidebar.tsx +5 -3
  88. package/src/layouts/PublicLayout.tsx +3 -1
  89. package/src/layouts/RootLayout.tsx +13 -6
  90. package/src/scripts/i18n-build.ts +122 -0
  91. package/src/scripts/init-app.ts +12 -12
  92. package/src/scripts/module-build.ts +483 -96
  93. package/src/templates/DefaultDoc.tsx +511 -1
  94. package/src/templates/middleware-i18n.example.ts +92 -0
  95. package/dist/app-shell/(admin)/dashboard/page.d.ts +0 -2
  96. package/dist/app-shell/(admin)/dashboard/page.d.ts.map +0 -1
  97. package/dist/app-shell/(admin)/dashboard/page.js +0 -5
  98. package/dist/app-shell/(admin)/page.d.ts +0 -2
  99. package/dist/app-shell/(admin)/page.d.ts.map +0 -1
  100. package/dist/app-shell/(admin)/page.js +0 -5
  101. package/dist/app-shell/(auth)/dashboard/page.d.ts +0 -2
  102. package/dist/app-shell/(auth)/dashboard/page.d.ts.map +0 -1
  103. package/dist/app-shell/(auth)/dashboard/page.js +0 -5
  104. package/dist/app-shell/(auth)/page.d.ts +0 -2
  105. package/dist/app-shell/(auth)/page.d.ts.map +0 -1
  106. package/dist/app-shell/(auth)/page.js +0 -5
  107. package/dist/components/TableStructure.d.ts +0 -8
  108. package/dist/components/TableStructure.d.ts.map +0 -1
  109. package/dist/components/TableStructure.js +0 -37
  110. package/dist/hooks/useNotificationsSimple.d.ts +0 -20
  111. package/dist/hooks/useNotificationsSimple.d.ts.map +0 -1
  112. package/dist/hooks/useNotificationsSimple.js +0 -37
  113. package/dist/module-build.d.ts +0 -2
  114. package/dist/module-build.d.ts.map +0 -1
  115. package/dist/module-build.js +0 -50
  116. package/dist/modules/index.d.ts +0 -3
  117. package/dist/modules/index.d.ts.map +0 -1
  118. package/dist/modules/index.js +0 -2
  119. package/dist/src/__tests__/module-registry.test.d.ts +0 -2
  120. package/dist/src/__tests__/module-registry.test.d.ts.map +0 -1
  121. package/dist/src/__tests__/module-registry.test.js +0 -53
  122. package/dist/src/app-shell/(admin)/layout.d.ts +0 -4
  123. package/dist/src/app-shell/(admin)/layout.d.ts.map +0 -1
  124. package/dist/src/app-shell/(admin)/layout.js +0 -5
  125. package/dist/src/app-shell/(auth)/layout.d.ts +0 -4
  126. package/dist/src/app-shell/(auth)/layout.d.ts.map +0 -1
  127. package/dist/src/app-shell/(auth)/layout.js +0 -5
  128. package/dist/src/app-shell/(public)/page.d.ts +0 -2
  129. package/dist/src/app-shell/(public)/page.d.ts.map +0 -1
  130. package/dist/src/app-shell/(public)/page.js +0 -5
  131. package/dist/src/app-shell/layout.d.ts +0 -3
  132. package/dist/src/app-shell/layout.d.ts.map +0 -1
  133. package/dist/src/app-shell/layout.js +0 -3
  134. package/dist/src/app-shell/not-found.d.ts +0 -2
  135. package/dist/src/app-shell/not-found.d.ts.map +0 -1
  136. package/dist/src/app-shell/not-found.js +0 -10
  137. package/dist/src/auth/authHelpers.d.ts +0 -7
  138. package/dist/src/auth/authHelpers.d.ts.map +0 -1
  139. package/dist/src/auth/authHelpers.js +0 -19
  140. package/dist/src/auth/useAuthSession.d.ts +0 -7
  141. package/dist/src/auth/useAuthSession.d.ts.map +0 -1
  142. package/dist/src/auth/useAuthSession.js +0 -49
  143. package/dist/src/cli.d.ts +0 -3
  144. package/dist/src/cli.d.ts.map +0 -1
  145. package/dist/src/cli.js +0 -143
  146. package/dist/src/components/NotificationContainer.d.ts +0 -2
  147. package/dist/src/components/NotificationContainer.d.ts.map +0 -1
  148. package/dist/src/components/NotificationContainer.js +0 -8
  149. package/dist/src/hooks/useNotifications.d.ts +0 -30
  150. package/dist/src/hooks/useNotifications.d.ts.map +0 -1
  151. package/dist/src/hooks/useNotifications.js +0 -165
  152. package/dist/src/index.d.ts +0 -22
  153. package/dist/src/index.d.ts.map +0 -1
  154. package/dist/src/index.js +0 -22
  155. package/dist/src/layouts/AdminLayout.d.ts +0 -4
  156. package/dist/src/layouts/AdminLayout.d.ts.map +0 -1
  157. package/dist/src/layouts/AdminLayout.js +0 -4
  158. package/dist/src/layouts/AdminLayoutWithSidebar.d.ts +0 -10
  159. package/dist/src/layouts/AdminLayoutWithSidebar.d.ts.map +0 -1
  160. package/dist/src/layouts/AdminLayoutWithSidebar.js +0 -62
  161. package/dist/src/layouts/AppProviders.d.ts +0 -27
  162. package/dist/src/layouts/AppProviders.d.ts.map +0 -1
  163. package/dist/src/layouts/AppProviders.js +0 -48
  164. package/dist/src/layouts/AuthLayout.d.ts +0 -4
  165. package/dist/src/layouts/AuthLayout.d.ts.map +0 -1
  166. package/dist/src/layouts/AuthLayout.js +0 -4
  167. package/dist/src/layouts/AuthLayoutWithSidebar.d.ts +0 -12
  168. package/dist/src/layouts/AuthLayoutWithSidebar.d.ts.map +0 -1
  169. package/dist/src/layouts/AuthLayoutWithSidebar.js +0 -60
  170. package/dist/src/layouts/PublicLayout.d.ts +0 -8
  171. package/dist/src/layouts/PublicLayout.d.ts.map +0 -1
  172. package/dist/src/layouts/PublicLayout.js +0 -6
  173. package/dist/src/layouts/PublicLayoutWithSidebar.d.ts +0 -9
  174. package/dist/src/layouts/PublicLayoutWithSidebar.d.ts.map +0 -1
  175. package/dist/src/layouts/PublicLayoutWithSidebar.js +0 -60
  176. package/dist/src/layouts/RootLayout.d.ts +0 -6
  177. package/dist/src/layouts/RootLayout.d.ts.map +0 -1
  178. package/dist/src/layouts/RootLayout.js +0 -9
  179. package/dist/src/modules/module-loader.d.ts +0 -5
  180. package/dist/src/modules/module-loader.d.ts.map +0 -1
  181. package/dist/src/modules/module-loader.js +0 -10
  182. package/dist/src/scripts/db-init.d.ts +0 -2
  183. package/dist/src/scripts/db-init.d.ts.map +0 -1
  184. package/dist/src/scripts/db-init.js +0 -300
  185. package/dist/src/scripts/db-migrations-sync.d.ts +0 -2
  186. package/dist/src/scripts/db-migrations-sync.d.ts.map +0 -1
  187. package/dist/src/scripts/db-migrations-sync.js +0 -84
  188. package/dist/src/scripts/dev-sync.d.ts +0 -2
  189. package/dist/src/scripts/dev-sync.d.ts.map +0 -1
  190. package/dist/src/scripts/dev-sync.js +0 -194
  191. package/dist/src/scripts/init-app.d.ts +0 -12
  192. package/dist/src/scripts/init-app.d.ts.map +0 -1
  193. package/dist/src/scripts/init-app.js +0 -2175
  194. package/dist/src/scripts/module-add.d.ts +0 -2
  195. package/dist/src/scripts/module-add.d.ts.map +0 -1
  196. package/dist/src/scripts/module-add.js +0 -232
  197. package/dist/src/scripts/module-build.d.ts +0 -2
  198. package/dist/src/scripts/module-build.d.ts.map +0 -1
  199. package/dist/src/scripts/module-build.js +0 -1280
  200. package/dist/src/scripts/module-create.d.ts +0 -28
  201. package/dist/src/scripts/module-create.d.ts.map +0 -1
  202. package/dist/src/scripts/module-create.js +0 -1429
  203. package/dist/src/scripts/module-delete.d.ts +0 -6
  204. package/dist/src/scripts/module-delete.d.ts.map +0 -1
  205. package/dist/src/scripts/module-delete.js +0 -147
  206. package/dist/src/scripts/module-list.d.ts +0 -2
  207. package/dist/src/scripts/module-list.d.ts.map +0 -1
  208. package/dist/src/scripts/module-list.js +0 -61
  209. package/dist/src/scripts/module-remove.d.ts +0 -2
  210. package/dist/src/scripts/module-remove.d.ts.map +0 -1
  211. package/dist/src/scripts/module-remove.js +0 -311
  212. package/dist/src/scripts/readme-build.d.ts +0 -2
  213. package/dist/src/scripts/readme-build.d.ts.map +0 -1
  214. package/dist/src/scripts/readme-build.js +0 -39
  215. package/dist/src/scripts/script-runner.d.ts +0 -5
  216. package/dist/src/scripts/script-runner.d.ts.map +0 -1
  217. package/dist/src/scripts/script-runner.js +0 -25
  218. package/dist/src/templates/AuthGuidePage.d.ts +0 -2
  219. package/dist/src/templates/AuthGuidePage.d.ts.map +0 -1
  220. package/dist/src/templates/AuthGuidePage.js +0 -9
  221. package/dist/src/templates/DefaultDoc.d.ts +0 -2
  222. package/dist/src/templates/DefaultDoc.d.ts.map +0 -1
  223. package/dist/src/templates/DefaultDoc.js +0 -240
  224. package/dist/src/templates/DocPage.d.ts +0 -17
  225. package/dist/src/templates/DocPage.d.ts.map +0 -1
  226. package/dist/src/templates/DocPage.js +0 -193
  227. package/dist/src/templates/DocsPageWithModules.d.ts +0 -2
  228. package/dist/src/templates/DocsPageWithModules.d.ts.map +0 -1
  229. package/dist/src/templates/DocsPageWithModules.js +0 -8
  230. package/dist/src/templates/MigrationsGuidePage.d.ts +0 -2
  231. package/dist/src/templates/MigrationsGuidePage.d.ts.map +0 -1
  232. package/dist/src/templates/MigrationsGuidePage.js +0 -11
  233. package/dist/src/templates/ModuleGuidePage.d.ts +0 -2
  234. package/dist/src/templates/ModuleGuidePage.d.ts.map +0 -1
  235. package/dist/src/templates/ModuleGuidePage.js +0 -14
  236. package/dist/src/templates/SimpleDocPage.d.ts +0 -2
  237. package/dist/src/templates/SimpleDocPage.d.ts.map +0 -1
  238. package/dist/src/templates/SimpleDocPage.js +0 -28
  239. package/dist/src/templates/SimpleHomePage.d.ts +0 -6
  240. package/dist/src/templates/SimpleHomePage.d.ts.map +0 -1
  241. package/dist/src/templates/SimpleHomePage.js +0 -7
  242. package/dist/src/types/menu.d.ts +0 -23
  243. package/dist/src/types/menu.d.ts.map +0 -1
  244. package/dist/src/types/menu.js +0 -1
  245. package/dist/templates/HomePage.d.ts +0 -6
  246. package/dist/templates/HomePage.d.ts.map +0 -1
  247. package/dist/templates/HomePage.js +0 -6
  248. package/dist/templates/components/AppAside.d.ts +0 -6
  249. package/dist/templates/components/AppAside.d.ts.map +0 -1
  250. package/dist/templates/components/AppAside.js +0 -9
  251. package/dist/templates/layouts/admin-layout.d.ts +0 -4
  252. package/dist/templates/layouts/admin-layout.d.ts.map +0 -1
  253. package/dist/templates/layouts/admin-layout.js +0 -6
  254. package/dist/templates/layouts/auth-layout.d.ts +0 -4
  255. package/dist/templates/layouts/auth-layout.d.ts.map +0 -1
  256. package/dist/templates/layouts/auth-layout.js +0 -6
  257. 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);
@@ -236,11 +245,33 @@ export default function ${wrapperName}${hasDynamicParams ? "(props: Record<strin
236
245
  `;
237
246
  }
238
247
  else {
248
+ // Déterminer le chemin d'import
249
+ const importPath = page.entryPoint
250
+ ? `${moduleConfig.moduleName}/${page.entryPoint}`
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
+ : "";
239
270
  content = `// GENERATED BY LASTBRAIN MODULE BUILD
240
- import { ${page.componentExport} } from "${moduleConfig.moduleName}";
241
-
242
- export default function ${wrapperName}${hasDynamicParams ? "(props: Record<string, unknown>)" : "()"} {
243
- return <${page.componentExport} ${hasDynamicParams ? "{...props}" : ""} />;
271
+ import { ${page.componentExport} } from "${importPath}";
272
+ ${page.metadataExport ? `\nexport { ${page.metadataExport} as generateMetadata } from "${importPath}";\n` : ""}
273
+ export default ${hasDynamicParams ? "async " : ""}function ${wrapperName}${propsSignature} {
274
+ ${awaitParams}return <${page.componentExport} />;
244
275
  }
245
276
  `;
246
277
  }
@@ -341,7 +372,7 @@ function generateMenuConfig(moduleConfigs) {
341
372
  // Fonction pour préparer les menus avec les composants
342
373
  const prepareMenusForExport = (menus) => {
343
374
  return menus.map((menu) => {
344
- const { moduleName, __componentRef, ...cleanMenu } = menu;
375
+ const { moduleName: _moduleName, __componentRef, ...cleanMenu } = menu;
345
376
  if (__componentRef) {
346
377
  // Retourner une référence au composant au lieu de la sérialiser
347
378
  return `{ ...${JSON.stringify(cleanMenu)}, component: ${__componentRef} }`;
@@ -550,10 +581,12 @@ function buildGroupedApi(apis, routePath) {
550
581
  const exportsBySource = new Map();
551
582
  apis.forEach(({ moduleConfig, api }) => {
552
583
  const handler = `${moduleConfig.moduleName}/${api.entryPoint}`;
553
- if (!exportsBySource.has(handler)) {
554
- exportsBySource.set(handler, []);
584
+ const currentExports = exportsBySource.get(handler);
585
+ if (currentExports) {
586
+ currentExports.push(api.handlerExport);
587
+ return;
555
588
  }
556
- exportsBySource.get(handler).push(api.handlerExport);
589
+ exportsBySource.set(handler, [api.handlerExport]);
557
590
  });
558
591
  // Générer les exports - un export statement par source
559
592
  const exportStatements = [];
@@ -658,7 +691,7 @@ function cleanGeneratedFiles() {
658
691
  }
659
692
  };
660
693
  // Nettoyer les dossiers de sections
661
- const sectionsToClean = ["(public)", "auth", "admin", "api"];
694
+ const sectionsToClean = ["(public)", "auth", "admin", "api", "[lang]"];
662
695
  sectionsToClean.forEach((section) => {
663
696
  const sectionPath = path.join(appDirectory, section);
664
697
  if (fs.existsSync(sectionPath)) {
@@ -680,15 +713,55 @@ function cleanGeneratedFiles() {
680
713
  console.log("🧹 Cleanup completed");
681
714
  }
682
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
+ }
683
763
  function generateAppAside() {
684
764
  const targetPath = path.join(appDirectory, "components", "AppAside.tsx");
685
- // Ne pas écraser si le fichier existe déjà
686
- if (fs.existsSync(targetPath)) {
687
- if (isDebugMode) {
688
- console.log(`⏭️ AppAside already exists, skipping: ${targetPath}`);
689
- }
690
- return;
691
- }
692
765
  const templateContent = `"use client";
693
766
 
694
767
  import { AppAside as UIAppAside } from "@lastbrain/app";
@@ -713,86 +786,236 @@ export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
713
786
  );
714
787
  }`;
715
788
  try {
716
- ensureDirectory(path.dirname(targetPath));
717
- fs.writeFileSync(targetPath, templateContent, "utf-8");
718
- if (isDebugMode) {
719
- console.log(`✅ Generated AppAside component: ${targetPath}`);
720
- }
789
+ writeScaffoldFile(targetPath, templateContent, "AppAside component");
721
790
  }
722
791
  catch (error) {
723
792
  console.error(`❌ Error generating AppAside component: ${error}`);
724
793
  }
725
794
  }
726
795
  function generateLayouts() {
727
- // Générer layout auth avec sidebar
728
- const authLayoutPath = path.join(appDirectory, "auth", "layout.tsx");
729
- if (!fs.existsSync(authLayoutPath)) {
730
- 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";
731
883
  import { menuConfig } from "../../config/menu";
732
884
 
733
- 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({
734
906
  children,
907
+ params,
735
908
  }: {
736
909
  children: React.ReactNode;
910
+ params: Promise<{ lang: string }>;
737
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
+
738
918
  return (
739
- <AuthLayoutWithSidebar menuConfig={menuConfig}>
740
- {children}
741
- </AuthLayoutWithSidebar>
919
+ <ClientLayout lang={validLang} translations={translations}>
920
+ <div className="min-h-screen pt-16">
921
+ {children}
922
+ </div>
923
+ </ClientLayout>
742
924
  );
743
925
  }`;
744
926
  try {
745
- ensureDirectory(path.dirname(authLayoutPath));
746
- fs.writeFileSync(authLayoutPath, authLayoutContent, "utf-8");
927
+ ensureDirectory(path.dirname(langLayoutPath));
928
+ fs.writeFileSync(langLayoutPath, langLayoutContent, "utf-8");
747
929
  if (isDebugMode) {
748
- console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
930
+ console.log(`✅ Generated [lang] layout: ${langLayoutPath}`);
749
931
  }
750
932
  }
751
933
  catch (error) {
752
- console.error(`❌ Error generating auth layout: ${error}`);
934
+ console.error(`❌ Error generating [lang] layout: ${error}`);
753
935
  }
754
936
  }
755
937
  else if (isDebugMode) {
756
- 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}`);
757
976
  }
758
977
  // Générer layout admin avec sidebar
759
- const adminLayoutPath = path.join(appDirectory, "admin", "layout.tsx");
760
- if (!fs.existsSync(adminLayoutPath)) {
761
- const adminLayoutContent = `import { AdminLayoutWithSidebar } from "@lastbrain/app";
762
- 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";
763
983
 
764
984
  export default function AdminLayout({
765
985
  children,
766
986
  }: {
767
987
  children: React.ReactNode;
768
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
+
769
1002
  return (
770
1003
  <AdminLayoutWithSidebar menuConfig={menuConfig}>
771
1004
  {children}
772
1005
  </AdminLayoutWithSidebar>
773
1006
  );
774
1007
  }`;
775
- try {
776
- ensureDirectory(path.dirname(adminLayoutPath));
777
- fs.writeFileSync(adminLayoutPath, adminLayoutContent, "utf-8");
778
- if (isDebugMode) {
779
- console.log(`✅ Generated admin layout with sidebar: ${adminLayoutPath}`);
780
- }
781
- }
782
- catch (error) {
783
- console.error(`❌ Error generating admin layout: ${error}`);
784
- }
1008
+ try {
1009
+ writeScaffoldFile(adminLayoutPath, adminLayoutContent, "admin layout with sidebar");
785
1010
  }
786
- else if (isDebugMode) {
787
- console.log(`⏭️ Admin layout already exists, skipping: ${adminLayoutPath}`);
1011
+ catch (error) {
1012
+ console.error(`❌ Error generating admin layout: ${error}`);
788
1013
  }
789
1014
  }
790
1015
  async function generateRealtimeConfig(moduleConfigs) {
791
1016
  try {
792
1017
  // Extraire les configurations realtime des modules
793
- const realtimeConfigs = moduleConfigs
794
- .filter((config) => config.realtime)
795
- .map((config) => config.realtime);
1018
+ const realtimeConfigs = moduleConfigs.flatMap((config) => (config.realtime ? [config.realtime] : []));
796
1019
  if (realtimeConfigs.length === 0) {
797
1020
  console.log("⏭️ No realtime configuration found in modules");
798
1021
  return;
@@ -842,8 +1065,7 @@ async function generateUserTabsConfig(moduleConfigs) {
842
1065
  try {
843
1066
  // Extraire les configurations user tabs des modules
844
1067
  const userTabsConfigs = moduleConfigs
845
- .filter((config) => config.userTabs && config.userTabs.length > 0)
846
- .flatMap((config) => config.userTabs.map((tab) => ({
1068
+ .flatMap((config) => (config.userTabs ?? []).map((tab) => ({
847
1069
  ...tab,
848
1070
  moduleName: config.moduleName,
849
1071
  })))
@@ -898,9 +1120,7 @@ async function generateUserTabsConfig(moduleConfigs) {
898
1120
  async function generateBucketsConfig(moduleConfigs) {
899
1121
  try {
900
1122
  // Extraire les configurations storage des modules
901
- const allBuckets = moduleConfigs
902
- .filter((config) => config.storage?.buckets && config.storage.buckets.length > 0)
903
- .flatMap((config) => config.storage.buckets);
1123
+ const allBuckets = moduleConfigs.flatMap((config) => config.storage?.buckets ?? []);
904
1124
  if (allBuckets.length === 0) {
905
1125
  console.log("⏭️ No storage buckets configuration found in modules");
906
1126
  return;
@@ -1023,9 +1243,7 @@ export function isValidFileSize(bucketName: string, fileSize: number): boolean {
1023
1243
  async function generateStorageProxyApi(moduleConfigs) {
1024
1244
  try {
1025
1245
  // Extraire les configurations storage des modules
1026
- const allBuckets = moduleConfigs
1027
- .filter((config) => config.storage?.buckets && config.storage.buckets.length > 0)
1028
- .flatMap((config) => config.storage.buckets);
1246
+ const allBuckets = moduleConfigs.flatMap((config) => config.storage?.buckets ?? []);
1029
1247
  if (allBuckets.length === 0) {
1030
1248
  console.log("⏭️ No storage buckets found, skipping proxy API generation");
1031
1249
  return;
@@ -1155,9 +1373,7 @@ export async function GET(
1155
1373
  async function generateFooterConfig(moduleConfigs) {
1156
1374
  try {
1157
1375
  // Extraire tous les liens footer des modules
1158
- const allFooterLinks = moduleConfigs
1159
- .filter((config) => config.footer && config.footer.length > 0)
1160
- .flatMap((config) => config.footer);
1376
+ const allFooterLinks = moduleConfigs.flatMap((config) => config.footer ?? []);
1161
1377
  if (allFooterLinks.length === 0) {
1162
1378
  console.log("⏭️ No footer links found, skipping footer config generation");
1163
1379
  return;
@@ -1199,6 +1415,85 @@ export const footerConfig: FooterConfig = {
1199
1415
  console.error("❌ Error generating footer config:", error);
1200
1416
  }
1201
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
+ }
1202
1497
  export async function runModuleBuild() {
1203
1498
  ensureDirectory(appDirectory);
1204
1499
  // Nettoyer les fichiers générés précédemment
@@ -1206,6 +1501,11 @@ export async function runModuleBuild() {
1206
1501
  console.log("🧹 Cleaning previously generated files...");
1207
1502
  }
1208
1503
  cleanGeneratedFiles();
1504
+ // Supprimer les anciennes routes sans [lang]
1505
+ if (isDebugMode) {
1506
+ console.log("🗑️ Removing old routes without [lang]...");
1507
+ }
1508
+ cleanOldRoutesWithoutLang();
1209
1509
  const moduleConfigs = await loadModuleConfigs();
1210
1510
  if (isDebugMode) {
1211
1511
  console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
@@ -1227,10 +1527,12 @@ export async function runModuleBuild() {
1227
1527
  const apisByPath = new Map();
1228
1528
  moduleConfigs.forEach((moduleConfig) => {
1229
1529
  moduleConfig.apis.forEach((api) => {
1230
- if (!apisByPath.has(api.path)) {
1231
- apisByPath.set(api.path, []);
1530
+ const apisForPath = apisByPath.get(api.path);
1531
+ if (apisForPath) {
1532
+ apisForPath.push({ moduleConfig, api });
1533
+ return;
1232
1534
  }
1233
- apisByPath.get(api.path).push({ moduleConfig, api });
1535
+ apisByPath.set(api.path, [{ moduleConfig, api }]);
1234
1536
  });
1235
1537
  });
1236
1538
  // Générer les fichiers de route groupés
@@ -1268,6 +1570,11 @@ export async function runModuleBuild() {
1268
1570
  console.log("🦶 Generating footer configuration...");
1269
1571
  }
1270
1572
  await generateFooterConfig(moduleConfigs);
1573
+ // Générer les fichiers i18n
1574
+ if (isDebugMode) {
1575
+ console.log("🌍 Building i18n files...");
1576
+ }
1577
+ await generateI18nFiles();
1271
1578
  // Message de succès final
1272
1579
  if (!isDebugMode) {
1273
1580
  console.log("\n✅ Module build completed successfully!");