@lastbrain/app 2.0.18 → 2.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/dist/app-shell/not-found.js +2 -2
  2. package/dist/components/LanguageSwitcher.d.ts +7 -0
  3. package/dist/components/LanguageSwitcher.d.ts.map +1 -0
  4. package/dist/components/LanguageSwitcher.js +62 -0
  5. package/dist/config/version.d.ts.map +1 -1
  6. package/dist/config/version.js +19 -21
  7. package/dist/i18n/LanguageProvider.d.ts +4 -0
  8. package/dist/i18n/LanguageProvider.d.ts.map +1 -0
  9. package/dist/i18n/LanguageProvider.js +6 -0
  10. package/dist/i18n/TranslationsScript.d.ts +8 -0
  11. package/dist/i18n/TranslationsScript.d.ts.map +1 -0
  12. package/dist/i18n/TranslationsScript.js +10 -0
  13. package/dist/i18n/cookies.d.ts +6 -0
  14. package/dist/i18n/cookies.d.ts.map +1 -0
  15. package/dist/i18n/cookies.js +24 -0
  16. package/dist/i18n/langHrefHelper.d.ts +36 -0
  17. package/dist/i18n/langHrefHelper.d.ts.map +1 -0
  18. package/dist/i18n/langHrefHelper.js +41 -0
  19. package/dist/i18n/server-helpers.d.ts +33 -0
  20. package/dist/i18n/server-helpers.d.ts.map +1 -0
  21. package/dist/i18n/server-helpers.js +39 -0
  22. package/dist/i18n/server-lang.d.ts +10 -0
  23. package/dist/i18n/server-lang.d.ts.map +1 -0
  24. package/dist/i18n/server-lang.js +42 -0
  25. package/dist/i18n/server.d.ts +10 -0
  26. package/dist/i18n/server.d.ts.map +1 -0
  27. package/dist/i18n/server.js +8 -0
  28. package/dist/i18n/types.d.ts +38 -0
  29. package/dist/i18n/types.d.ts.map +1 -0
  30. package/dist/i18n/types.js +4 -0
  31. package/dist/i18n/useLangRouter.d.ts +12 -0
  32. package/dist/i18n/useLangRouter.d.ts.map +1 -0
  33. package/dist/i18n/useLangRouter.js +18 -0
  34. package/dist/i18n/useLink.d.ts +34 -0
  35. package/dist/i18n/useLink.d.ts.map +1 -0
  36. package/dist/i18n/useLink.js +58 -0
  37. package/dist/i18n/useModuleTranslation.d.ts +11 -0
  38. package/dist/i18n/useModuleTranslation.d.ts.map +1 -0
  39. package/dist/i18n/useModuleTranslation.js +23 -0
  40. package/dist/index.d.ts +7 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +7 -0
  43. package/dist/layouts/AdminLayout.js +1 -1
  44. package/dist/layouts/AppProviders.d.ts +4 -1
  45. package/dist/layouts/AppProviders.d.ts.map +1 -1
  46. package/dist/layouts/AppProviders.js +18 -8
  47. package/dist/layouts/AuthLayout.js +1 -1
  48. package/dist/layouts/AuthLayoutWithSidebar.d.ts.map +1 -1
  49. package/dist/layouts/AuthLayoutWithSidebar.js +5 -3
  50. package/dist/layouts/RootLayout.d.ts +4 -1
  51. package/dist/layouts/RootLayout.d.ts.map +1 -1
  52. package/dist/layouts/RootLayout.js +2 -2
  53. package/dist/scripts/i18n-build.d.ts +7 -0
  54. package/dist/scripts/i18n-build.d.ts.map +1 -0
  55. package/dist/scripts/i18n-build.js +100 -0
  56. package/dist/scripts/init-app.js +12 -12
  57. package/dist/scripts/module-build.d.ts.map +1 -1
  58. package/dist/scripts/module-build.js +384 -81
  59. package/dist/styles.css +1 -1
  60. package/dist/templates/DefaultDoc.d.ts.map +1 -1
  61. package/dist/templates/DefaultDoc.js +90 -2
  62. package/dist/templates/middleware-i18n.example.d.ts +16 -0
  63. package/dist/templates/middleware-i18n.example.d.ts.map +1 -0
  64. package/dist/templates/middleware-i18n.example.js +72 -0
  65. package/package.json +14 -3
  66. package/src/app-shell/not-found.tsx +2 -2
  67. package/src/components/LanguageSwitcher.tsx +156 -0
  68. package/src/config/version.ts +19 -21
  69. package/src/i18n/LanguageProvider.tsx +7 -0
  70. package/src/i18n/README_LANG_HELPERS.md +187 -0
  71. package/src/i18n/TranslationsScript.tsx +17 -0
  72. package/src/i18n/cookies.ts +24 -0
  73. package/src/i18n/langHrefHelper.ts +51 -0
  74. package/src/i18n/server-helpers.ts +48 -0
  75. package/src/i18n/server-lang.ts +51 -0
  76. package/src/i18n/server.ts +13 -0
  77. package/src/i18n/types.ts +39 -0
  78. package/src/i18n/useLangRouter.ts +21 -0
  79. package/src/i18n/useLink.ts +60 -0
  80. package/src/i18n/useModuleTranslation.ts +27 -0
  81. package/src/index.ts +20 -0
  82. package/src/layouts/AdminLayout.tsx +1 -1
  83. package/src/layouts/AppProviders.tsx +35 -16
  84. package/src/layouts/AuthLayout.tsx +1 -1
  85. package/src/layouts/AuthLayoutWithSidebar.tsx +5 -3
  86. package/src/layouts/RootLayout.tsx +12 -5
  87. package/src/scripts/i18n-build.ts +122 -0
  88. package/src/scripts/init-app.ts +12 -12
  89. package/src/scripts/module-build.ts +476 -94
  90. package/src/templates/DefaultDoc.tsx +511 -1
  91. package/src/templates/middleware-i18n.example.ts +92 -0
  92. package/dist/app-shell/(admin)/dashboard/page.d.ts +0 -2
  93. package/dist/app-shell/(admin)/dashboard/page.d.ts.map +0 -1
  94. package/dist/app-shell/(admin)/dashboard/page.js +0 -5
  95. package/dist/app-shell/(admin)/page.d.ts +0 -2
  96. package/dist/app-shell/(admin)/page.d.ts.map +0 -1
  97. package/dist/app-shell/(admin)/page.js +0 -5
  98. package/dist/app-shell/(auth)/dashboard/page.d.ts +0 -2
  99. package/dist/app-shell/(auth)/dashboard/page.d.ts.map +0 -1
  100. package/dist/app-shell/(auth)/dashboard/page.js +0 -5
  101. package/dist/app-shell/(auth)/page.d.ts +0 -2
  102. package/dist/app-shell/(auth)/page.d.ts.map +0 -1
  103. package/dist/app-shell/(auth)/page.js +0 -5
  104. package/dist/components/TableStructure.d.ts +0 -8
  105. package/dist/components/TableStructure.d.ts.map +0 -1
  106. package/dist/components/TableStructure.js +0 -37
  107. package/dist/hooks/useNotificationsSimple.d.ts +0 -20
  108. package/dist/hooks/useNotificationsSimple.d.ts.map +0 -1
  109. package/dist/hooks/useNotificationsSimple.js +0 -37
  110. package/dist/module-build.d.ts +0 -2
  111. package/dist/module-build.d.ts.map +0 -1
  112. package/dist/module-build.js +0 -50
  113. package/dist/modules/index.d.ts +0 -3
  114. package/dist/modules/index.d.ts.map +0 -1
  115. package/dist/modules/index.js +0 -2
  116. package/dist/src/__tests__/module-registry.test.d.ts +0 -2
  117. package/dist/src/__tests__/module-registry.test.d.ts.map +0 -1
  118. package/dist/src/__tests__/module-registry.test.js +0 -53
  119. package/dist/src/app-shell/(admin)/layout.d.ts +0 -4
  120. package/dist/src/app-shell/(admin)/layout.d.ts.map +0 -1
  121. package/dist/src/app-shell/(admin)/layout.js +0 -5
  122. package/dist/src/app-shell/(auth)/layout.d.ts +0 -4
  123. package/dist/src/app-shell/(auth)/layout.d.ts.map +0 -1
  124. package/dist/src/app-shell/(auth)/layout.js +0 -5
  125. package/dist/src/app-shell/(public)/page.d.ts +0 -2
  126. package/dist/src/app-shell/(public)/page.d.ts.map +0 -1
  127. package/dist/src/app-shell/(public)/page.js +0 -5
  128. package/dist/src/app-shell/layout.d.ts +0 -3
  129. package/dist/src/app-shell/layout.d.ts.map +0 -1
  130. package/dist/src/app-shell/layout.js +0 -3
  131. package/dist/src/app-shell/not-found.d.ts +0 -2
  132. package/dist/src/app-shell/not-found.d.ts.map +0 -1
  133. package/dist/src/app-shell/not-found.js +0 -10
  134. package/dist/src/auth/authHelpers.d.ts +0 -7
  135. package/dist/src/auth/authHelpers.d.ts.map +0 -1
  136. package/dist/src/auth/authHelpers.js +0 -19
  137. package/dist/src/auth/useAuthSession.d.ts +0 -7
  138. package/dist/src/auth/useAuthSession.d.ts.map +0 -1
  139. package/dist/src/auth/useAuthSession.js +0 -49
  140. package/dist/src/cli.d.ts +0 -3
  141. package/dist/src/cli.d.ts.map +0 -1
  142. package/dist/src/cli.js +0 -143
  143. package/dist/src/components/NotificationContainer.d.ts +0 -2
  144. package/dist/src/components/NotificationContainer.d.ts.map +0 -1
  145. package/dist/src/components/NotificationContainer.js +0 -8
  146. package/dist/src/hooks/useNotifications.d.ts +0 -30
  147. package/dist/src/hooks/useNotifications.d.ts.map +0 -1
  148. package/dist/src/hooks/useNotifications.js +0 -165
  149. package/dist/src/index.d.ts +0 -22
  150. package/dist/src/index.d.ts.map +0 -1
  151. package/dist/src/index.js +0 -22
  152. package/dist/src/layouts/AdminLayout.d.ts +0 -4
  153. package/dist/src/layouts/AdminLayout.d.ts.map +0 -1
  154. package/dist/src/layouts/AdminLayout.js +0 -4
  155. package/dist/src/layouts/AdminLayoutWithSidebar.d.ts +0 -10
  156. package/dist/src/layouts/AdminLayoutWithSidebar.d.ts.map +0 -1
  157. package/dist/src/layouts/AdminLayoutWithSidebar.js +0 -62
  158. package/dist/src/layouts/AppProviders.d.ts +0 -27
  159. package/dist/src/layouts/AppProviders.d.ts.map +0 -1
  160. package/dist/src/layouts/AppProviders.js +0 -48
  161. package/dist/src/layouts/AuthLayout.d.ts +0 -4
  162. package/dist/src/layouts/AuthLayout.d.ts.map +0 -1
  163. package/dist/src/layouts/AuthLayout.js +0 -4
  164. package/dist/src/layouts/AuthLayoutWithSidebar.d.ts +0 -12
  165. package/dist/src/layouts/AuthLayoutWithSidebar.d.ts.map +0 -1
  166. package/dist/src/layouts/AuthLayoutWithSidebar.js +0 -60
  167. package/dist/src/layouts/PublicLayout.d.ts +0 -8
  168. package/dist/src/layouts/PublicLayout.d.ts.map +0 -1
  169. package/dist/src/layouts/PublicLayout.js +0 -6
  170. package/dist/src/layouts/PublicLayoutWithSidebar.d.ts +0 -9
  171. package/dist/src/layouts/PublicLayoutWithSidebar.d.ts.map +0 -1
  172. package/dist/src/layouts/PublicLayoutWithSidebar.js +0 -60
  173. package/dist/src/layouts/RootLayout.d.ts +0 -6
  174. package/dist/src/layouts/RootLayout.d.ts.map +0 -1
  175. package/dist/src/layouts/RootLayout.js +0 -9
  176. package/dist/src/modules/module-loader.d.ts +0 -5
  177. package/dist/src/modules/module-loader.d.ts.map +0 -1
  178. package/dist/src/modules/module-loader.js +0 -10
  179. package/dist/src/scripts/db-init.d.ts +0 -2
  180. package/dist/src/scripts/db-init.d.ts.map +0 -1
  181. package/dist/src/scripts/db-init.js +0 -300
  182. package/dist/src/scripts/db-migrations-sync.d.ts +0 -2
  183. package/dist/src/scripts/db-migrations-sync.d.ts.map +0 -1
  184. package/dist/src/scripts/db-migrations-sync.js +0 -84
  185. package/dist/src/scripts/dev-sync.d.ts +0 -2
  186. package/dist/src/scripts/dev-sync.d.ts.map +0 -1
  187. package/dist/src/scripts/dev-sync.js +0 -194
  188. package/dist/src/scripts/init-app.d.ts +0 -12
  189. package/dist/src/scripts/init-app.d.ts.map +0 -1
  190. package/dist/src/scripts/init-app.js +0 -2175
  191. package/dist/src/scripts/module-add.d.ts +0 -2
  192. package/dist/src/scripts/module-add.d.ts.map +0 -1
  193. package/dist/src/scripts/module-add.js +0 -232
  194. package/dist/src/scripts/module-build.d.ts +0 -2
  195. package/dist/src/scripts/module-build.d.ts.map +0 -1
  196. package/dist/src/scripts/module-build.js +0 -1280
  197. package/dist/src/scripts/module-create.d.ts +0 -28
  198. package/dist/src/scripts/module-create.d.ts.map +0 -1
  199. package/dist/src/scripts/module-create.js +0 -1429
  200. package/dist/src/scripts/module-delete.d.ts +0 -6
  201. package/dist/src/scripts/module-delete.d.ts.map +0 -1
  202. package/dist/src/scripts/module-delete.js +0 -147
  203. package/dist/src/scripts/module-list.d.ts +0 -2
  204. package/dist/src/scripts/module-list.d.ts.map +0 -1
  205. package/dist/src/scripts/module-list.js +0 -61
  206. package/dist/src/scripts/module-remove.d.ts +0 -2
  207. package/dist/src/scripts/module-remove.d.ts.map +0 -1
  208. package/dist/src/scripts/module-remove.js +0 -311
  209. package/dist/src/scripts/readme-build.d.ts +0 -2
  210. package/dist/src/scripts/readme-build.d.ts.map +0 -1
  211. package/dist/src/scripts/readme-build.js +0 -39
  212. package/dist/src/scripts/script-runner.d.ts +0 -5
  213. package/dist/src/scripts/script-runner.d.ts.map +0 -1
  214. package/dist/src/scripts/script-runner.js +0 -25
  215. package/dist/src/templates/AuthGuidePage.d.ts +0 -2
  216. package/dist/src/templates/AuthGuidePage.d.ts.map +0 -1
  217. package/dist/src/templates/AuthGuidePage.js +0 -9
  218. package/dist/src/templates/DefaultDoc.d.ts +0 -2
  219. package/dist/src/templates/DefaultDoc.d.ts.map +0 -1
  220. package/dist/src/templates/DefaultDoc.js +0 -240
  221. package/dist/src/templates/DocPage.d.ts +0 -17
  222. package/dist/src/templates/DocPage.d.ts.map +0 -1
  223. package/dist/src/templates/DocPage.js +0 -193
  224. package/dist/src/templates/DocsPageWithModules.d.ts +0 -2
  225. package/dist/src/templates/DocsPageWithModules.d.ts.map +0 -1
  226. package/dist/src/templates/DocsPageWithModules.js +0 -8
  227. package/dist/src/templates/MigrationsGuidePage.d.ts +0 -2
  228. package/dist/src/templates/MigrationsGuidePage.d.ts.map +0 -1
  229. package/dist/src/templates/MigrationsGuidePage.js +0 -11
  230. package/dist/src/templates/ModuleGuidePage.d.ts +0 -2
  231. package/dist/src/templates/ModuleGuidePage.d.ts.map +0 -1
  232. package/dist/src/templates/ModuleGuidePage.js +0 -14
  233. package/dist/src/templates/SimpleDocPage.d.ts +0 -2
  234. package/dist/src/templates/SimpleDocPage.d.ts.map +0 -1
  235. package/dist/src/templates/SimpleDocPage.js +0 -28
  236. package/dist/src/templates/SimpleHomePage.d.ts +0 -6
  237. package/dist/src/templates/SimpleHomePage.d.ts.map +0 -1
  238. package/dist/src/templates/SimpleHomePage.js +0 -7
  239. package/dist/src/types/menu.d.ts +0 -23
  240. package/dist/src/types/menu.d.ts.map +0 -1
  241. package/dist/src/types/menu.js +0 -1
  242. package/dist/templates/HomePage.d.ts +0 -6
  243. package/dist/templates/HomePage.d.ts.map +0 -1
  244. package/dist/templates/HomePage.js +0 -6
  245. package/dist/templates/components/AppAside.d.ts +0 -6
  246. package/dist/templates/components/AppAside.d.ts.map +0 -1
  247. package/dist/templates/components/AppAside.js +0 -9
  248. package/dist/templates/layouts/admin-layout.d.ts +0 -4
  249. package/dist/templates/layouts/admin-layout.d.ts.map +0 -1
  250. package/dist/templates/layouts/admin-layout.js +0 -6
  251. package/dist/templates/layouts/auth-layout.d.ts +0 -4
  252. package/dist/templates/layouts/auth-layout.d.ts.map +0 -1
  253. package/dist/templates/layouts/auth-layout.js +0 -6
  254. package/dist/templates/migrations/20201010100000_init.sql +0 -123
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Middleware optionnel pour la gestion automatique de la langue
3
+ *
4
+ * À placer dans apps/<votre-app>/middleware.ts
5
+ *
6
+ * Ce middleware :
7
+ * - Détecte si l'URL contient /fr ou /en
8
+ * - Si non, redirige vers la langue par défaut (cookie ou navigateur)
9
+ * - Sauvegarde la préférence dans un cookie
10
+ */
11
+ import { NextResponse } from "next/server";
12
+ // Langues supportées
13
+ const locales = ["fr", "en"];
14
+ const defaultLocale = "fr";
15
+ /**
16
+ * Extrait la langue préférée depuis le header Accept-Language
17
+ */
18
+ function getPreferredLocale(request) {
19
+ const acceptLanguage = request.headers.get("accept-language");
20
+ if (!acceptLanguage)
21
+ return defaultLocale;
22
+ // Parse "fr-FR,fr;q=0.9,en;q=0.8" -> ["fr-FR", "fr", "en"]
23
+ const languages = acceptLanguage.split(",").map((lang) => {
24
+ const [code] = lang.split(";");
25
+ return code.trim().split("-")[0];
26
+ });
27
+ // Trouver la première langue supportée
28
+ for (const lang of languages) {
29
+ if (locales.includes(lang)) {
30
+ return lang;
31
+ }
32
+ }
33
+ return defaultLocale;
34
+ }
35
+ /**
36
+ * Vérifie si l'URL contient déjà une langue
37
+ */
38
+ function hasLocale(pathname) {
39
+ return locales.some((locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`);
40
+ }
41
+ export function middleware(request) {
42
+ const { pathname } = request.nextUrl;
43
+ // Ignorer les fichiers statiques et API
44
+ if (pathname.startsWith("/_next") ||
45
+ pathname.startsWith("/api") ||
46
+ pathname.includes(".")) {
47
+ return NextResponse.next();
48
+ }
49
+ // Si l'URL contient déjà une langue, continuer
50
+ if (hasLocale(pathname)) {
51
+ return NextResponse.next();
52
+ }
53
+ // Déterminer la langue à utiliser
54
+ const cookieLocale = request.cookies.get("NEXT_LOCALE")?.value;
55
+ const locale = cookieLocale && locales.includes(cookieLocale)
56
+ ? cookieLocale
57
+ : getPreferredLocale(request);
58
+ // Rediriger vers l'URL avec la langue
59
+ const url = request.nextUrl.clone();
60
+ url.pathname = `/${locale}${pathname}`;
61
+ const response = NextResponse.redirect(url);
62
+ // Sauvegarder la préférence dans un cookie
63
+ response.cookies.set("NEXT_LOCALE", locale, {
64
+ path: "/",
65
+ maxAge: 365 * 24 * 60 * 60, // 1 an
66
+ });
67
+ return response;
68
+ }
69
+ export const config = {
70
+ // Matcher pour toutes les routes sauf les fichiers statiques
71
+ matcher: ["/((?!_next|api|.*\\..*).*)"],
72
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/app",
3
- "version": "2.0.18",
3
+ "version": "2.0.21",
4
4
  "description": "Framework modulaire Next.js avec CLI et système de modules",
5
5
  "private": false,
6
6
  "type": "module",
@@ -38,15 +38,26 @@
38
38
  "types": "./dist/index.d.ts",
39
39
  "import": "./dist/index.js",
40
40
  "default": "./dist/index.js"
41
+ },
42
+ "./i18n/server": {
43
+ "types": "./dist/i18n/server.d.ts",
44
+ "import": "./dist/i18n/server.js",
45
+ "default": "./dist/i18n/server.js"
46
+ },
47
+ "./i18n/server-lang": {
48
+ "types": "./dist/i18n/server-lang.d.ts",
49
+ "import": "./dist/i18n/server-lang.js",
50
+ "default": "./dist/i18n/server-lang.js"
41
51
  }
42
52
  },
43
53
  "dependencies": {
44
- "@lastbrain/core": "^2.0.16",
45
- "@lastbrain/ui": "^2.0.16",
54
+ "@lastbrain/core": "^2.0.19",
55
+ "@lastbrain/ui": "^2.0.19",
46
56
  "@supabase/supabase-js": "^2.84.0",
47
57
  "chalk": "^5.3.0",
48
58
  "commander": "^14.0.2",
49
59
  "fs-extra": "^11.2.0",
60
+ "glob": "^11.0.0",
50
61
  "inquirer": "^13.0.1",
51
62
  "lucide-react": "^0.554.0",
52
63
  "next": "^15.5.7",
@@ -1,9 +1,9 @@
1
1
  "use client";
2
+ import { useLocalizedRouter } from "@lastbrain/core";
2
3
  import { Button } from "@lastbrain/ui";
3
- import { useRouter } from "next/navigation";
4
4
 
5
5
  export default function NotFound() {
6
- const router = useRouter();
6
+ const router = useLocalizedRouter();
7
7
  return (
8
8
  <div className="flex min-h-screen items-center justify-center bg-background">
9
9
  <div className="mx-auto max-w-md text-center">
@@ -0,0 +1,156 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { useLanguage } from "../i18n/LanguageProvider";
5
+ import {
6
+ Button,
7
+ Dropdown,
8
+ DropdownItem,
9
+ DropdownMenu,
10
+ DropdownTrigger,
11
+ Spinner,
12
+ } from "@lastbrain/ui";
13
+
14
+ interface LanguageSwitcherProps {
15
+ variant?: "default" | "minimal";
16
+ className?: string;
17
+ }
18
+
19
+ export function LanguageSwitcher({
20
+ variant = "default",
21
+ className = "",
22
+ }: LanguageSwitcherProps) {
23
+ const { lang, setLang } = useLanguage();
24
+ const [flagUrls, setFlagUrls] = useState<Record<"fr" | "en", string>>({
25
+ fr: "",
26
+ en: "",
27
+ });
28
+ const [loading, setLoading] = useState(false);
29
+ const [isHydrated, setIsHydrated] = useState(false);
30
+
31
+ // Marquer l'hydration comme terminée
32
+ useEffect(() => {
33
+ setIsHydrated(true);
34
+ }, []);
35
+
36
+ useEffect(() => {
37
+ let canceled = false;
38
+ const objectUrls: string[] = [];
39
+
40
+ async function loadFlags() {
41
+ setLoading(true);
42
+ try {
43
+ const entries = await Promise.all(
44
+ (["fr", "en"] as const).map(async (code) => {
45
+ const apiCode = code === "fr" ? "fr" : "gb";
46
+ const response = await fetch(`https://flagcdn.com/${apiCode}.svg`);
47
+ if (!response.ok) throw new Error(`Flag fetch failed: ${code}`);
48
+ const blob = await response.blob();
49
+ const url = URL.createObjectURL(blob);
50
+ objectUrls.push(url);
51
+ return [code, url] as const;
52
+ })
53
+ );
54
+
55
+ if (!canceled) {
56
+ setFlagUrls(
57
+ Object.fromEntries(entries) as Record<"fr" | "en", string>
58
+ );
59
+ }
60
+ } catch (_err) {
61
+ if (!canceled) {
62
+ setFlagUrls({
63
+ fr: "https://flagcdn.com/fr.svg",
64
+ en: "https://flagcdn.com/gb.svg",
65
+ });
66
+ }
67
+ } finally {
68
+ if (!canceled) setLoading(false);
69
+ }
70
+ }
71
+
72
+ loadFlags();
73
+
74
+ return () => {
75
+ canceled = true;
76
+ objectUrls.forEach((url) => URL.revokeObjectURL(url));
77
+ };
78
+ }, []);
79
+
80
+ const renderFlag = (code: "fr" | "en") => (
81
+ <span className="inline-flex items-center gap-2">
82
+ {flagUrls[code] ? (
83
+ <img
84
+ src={flagUrls[code]}
85
+ alt={code === "fr" ? "Drapeau français" : "Flag English"}
86
+ className="h-4 w-6 rounded-sm border border-slate-200 object-cover"
87
+ />
88
+ ) : isHydrated ? (
89
+ <span className="inline-flex h-4 w-6 items-center justify-center rounded-sm bg-slate-200 text-[10px] font-semibold text-slate-600">
90
+ {code.toUpperCase()}
91
+ </span>
92
+ ) : null}
93
+ {/* <span>{code === "fr" ? "Français" : "English"}</span> */}
94
+ </span>
95
+ );
96
+
97
+ if (variant === "minimal") {
98
+ return (
99
+ <Dropdown size="sm" className="px-0 m-0">
100
+ <DropdownTrigger className="px-0 m-0">
101
+ {renderFlag(lang)}
102
+ </DropdownTrigger>
103
+ <DropdownMenu>
104
+ <DropdownItem
105
+ key="en"
106
+ onPress={() => setLang("en")}
107
+ startContent={renderFlag("en")}
108
+ className={lang === "en" ? "bg-primary/10" : ""}
109
+ >
110
+ English {isHydrated && lang === "en" && "✓"}
111
+ </DropdownItem>
112
+ <DropdownItem
113
+ key="fr"
114
+ onPress={() => setLang("fr")}
115
+ startContent={renderFlag("fr")}
116
+ className={lang === "fr" ? "bg-primary/10" : ""}
117
+ >
118
+ Français {isHydrated && lang === "fr" && "✓"}
119
+ </DropdownItem>
120
+ </DropdownMenu>
121
+ </Dropdown>
122
+ );
123
+ }
124
+
125
+ return (
126
+ <Dropdown size="sm" className="px-0">
127
+ <DropdownTrigger className="px-0 m-0">
128
+ <Button
129
+ variant="light"
130
+ size="sm"
131
+ className="px-0 m-0"
132
+ startContent={renderFlag(lang)}
133
+ endContent={loading ? <Spinner size="sm" /> : null}
134
+ >
135
+ {/* {lang === "fr" ? "Français" : "English"} */}
136
+ </Button>
137
+ </DropdownTrigger>
138
+ <DropdownMenu>
139
+ <DropdownItem
140
+ key="en"
141
+ onPress={() => setLang("en")}
142
+ className={lang === "en" ? "bg-primary/10" : ""}
143
+ >
144
+ {renderFlag("en")} English {isHydrated && lang === "en" && "✓"}
145
+ </DropdownItem>
146
+ <DropdownItem
147
+ key="fr"
148
+ onPress={() => setLang("fr")}
149
+ className={lang === "fr" ? "bg-primary/10" : ""}
150
+ >
151
+ {renderFlag("fr")} Français {isHydrated && lang === "fr" && "✓"}
152
+ </DropdownItem>
153
+ </DropdownMenu>
154
+ </Dropdown>
155
+ );
156
+ }
@@ -5,25 +5,23 @@
5
5
  */
