@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
@@ -0,0 +1,48 @@
1
+ import { type Language, loadTranslations } from "./server-lang";
2
+
3
+ /**
4
+ * Helper pour créer une fonction de traduction côté serveur
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * export default async function MyPage({ params }) {
9
+ * const t = await createServerTranslator(params);
10
+ * return <h1>{t("module-auth.welcome")}</h1>;
11
+ * }
12
+ * ```
13
+ */
14
+ export async function createServerTranslator(
15
+ params: Promise<{ lang?: string }> | { lang?: string }
16
+ ): Promise<(key: string) => string> {
17
+ // Si params est une Promise, l'attendre
18
+ const resolvedParams = params instanceof Promise ? await params : params;
19
+ const lang = (resolvedParams.lang as Language) || "fr";
20
+ const translations = await loadTranslations(lang);
21
+
22
+ return (key: string): string => {
23
+ return translations[key] || key;
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Hook pour utiliser les traductions côté serveur avec namespace
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * export default async function MyPage({ params }) {
33
+ * const t = await createNamespacedTranslator(params, "module-auth");
34
+ * return <h1>{t("welcome")}</h1>; // Cherche "module-auth.welcome"
35
+ * }
36
+ * ```
37
+ */
38
+ export async function createNamespacedTranslator(
39
+ params: Promise<{ lang?: string }> | { lang?: string },
40
+ namespace: string
41
+ ): Promise<(key: string) => string> {
42
+ const t = await createServerTranslator(params);
43
+
44
+ return (key: string): string => {
45
+ const fullKey = `${namespace}.${key}`;
46
+ return t(fullKey);
47
+ };
48
+ }
@@ -0,0 +1,51 @@
1
+ import { cookies } from "next/headers";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ export type Language = "fr" | "en";
6
+
7
+ /**
8
+ * Récupère la langue depuis les cookies côté serveur
9
+ */
10
+ export async function getServerLanguage(): Promise<Language> {
11
+ const cookieStore = await cookies();
12
+ const lang = cookieStore.get("NEXT_LOCALE")?.value as Language | undefined;
13
+ return lang || "fr";
14
+ }
15
+
16
+ /**
17
+ * Charge les traductions pour une langue donnée côté serveur
18
+ */
19
+ export async function loadTranslations(
20
+ lang: Language
21
+ ): Promise<Record<string, string>> {
22
+ try {
23
+ // Charger depuis l'app final (source unique = concaténation de tous les modules)
24
+ // En développement et production, chercher dans apps/recipe/i18n (ou apps/lastbrain/i18n)
25
+ const appPaths = [
26
+ path.join(process.cwd(), "apps", "recipe", "i18n", `${lang}.json`),
27
+ path.join(process.cwd(), "apps", "lastbrain", "i18n", `${lang}.json`),
28
+ path.join(process.cwd(), "i18n", `${lang}.json`),
29
+ path.join("/app", "i18n", `${lang}.json`),
30
+ ];
31
+
32
+ let translationPath = "";
33
+ for (const appPath of appPaths) {
34
+ if (fs.existsSync(appPath)) {
35
+ translationPath = appPath;
36
+ break;
37
+ }
38
+ }
39
+
40
+ if (!fs.existsSync(translationPath)) {
41
+ return {};
42
+ }
43
+
44
+ const content = fs.readFileSync(translationPath, "utf-8");
45
+ const parsed = JSON.parse(content);
46
+
47
+ return parsed;
48
+ } catch (_error) {
49
+ return {};
50
+ }
51
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Server-only exports for i18n
3
+ *
4
+ * Import from "@lastbrain/app/i18n/server-lang" in Server Components
5
+ * Ne pas importer depuis "@lastbrain/app" pour éviter les erreurs côté client
6
+ */
7
+
8
+ export { getServerLanguage, loadTranslations } from "./server-lang";
9
+ export {
10
+ createServerTranslator,
11
+ createNamespacedTranslator,
12
+ } from "./server-helpers";
13
+ export type { Language } from "./LanguageProvider";
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Types pour le système i18n
3
+ */
4
+
5
+ export type Language = "fr" | "en";
6
+
7
+ export type TranslationKey = string;
8
+
9
+ export interface Translations {
10
+ [key: string]: string;
11
+ }
12
+
13
+ export interface I18nConfig {
14
+ defaultLocale: Language;
15
+ locales: Language[];
16
+ }
17
+
18
+ /**
19
+ * Type pour les paramètres de page avec langue
20
+ */
21
+ export interface LangParams {
22
+ params: Promise<{ lang: Language }>;
23
+ }
24
+
25
+ /**
26
+ * Type pour les paramètres de page avec langue et ID
27
+ */
28
+ export interface LangIdParams {
29
+ params: Promise<{ lang: Language; id: string }>;
30
+ }
31
+
32
+ /**
33
+ * Helper type pour extraire les clés de traduction d'un module
34
+ *
35
+ * @example
36
+ * type AuthTranslations = ModuleTranslations<"module-auth">;
37
+ * // "module-auth.welcome" | "module-auth.signin" | ...
38
+ */
39
+ export type ModuleTranslations<T extends string> = `${T}.${string}`;
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ import { useLocalizedRouter } from "@lastbrain/core";
4
+ import { useLink } from "./useLink";
5
+
6
+ /**
7
+ * Wrapper autour de router.push/replace avec injection automatique du préfixe [lang].
8
+ */
9
+ export function useLangRouter() {
10
+ const router = useLocalizedRouter();
11
+ const link = useLink();
12
+
13
+ return {
14
+ push: (href: string) => router.push(link(href)),
15
+ replace: (href: string) => router.replace(link(href)),
16
+ prefetch: (href: string) => router.prefetch(link(href)),
17
+ back: () => router.back(),
18
+ forward: () => router.forward(),
19
+ refresh: () => router.refresh(),
20
+ };
21
+ }
@@ -0,0 +1,60 @@
1
+ "use client";
2
+
3
+ import { useLanguage } from "./LanguageProvider";
4
+ import type { Language } from "./types";
5
+
6
+ /**
7
+ * Helper pour transformer un URL avec le paramètre [lang]
8
+ * Utile pour les transformations statiques avant le rendu React
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const href = langHref("/settings", "fr"); // "/fr/settings"
13
+ * const href = langHref("/api/users", "en"); // "/api/users" (API non modifiée)
14
+ * ```
15
+ */
16
+ export function langHref(href: string, lang: Language): string {
17
+ const normalizedHref = String(href);
18
+ const isAbsolute = normalizedHref.startsWith("http");
19
+ const hasLangPrefix = normalizedHref.startsWith(`/${lang}/`);
20
+
21
+ if (isAbsolute) {
22
+ return normalizedHref;
23
+ } else if (hasLangPrefix) {
24
+ return normalizedHref;
25
+ } else if (normalizedHref === "/") {
26
+ return `/${lang}`;
27
+ } else if (normalizedHref.startsWith("/api/")) {
28
+ return normalizedHref;
29
+ } else {
30
+ return `/${lang}${normalizedHref}`;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Hook pour générer des URLs avec le paramètre [lang] injecté automatiquement
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * "use client";
40
+ * import { useLink } from "@lastbrain/app";
41
+ *
42
+ * export function MyComponent() {
43
+ * const link = useLink();
44
+ *
45
+ * return (
46
+ * <>
47
+ * <img src={link("/api/storage/avatar.jpg")} />
48
+ * <a href={link("/recipes")}>Recettes</a>
49
+ * </>
50
+ * );
51
+ * }
52
+ * ```
53
+ */
54
+ export function useLink() {
55
+ const { lang } = useLanguage();
56
+
57
+ return (href: string): string => {
58
+ return langHref(href, lang);
59
+ };
60
+ }
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import { useLanguage } from "./LanguageProvider";
4
+
5
+ /**
6
+ * Hook pour utiliser les traductions d'un module spécifique
7
+ * Préfixe automatiquement les clés avec le nom du module
8
+ *
9
+ * @example
10
+ * const t = useModuleTranslation("auth");
11
+ * t("hello") // -> cherche "module-auth.hello"
12
+ * t("hello", "Hello") // -> cherche "module-auth.hello", fallback à "Hello"
13
+ */
14
+ export function useModuleTranslation(moduleName: string) {
15
+ const { t: globalT } = useLanguage();
16
+
17
+ return (key: string, defaultValue?: string): string => {
18
+ const prefixedKey = `module-${moduleName}.${key}`;
19
+ const translation = globalT(prefixedKey);
20
+
21
+ // Si la traduction retourne exactement la clé (non trouvée), utiliser le fallback
22
+ if (translation === prefixedKey && defaultValue !== undefined) {
23
+ return defaultValue;
24
+ }
25
+ return translation;
26
+ };
27
+ }
package/src/index.ts CHANGED
@@ -22,6 +22,26 @@ export { AdminLayout } from "./layouts/AdminLayout";
22
22
  export { AdminLayoutWithSidebar } from "./layouts/AdminLayoutWithSidebar";
23
23
  export { getModuleConfigs } from "./modules/module-loader";
24
24
 
25
+ // i18n
26
+ export {
27
+ LanguageProvider,
28
+ useLanguage,
29
+ type Language,
30
+ } from "./i18n/LanguageProvider";
31
+ export { useLink, langHref } from "./i18n/useLink";
32
+ export { useLangRouter } from "./i18n/useLangRouter";
33
+ export { useModuleTranslation } from "@lastbrain/core";
34
+ export { transformLangHrefs, transformLangHref } from "./i18n/langHrefHelper";
35
+ export { LanguageSwitcher } from "./components/LanguageSwitcher";
36
+ export type {
37
+ TranslationKey,
38
+ Translations,
39
+ I18nConfig,
40
+ LangParams,
41
+ LangIdParams,
42
+ ModuleTranslations,
43
+ } from "./i18n/types";
44
+
25
45
  // Components
26
46
  export { AppAside } from "@lastbrain/ui";
27
47
  export type { AppAsideMenuItem, AppAsideMenuConfig } from "@lastbrain/ui";
@@ -1,3 +1,3 @@
1
1
  export function AdminLayout({ children }: { children: React.ReactNode }) {
2
- return <div className="max-w-screen pt-12 px-2 md:px-5">{children}</div>;
2
+ return <div className="max-w-screen px-2 md:px-5">{children}</div>;
3
3
  }
@@ -2,10 +2,11 @@
2
2
 
3
3
  import { createContext, useContext, useMemo } from "react";
4
4
  import { getModuleConfigs } from "../modules/module-loader";
5
- import { ToastProvider } from "@lastbrain/ui";
5
+ import { ToastProvider, AppLinkProvider } from "@lastbrain/ui";
6
6
  import { RealtimeProvider } from "@lastbrain/core";
7
7
  import { useAuthSession } from "../auth/useAuthSession";
8
8
  import { useNotifications as useNotificationsHook } from "../hooks/useNotifications";
9
+ import { LanguageProvider, type Language } from "../i18n/LanguageProvider";
9
10
  import type { User } from "@supabase/supabase-js";
10
11
  import type { ModuleRealtimeConfig } from "@lastbrain/core";
11
12
  import type { NotificationsData } from "../hooks/useNotifications";
@@ -62,9 +63,13 @@ export function useAuth() {
62
63
  export function AppProviders({
63
64
  children,
64
65
  realtimeConfig = [],
66
+ lang = "fr",
67
+ translations = {},
65
68
  }: {
66
69
  children: React.ReactNode;
67
70
  realtimeConfig?: ModuleRealtimeConfig[];
71
+ lang?: Language;
72
+ translations?: Record<string, string>;
68
73
  }) {
69
74
  const modules = useMemo(() => getModuleConfigs(), []);
70
75
  const { user, loading: authLoading, isSuperAdmin } = useAuthSession();
@@ -75,22 +80,36 @@ export function AppProviders({
75
80
  const authValue = useMemo(
76
81
  () => ({ user, loading: authLoading, isSuperAdmin }),
77
82
  [user, authLoading, isSuperAdmin]
78
- ); // const handleLogout = async () => {
79
- // const { signOut } = await import("../auth/authHelpers.js");
80
- // await signOut();
81
- // router.push("/auth/signin");
82
- // };
83
+ );
84
+
85
+ // Mémoriser les traductions pour éviter les changements
86
+ const memoizedTranslations = useMemo(() => {
87
+ // Si des traductions non-vides sont fournies, les utiliser
88
+ if (Object.keys(translations).length > 0) {
89
+ return translations;
90
+ }
91
+ // Sinon, tenter de récupérer celles du script injecté côté client
92
+ if (typeof window !== "undefined" && (window as any).__TRANSLATIONS__) {
93
+ return (window as any).__TRANSLATIONS__ as Record<string, string>;
94
+ }
95
+ // Ne retourner un objet vide que si vraiment rien n'est disponible
96
+ return {};
97
+ }, [translations]);
83
98
 
84
99
  return (
85
- <AuthContext.Provider value={authValue}>
86
- <ModuleContext.Provider value={modules}>
87
- <NotificationContext.Provider value={notificationsData}>
88
- <RealtimeProvider userId={user?.id} config={realtimeConfig}>
89
- {children}
90
- <ToastProvider placement="bottom-right" toastOffset={5} />
91
- </RealtimeProvider>
92
- </NotificationContext.Provider>
93
- </ModuleContext.Provider>
94
- </AuthContext.Provider>
100
+ <LanguageProvider initialLang={lang} translations={memoizedTranslations}>
101
+ <AppLinkProvider lang={lang || "fr"}>
102
+ <AuthContext.Provider value={authValue}>
103
+ <ModuleContext.Provider value={modules}>
104
+ <NotificationContext.Provider value={notificationsData}>
105
+ <RealtimeProvider userId={user?.id} config={realtimeConfig}>
106
+ {children}
107
+ <ToastProvider placement="bottom-right" toastOffset={5} />
108
+ </RealtimeProvider>
109
+ </NotificationContext.Provider>
110
+ </ModuleContext.Provider>
111
+ </AuthContext.Provider>
112
+ </AppLinkProvider>
113
+ </LanguageProvider>
95
114
  );
96
115
  }
@@ -1,3 +1,3 @@
1
1
  export function AuthLayout({ children }: { children: React.ReactNode }) {
2
- return <div className="pt-18 px-2 md:px-5 max-w-screen">{children}</div>;
2
+ return <div className="pt-4 px-2 md:px-5 max-w-screen">{children}</div>;
3
3
  }
@@ -8,6 +8,7 @@ import {
8
8
  type MenuItem,
9
9
  } from "@lastbrain/ui";
10
10
  import { useAuthSession } from "../auth/useAuthSession";
11
+ import { usePathname } from "next/navigation";
11
12
  import type { MenuIgnored, MenuCustom } from "../types/menu";
12
13
  import { useEffect, useState } from "react";
13
14
 
@@ -29,6 +30,7 @@ export function AuthLayoutWithSidebar({
29
30
  menuCustom,
30
31
  }: AuthLayoutWithSidebarProps) {
31
32
  const { isSuperAdmin, loading, user } = useAuthSession();
33
+ const pathname = usePathname();
32
34
  const [isCollapsed, setIsCollapsed] = useState(() => {
33
35
  if (typeof window !== "undefined") {
34
36
  const savedState = localStorage.getItem("aside-collapsed");
@@ -77,8 +79,8 @@ export function AuthLayoutWithSidebar({
77
79
  }
78
80
  }, []);
79
81
 
80
- // Pour la section auth, isAdminSection sera false
81
- const isAdminSection = false;
82
+ // Détecter si on est dans la section auth pour le skeleton (avec support [lang])
83
+ const isAuthSection = pathname.includes("/auth");
82
84
 
83
85
  // Créer un menuConfig sûr avec des valeurs par défaut
84
86
  const safeMenuConfig = {
@@ -104,7 +106,7 @@ export function AuthLayoutWithSidebar({
104
106
  {shouldShowSkeleton ? (
105
107
  <AppAsideSkeleton
106
108
  className={className}
107
- isAdminSection={isAdminSection}
109
+ isAdminSection={isAuthSection}
108
110
  />
109
111
  ) : (
110
112
  <AppAside
@@ -3,18 +3,23 @@
3
3
  import { ThemeProvider } from "next-themes";
4
4
  import { AppProviders } from "./AppProviders";
5
5
  import type { ModuleRealtimeConfig } from "@lastbrain/core";
6
+ import type { Language } from "../i18n/LanguageProvider";
6
7
 
7
8
  // Note: L'app Next.js doit importer son propre globals.css dans son layout
8
9
  // Note: La configuration realtime doit être fournie par l'app qui utilise ce layout
9
10
  export function RootLayout({
10
11
  children,
11
12
  realtimeConfig = [],
13
+ lang = "fr",
14
+ translations = {},
12
15
  }: {
13
16
  children: React.ReactNode;
14
17
  realtimeConfig?: ModuleRealtimeConfig[];
18
+ lang?: Language;
19
+ translations?: Record<string, string>;
15
20
  }) {
16
21
  return (
17
- <html lang="fr" suppressHydrationWarning>
22
+ <html lang={lang} suppressHydrationWarning>
18
23
  <body className="min-h-screen max-w-screen">
19
24
  <ThemeProvider
20
25
  attribute="class"
@@ -22,10 +27,12 @@ export function RootLayout({
22
27
  enableSystem={false}
23
28
  storageKey="lastbrain-theme"
24
29
  >
25
- <AppProviders realtimeConfig={realtimeConfig}>
26
- <div className=" min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-white">
27
- {children}
28
- </div>
30
+ <AppProviders
31
+ realtimeConfig={realtimeConfig}
32
+ lang={lang}
33
+ translations={translations}
34
+ >
35
+ <div className=" min-h-screen pt-12">{children}</div>
29
36
  </AppProviders>
30
37
  </ThemeProvider>
31
38
  </body>
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Script pour collecter tous les fichiers i18n des modules et les concaténer
4
+ * dans le dossier i18n de l'application
5
+ */
6
+
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+
10
+ const projectRoot = process.env.PROJECT_ROOT || process.cwd();
11
+ const monorepoRoot = projectRoot.includes("/apps/")
12
+ ? path.resolve(projectRoot, "..", "..")
13
+ : projectRoot;
14
+
15
+ const packagesDir = path.join(monorepoRoot, "packages");
16
+ const appI18nDir = path.join(projectRoot, "i18n");
17
+
18
+ interface TranslationMap {
19
+ [key: string]: Record<string, string>;
20
+ }
21
+
22
+ /**
23
+ * Collecte tous les fichiers i18n des modules
24
+ */
25
+ async function collectTranslations(): Promise<TranslationMap> {
26
+ const translations: TranslationMap = {
27
+ fr: {},
28
+ en: {},
29
+ };
30
+
31
+ try {
32
+ // Importer glob dynamiquement
33
+ const { glob } = await import("glob");
34
+
35
+ // Rechercher tous les fichiers i18n dans packages/**/src/i18n/*.json
36
+ const pattern = path.join(packagesDir, "**/src/i18n/*.json");
37
+ const files = await glob(pattern, {
38
+ ignore: ["**/node_modules/**", "**/dist/**"],
39
+ });
40
+
41
+ console.log(`\n🔍 Found ${files.length} i18n files\n`);
42
+
43
+ for (const file of files) {
44
+ const fileName = path.basename(file, ".json");
45
+ const lang = fileName; // fr.json -> fr, en.json -> en
46
+
47
+ if (!translations[lang]) {
48
+ translations[lang] = {};
49
+ }
50
+
51
+ try {
52
+ const content = JSON.parse(fs.readFileSync(file, "utf-8"));
53
+ const moduleName = extractModuleName(file);
54
+
55
+ console.log(` ✓ ${moduleName} (${lang})`);
56
+
57
+ // Préfixer les clés avec le nom du module pour éviter les collisions
58
+ for (const [key, value] of Object.entries(content)) {
59
+ const prefixedKey = `${moduleName}.${key}`;
60
+ translations[lang][prefixedKey] = value as string;
61
+ }
62
+ } catch (error) {
63
+ console.warn(` ⚠️ Error reading ${file}:`, error);
64
+ }
65
+ }
66
+ } catch (error) {
67
+ console.error("❌ Error collecting translations:", error);
68
+ }
69
+
70
+ return translations;
71
+ }
72
+
73
+ /**
74
+ * Extrait le nom du module depuis le chemin du fichier
75
+ */
76
+ function extractModuleName(filePath: string): string {
77
+ // packages/module-auth/src/i18n/fr.json -> module-auth
78
+ const parts = filePath.split(path.sep);
79
+ const packagesIndex = parts.indexOf("packages");
80
+ if (packagesIndex !== -1 && packagesIndex < parts.length - 1) {
81
+ return parts[packagesIndex + 1];
82
+ }
83
+ return "unknown";
84
+ }
85
+
86
+ /**
87
+ * Écrit les traductions concaténées dans le dossier i18n de l'app
88
+ */
89
+ function writeTranslations(translations: TranslationMap) {
90
+ // Créer le dossier i18n s'il n'existe pas
91
+ if (!fs.existsSync(appI18nDir)) {
92
+ fs.mkdirSync(appI18nDir, { recursive: true });
93
+ }
94
+
95
+ for (const [lang, content] of Object.entries(translations)) {
96
+ const filePath = path.join(appI18nDir, `${lang}.json`);
97
+ fs.writeFileSync(filePath, JSON.stringify(content, null, 2), "utf-8");
98
+ const keyCount = Object.keys(content).length;
99
+ console.log(`\n✅ Generated ${filePath} (${keyCount} keys)`);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Point d'entrée principal
105
+ */
106
+ async function buildI18n() {
107
+ console.log("🌍 Building i18n files...\n");
108
+ console.log(`📁 Project root: ${projectRoot}`);
109
+ console.log(`📦 Packages dir: ${packagesDir}`);
110
+ console.log(`🎯 App i18n dir: ${appI18nDir}\n`);
111
+
112
+ const translations = await collectTranslations();
113
+ writeTranslations(translations);
114
+
115
+ console.log("\n✨ i18n build complete!\n");
116
+ }
117
+
118
+ // Exécuter
119
+ buildI18n().catch((error) => {
120
+ console.error("\n❌ Error:", error);
121
+ process.exit(1);
122
+ });