6
6
 
7
7
  export const PACKAGE_VERSIONS: Record<string, string> = {
8
- "@lastbrain-labs/module-billing-pro": "^2.0.14",
9
- "@lastbrain-labs/module-cj-analyzer-pro": "^0.1.6",
10
- "@lastbrain-labs/module-core-cart-pro": "^2.0.14",
11
- "@lastbrain-labs/module-core-commerce-pro": "^2.0.14",
12
- "@lastbrain-labs/module-core-order-pro": "^2.0.14",
13
- "@lastbrain-labs/module-core-payment-pro": "^2.0.14",
14
- "@lastbrain-labs/module-core-product-pro": "^2.0.14",
15
- "@lastbrain-labs/module-recipes-pro": "^2.0.14",
16
- "@lastbrain-labs/module-shop-pro": "^0.1.6",
17
- "@lastbrain/app": "^2.0.17",
18
- "@lastbrain/core": "^2.0.15",
19
- "@lastbrain/module-ai": "^2.0.14",
20
- "@lastbrain/module-auth": "^2.0.15",
21
- "@lastbrain/module-legal": "^2.0.14",
22
- "@lastbrain/module-project-board": "^2.0.14",
23
- "@lastbrain/module-tasks": "^2.0.14",
24
- "@lastbrain/ui": "^2.0.15",
25
- "apps/recipe": "^2.0.10",
26
- "apps/test-app": "^2.0.10",
27
- "apps/test-module": "^2.0.10",
28
- "lastbrain": "^2.0.10",
8
+ "@lastbrain-labs/module-billing-pro": "^2.0.17",
9
+ "@lastbrain-labs/module-cj-analyzer-pro": "^0.1.9",
10
+ "@lastbrain-labs/module-core-cart-pro": "^2.0.17",
11
+ "@lastbrain-labs/module-core-commerce-pro": "^2.0.17",
12
+ "@lastbrain-labs/module-core-order-pro": "^2.0.17",
13
+ "@lastbrain-labs/module-core-payment-pro": "^2.0.17",
14
+ "@lastbrain-labs/module-core-product-pro": "^2.0.17",
15
+ "@lastbrain-labs/module-recipes-pro": "^2.0.17",
16
+ "@lastbrain-labs/module-shop-pro": "^0.1.9",
17
+ "@lastbrain/app": "^2.0.20",
18
+ "@lastbrain/core": "^2.0.18",
19
+ "@lastbrain/module-ai": "^2.0.17",
20
+ "@lastbrain/module-auth": "^2.0.18",
21
+ "@lastbrain/module-legal": "^2.0.17",
22
+ "@lastbrain/module-project-board": "^2.0.17",
23
+ "@lastbrain/module-tasks": "^2.0.17",
24
+ "@lastbrain/ui": "^2.0.18",
25
+ "apps/recipe": "^2.0.11",
26
+ "lastbrain": "^2.0.11",
29
27
  };
@@ -0,0 +1,7 @@
1
+ "use client";
2
+
3
+ // Cette implémentation a été déplacée dans @lastbrain/core pour éviter les dépendances cycliques
4
+ // Réexport pour la compatibilité backward
5
+ export { LanguageProvider, type Language } from "@lastbrain/core";
6
+ export { useLanguage } from "@lastbrain/core";
7
+ export { LanguageContext } from "@lastbrain/core";
@@ -0,0 +1,187 @@
1
+ # Helpers pour URLs avec [lang]
2
+
3
+ Ce document explique comment utiliser les différents helpers pour générer des URLs avec le paramètre `[lang]` automatiquement.
4
+
5
+ ## 1. Hook `useLink()` - Pour les composants React
6
+
7
+ **Utilisation:** À l'intérieur d'un composant client.
8
+
9
+ ```typescript
10
+ "use client";
11
+ import { useLink } from "@lastbrain/app";
12
+
13
+ export function MyComponent() {
14
+ const link = useLink();
15
+
16
+ return (
17
+ <>
18
+ <a href={link("/recipes")}>Recettes</a>
19
+ <img src={link("/api/storage/avatar.jpg")} />
20
+ <a href={link("/settings")}>Paramètres</a>
21
+ </>
22
+ );
23
+ }
24
+ ```
25
+
26
+ **Comportement:**
27
+
28
+ - `/recipes` → `/fr/recipes` (si lang="fr")
29
+ - `/api/storage/...` → `/api/storage/...` (API routes non modifiées)
30
+ - `https://example.com` → `https://example.com` (URLs absolues inchangées)
31
+ - `/` → `/fr/` (racine avec lang)
32
+
33
+ ---
34
+
35
+ ## 2. Helper `langHref()` - Pour les transformations pures
36
+
37
+ **Utilisation:** Pour transformer une URL avec une langue connue (sans hook).
38
+
39
+ ```typescript
40
+ import { langHref } from "@lastbrain/app";
41
+
42
+ const frHref = langHref("/recipes", "fr"); // "/fr/recipes"
43
+ const enHref = langHref("/recipes", "en"); // "/en/recipes"
44
+ ```
45
+
46
+ **Cas d'usage typical:**
47
+
48
+ - Transformation de données avant rendu React
49
+ - Utilisation dans les mappages de tableaux
50
+ - Dans les `DropdownMenu` items
51
+
52
+ ---
53
+
54
+ ## 3. Helper `transformLangHref()` - Pour un seul élément
55
+
56
+ **Utilisation:** Transformer un objet avec un champ `href`.
57
+
58
+ ```typescript
59
+ import { transformLangHref } from "@lastbrain/app";
60
+ import { useLanguage } from "@lastbrain/app";
61
+
62
+ export function MyComponent() {
63
+ const { lang } = useLanguage();
64
+
65
+ const menuItem = {
66
+ label: "Paramètres",
67
+ href: "/settings",
68
+ icon: "Settings"
69
+ };
70
+
71
+ const transformed = transformLangHref(menuItem, lang);
72
+ // Résultat: { label: "Paramètres", href: "/fr/settings", icon: "Settings" }
73
+
74
+ return <a href={transformed.href}>{transformed.label}</a>;
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 4. Helper `transformLangHrefs()` - Pour un tableau d'éléments
81
+
82
+ **Utilisation:** Transformer un tableau d'objets avec des `href`.
83
+
84
+ ```typescript
85
+ import { transformLangHrefs, useLanguage } from "@lastbrain/app";
86
+
87
+ export function Menu() {
88
+ const { lang } = useLanguage();
89
+
90
+ const accountMenu = [
91
+ { title: "Mon profil", path: "/profile" },
92
+ { title: "Paramètres", path: "/settings" },
93
+ { title: "Se déconnecter", path: "/logout" }
94
+ ];
95
+
96
+ const transformed = transformLangHrefs(
97
+ accountMenu.map(item => ({ ...item, href: item.path })),
98
+ lang
99
+ );
100
+
101
+ return transformed.map(item => (
102
+ <a key={item.title} href={item.href}>{item.title}</a>
103
+ ));
104
+ }
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Exemple complet: `AccountButton.tsx`
110
+
111
+ ```typescript
112
+ "use client";
113
+
114
+ import { useLanguage, langHref } from "@lastbrain/app";
115
+ import { DropdownMenu } from "@lastbrain/ui";
116
+
117
+ export function AccountButton({ accountMenu = [], user }) {
118
+ const { lang } = useLanguage();
119
+
120
+ return (
121
+ <DropdownMenu
122
+ items={[
123
+ {
124
+ key: "hello",
125
+ label: `Bonjour ${user?.email}`,
126
+ isReadOnly: true,
127
+ },
128
+ // Transformer les hrefs avec langHref
129
+ ...accountMenu.map((item) => ({
130
+ key: item.path,
131
+ label: item.title,
132
+ href: item.path.includes("logout")
133
+ ? undefined
134
+ : langHref(item.path, lang), // ← ici
135
+ })),
136
+ ]}
137
+ >
138
+ {/* Rendu des items */}
139
+ </DropdownMenu>
140
+ );
141
+ }
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Tableau comparatif
147
+
148
+ | Helper | Type | Contexte | Retour |
149
+ | ---------------------- | ------------- | ----------------------------- | ---------------------------- |
150
+ | `useLink()` | Hook | Composant client | Fonction `(href) => string` |
151
+ | `langHref()` | Fonction pure | N'importe où | `string` |
152
+ | `transformLangHref()` | Fonction pure | Transformation d'objet unique | Objet avec `href` modifié |
153
+ | `transformLangHrefs()` | Fonction pure | Transformation de tableau | Tableau avec `href` modifiés |
154
+
155
+ ---
156
+
157
+ ## Cas d'usage courants
158
+
159
+ ### Dropdown items
160
+
161
+ ```typescript
162
+ const items = accountMenu.map((item) => ({
163
+ ...item,
164
+ href: langHref(item.path, lang),
165
+ }));
166
+ ```
167
+
168
+ ### Navigation menus
169
+
170
+ ```typescript
171
+ const link = useLink();
172
+ <a href={link("/recipes")}>Recettes</a>
173
+ ```
174
+
175
+ ### API calls (no lang prefix)
176
+
177
+ ```typescript
178
+ const link = useLink();
179
+ fetch(link("/api/users")); // → "/api/users"
180
+ ```
181
+
182
+ ### Redirection après authentification
183
+
184
+ ```typescript
185
+ const router = useLocalizedRouter();
186
+ router.push(useLink()("/dashboard"));
187
+ ```
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Composant pour sérialiser les traductions dans le HTML
3
+ * Cela permet au client d'accéder aux traductions sans les repasser en props
4
+ */
5
+ export function TranslationsScript({
6
+ translations,
7
+ }: {
8
+ translations: Record<string, string>;
9
+ }) {
10
+ return (
11
+ <script
12
+ dangerouslySetInnerHTML={{
13
+ __html: `window.__TRANSLATIONS__ = ${JSON.stringify(translations)};`,
14
+ }}
15
+ />
16
+ );
17
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Gestion des cookies pour la langue
3
+ */
4
+
5
+ export function setCookie(name: string, value: string, days: number) {
6
+ if (typeof document === "undefined") return;
7
+
8
+ const expires = new Date();
9
+ expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000);
10
+ document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/`;
11
+ }
12
+
13
+ export function getCookie(name: string): string | null {
14
+ if (typeof document === "undefined") return null;
15
+
16
+ const nameEQ = `${name}=`;
17
+ const ca = document.cookie.split(";");
18
+ for (let i = 0; i < ca.length; i++) {
19
+ let c = ca[i];
20
+ while (c.charAt(0) === " ") c = c.substring(1, c.length);
21
+ if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
22
+ }
23
+ return null;
24
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Fichier utilitaire pour transformer des listes/objets avec hrefs
3
+ * Utile pour les données statiques qui n'ont pas accès au hook useLanguage
4
+ */
5
+
6
+ import { langHref } from "./useLink";
7
+ import type { Language } from "./types";
8
+
9
+ /**
10
+ * Transforme un tableau d'éléments avec des hrefs
11
+ * @param items - Tableau d'éléments contenant un champ href
12
+ * @param lang - Code de langue
13
+ * @returns Le tableau avec les hrefs transformés
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const menu = transformLangHrefs([
18
+ * { label: "Accueil", href: "/" },
19
+ * { label: "Recettes", href: "/recipes" }
20
+ * ], "fr");
21
+ * // Résultat: [
22
+ * // { label: "Accueil", href: "/fr" },
23
+ * // { label: "Recettes", href: "/fr/recipes" }
24
+ * // ]
25
+ * ```
26
+ */
27
+ export function transformLangHrefs<T extends { href?: string }>(
28
+ items: T[],
29
+ lang: Language
30
+ ): T[] {
31
+ return items.map((item) => ({
32
+ ...item,
33
+ href: item.href ? langHref(item.href, lang) : undefined,
34
+ }));
35
+ }
36
+
37
+ /**
38
+ * Transforme un seul élément avec un href
39
+ * @param item - Élément contenant un champ href
40
+ * @param lang - Code de langue
41
+ * @returns L'élément avec le href transformé
42
+ */
43
+ export function transformLangHref<T extends { href?: string }>(
44
+ item: T,
45
+ lang: Language
46
+ ): T {
47
+ return {
48
+ ...item,
49
+ href: item.href ? langHref(item.href, lang) : undefined,
50
+ };
51
+ }
@@ -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
+ }