@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.
- package/dist/app-shell/not-found.js +2 -2
- package/dist/components/LanguageSwitcher.d.ts +7 -0
- package/dist/components/LanguageSwitcher.d.ts.map +1 -0
- package/dist/components/LanguageSwitcher.js +62 -0
- package/dist/config/version.d.ts.map +1 -1
- package/dist/config/version.js +19 -21
- package/dist/i18n/LanguageProvider.d.ts +4 -0
- package/dist/i18n/LanguageProvider.d.ts.map +1 -0
- package/dist/i18n/LanguageProvider.js +6 -0
- package/dist/i18n/TranslationsScript.d.ts +8 -0
- package/dist/i18n/TranslationsScript.d.ts.map +1 -0
- package/dist/i18n/TranslationsScript.js +10 -0
- package/dist/i18n/cookies.d.ts +6 -0
- package/dist/i18n/cookies.d.ts.map +1 -0
- package/dist/i18n/cookies.js +24 -0
- package/dist/i18n/langHrefHelper.d.ts +36 -0
- package/dist/i18n/langHrefHelper.d.ts.map +1 -0
- package/dist/i18n/langHrefHelper.js +41 -0
- package/dist/i18n/server-helpers.d.ts +33 -0
- package/dist/i18n/server-helpers.d.ts.map +1 -0
- package/dist/i18n/server-helpers.js +39 -0
- package/dist/i18n/server-lang.d.ts +10 -0
- package/dist/i18n/server-lang.d.ts.map +1 -0
- package/dist/i18n/server-lang.js +42 -0
- package/dist/i18n/server.d.ts +10 -0
- package/dist/i18n/server.d.ts.map +1 -0
- package/dist/i18n/server.js +8 -0
- package/dist/i18n/types.d.ts +38 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +4 -0
- package/dist/i18n/useLangRouter.d.ts +12 -0
- package/dist/i18n/useLangRouter.d.ts.map +1 -0
- package/dist/i18n/useLangRouter.js +18 -0
- package/dist/i18n/useLink.d.ts +34 -0
- package/dist/i18n/useLink.d.ts.map +1 -0
- package/dist/i18n/useLink.js +58 -0
- package/dist/i18n/useModuleTranslation.d.ts +11 -0
- package/dist/i18n/useModuleTranslation.d.ts.map +1 -0
- package/dist/i18n/useModuleTranslation.js +23 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/layouts/AdminLayout.js +1 -1
- package/dist/layouts/AppProviders.d.ts +4 -1
- package/dist/layouts/AppProviders.d.ts.map +1 -1
- package/dist/layouts/AppProviders.js +18 -8
- package/dist/layouts/AuthLayout.js +1 -1
- package/dist/layouts/AuthLayoutWithSidebar.d.ts.map +1 -1
- package/dist/layouts/AuthLayoutWithSidebar.js +5 -3
- package/dist/layouts/RootLayout.d.ts +4 -1
- package/dist/layouts/RootLayout.d.ts.map +1 -1
- package/dist/layouts/RootLayout.js +2 -2
- package/dist/scripts/i18n-build.d.ts +7 -0
- package/dist/scripts/i18n-build.d.ts.map +1 -0
- package/dist/scripts/i18n-build.js +100 -0
- package/dist/scripts/init-app.js +12 -12
- package/dist/scripts/module-build.d.ts.map +1 -1
- package/dist/scripts/module-build.js +384 -81
- package/dist/styles.css +1 -1
- package/dist/templates/DefaultDoc.d.ts.map +1 -1
- package/dist/templates/DefaultDoc.js +90 -2
- package/dist/templates/middleware-i18n.example.d.ts +16 -0
- package/dist/templates/middleware-i18n.example.d.ts.map +1 -0
- package/dist/templates/middleware-i18n.example.js +72 -0
- package/package.json +14 -3
- package/src/app-shell/not-found.tsx +2 -2
- package/src/components/LanguageSwitcher.tsx +156 -0
- package/src/config/version.ts +19 -21
- package/src/i18n/LanguageProvider.tsx +7 -0
- package/src/i18n/README_LANG_HELPERS.md +187 -0
- package/src/i18n/TranslationsScript.tsx +17 -0
- package/src/i18n/cookies.ts +24 -0
- package/src/i18n/langHrefHelper.ts +51 -0
- package/src/i18n/server-helpers.ts +48 -0
- package/src/i18n/server-lang.ts +51 -0
- package/src/i18n/server.ts +13 -0
- package/src/i18n/types.ts +39 -0
- package/src/i18n/useLangRouter.ts +21 -0
- package/src/i18n/useLink.ts +60 -0
- package/src/i18n/useModuleTranslation.ts +27 -0
- package/src/index.ts +20 -0
- package/src/layouts/AdminLayout.tsx +1 -1
- package/src/layouts/AppProviders.tsx +35 -16
- package/src/layouts/AuthLayout.tsx +1 -1
- package/src/layouts/AuthLayoutWithSidebar.tsx +5 -3
- package/src/layouts/RootLayout.tsx +12 -5
- package/src/scripts/i18n-build.ts +122 -0
- package/src/scripts/init-app.ts +12 -12
- package/src/scripts/module-build.ts +476 -94
- package/src/templates/DefaultDoc.tsx +511 -1
- package/src/templates/middleware-i18n.example.ts +92 -0
- package/dist/app-shell/(admin)/dashboard/page.d.ts +0 -2
- package/dist/app-shell/(admin)/dashboard/page.d.ts.map +0 -1
- package/dist/app-shell/(admin)/dashboard/page.js +0 -5
- package/dist/app-shell/(admin)/page.d.ts +0 -2
- package/dist/app-shell/(admin)/page.d.ts.map +0 -1
- package/dist/app-shell/(admin)/page.js +0 -5
- package/dist/app-shell/(auth)/dashboard/page.d.ts +0 -2
- package/dist/app-shell/(auth)/dashboard/page.d.ts.map +0 -1
- package/dist/app-shell/(auth)/dashboard/page.js +0 -5
- package/dist/app-shell/(auth)/page.d.ts +0 -2
- package/dist/app-shell/(auth)/page.d.ts.map +0 -1
- package/dist/app-shell/(auth)/page.js +0 -5
- package/dist/components/TableStructure.d.ts +0 -8
- package/dist/components/TableStructure.d.ts.map +0 -1
- package/dist/components/TableStructure.js +0 -37
- package/dist/hooks/useNotificationsSimple.d.ts +0 -20
- package/dist/hooks/useNotificationsSimple.d.ts.map +0 -1
- package/dist/hooks/useNotificationsSimple.js +0 -37
- package/dist/module-build.d.ts +0 -2
- package/dist/module-build.d.ts.map +0 -1
- package/dist/module-build.js +0 -50
- package/dist/modules/index.d.ts +0 -3
- package/dist/modules/index.d.ts.map +0 -1
- package/dist/modules/index.js +0 -2
- package/dist/src/__tests__/module-registry.test.d.ts +0 -2
- package/dist/src/__tests__/module-registry.test.d.ts.map +0 -1
- package/dist/src/__tests__/module-registry.test.js +0 -53
- package/dist/src/app-shell/(admin)/layout.d.ts +0 -4
- package/dist/src/app-shell/(admin)/layout.d.ts.map +0 -1
- package/dist/src/app-shell/(admin)/layout.js +0 -5
- package/dist/src/app-shell/(auth)/layout.d.ts +0 -4
- package/dist/src/app-shell/(auth)/layout.d.ts.map +0 -1
- package/dist/src/app-shell/(auth)/layout.js +0 -5
- package/dist/src/app-shell/(public)/page.d.ts +0 -2
- package/dist/src/app-shell/(public)/page.d.ts.map +0 -1
- package/dist/src/app-shell/(public)/page.js +0 -5
- package/dist/src/app-shell/layout.d.ts +0 -3
- package/dist/src/app-shell/layout.d.ts.map +0 -1
- package/dist/src/app-shell/layout.js +0 -3
- package/dist/src/app-shell/not-found.d.ts +0 -2
- package/dist/src/app-shell/not-found.d.ts.map +0 -1
- package/dist/src/app-shell/not-found.js +0 -10
- package/dist/src/auth/authHelpers.d.ts +0 -7
- package/dist/src/auth/authHelpers.d.ts.map +0 -1
- package/dist/src/auth/authHelpers.js +0 -19
- package/dist/src/auth/useAuthSession.d.ts +0 -7
- package/dist/src/auth/useAuthSession.d.ts.map +0 -1
- package/dist/src/auth/useAuthSession.js +0 -49
- package/dist/src/cli.d.ts +0 -3
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -143
- package/dist/src/components/NotificationContainer.d.ts +0 -2
- package/dist/src/components/NotificationContainer.d.ts.map +0 -1
- package/dist/src/components/NotificationContainer.js +0 -8
- package/dist/src/hooks/useNotifications.d.ts +0 -30
- package/dist/src/hooks/useNotifications.d.ts.map +0 -1
- package/dist/src/hooks/useNotifications.js +0 -165
- package/dist/src/index.d.ts +0 -22
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -22
- package/dist/src/layouts/AdminLayout.d.ts +0 -4
- package/dist/src/layouts/AdminLayout.d.ts.map +0 -1
- package/dist/src/layouts/AdminLayout.js +0 -4
- package/dist/src/layouts/AdminLayoutWithSidebar.d.ts +0 -10
- package/dist/src/layouts/AdminLayoutWithSidebar.d.ts.map +0 -1
- package/dist/src/layouts/AdminLayoutWithSidebar.js +0 -62
- package/dist/src/layouts/AppProviders.d.ts +0 -27
- package/dist/src/layouts/AppProviders.d.ts.map +0 -1
- package/dist/src/layouts/AppProviders.js +0 -48
- package/dist/src/layouts/AuthLayout.d.ts +0 -4
- package/dist/src/layouts/AuthLayout.d.ts.map +0 -1
- package/dist/src/layouts/AuthLayout.js +0 -4
- package/dist/src/layouts/AuthLayoutWithSidebar.d.ts +0 -12
- package/dist/src/layouts/AuthLayoutWithSidebar.d.ts.map +0 -1
- package/dist/src/layouts/AuthLayoutWithSidebar.js +0 -60
- package/dist/src/layouts/PublicLayout.d.ts +0 -8
- package/dist/src/layouts/PublicLayout.d.ts.map +0 -1
- package/dist/src/layouts/PublicLayout.js +0 -6
- package/dist/src/layouts/PublicLayoutWithSidebar.d.ts +0 -9
- package/dist/src/layouts/PublicLayoutWithSidebar.d.ts.map +0 -1
- package/dist/src/layouts/PublicLayoutWithSidebar.js +0 -60
- package/dist/src/layouts/RootLayout.d.ts +0 -6
- package/dist/src/layouts/RootLayout.d.ts.map +0 -1
- package/dist/src/layouts/RootLayout.js +0 -9
- package/dist/src/modules/module-loader.d.ts +0 -5
- package/dist/src/modules/module-loader.d.ts.map +0 -1
- package/dist/src/modules/module-loader.js +0 -10
- package/dist/src/scripts/db-init.d.ts +0 -2
- package/dist/src/scripts/db-init.d.ts.map +0 -1
- package/dist/src/scripts/db-init.js +0 -300
- package/dist/src/scripts/db-migrations-sync.d.ts +0 -2
- package/dist/src/scripts/db-migrations-sync.d.ts.map +0 -1
- package/dist/src/scripts/db-migrations-sync.js +0 -84
- package/dist/src/scripts/dev-sync.d.ts +0 -2
- package/dist/src/scripts/dev-sync.d.ts.map +0 -1
- package/dist/src/scripts/dev-sync.js +0 -194
- package/dist/src/scripts/init-app.d.ts +0 -12
- package/dist/src/scripts/init-app.d.ts.map +0 -1
- package/dist/src/scripts/init-app.js +0 -2175
- package/dist/src/scripts/module-add.d.ts +0 -2
- package/dist/src/scripts/module-add.d.ts.map +0 -1
- package/dist/src/scripts/module-add.js +0 -232
- package/dist/src/scripts/module-build.d.ts +0 -2
- package/dist/src/scripts/module-build.d.ts.map +0 -1
- package/dist/src/scripts/module-build.js +0 -1280
- package/dist/src/scripts/module-create.d.ts +0 -28
- package/dist/src/scripts/module-create.d.ts.map +0 -1
- package/dist/src/scripts/module-create.js +0 -1429
- package/dist/src/scripts/module-delete.d.ts +0 -6
- package/dist/src/scripts/module-delete.d.ts.map +0 -1
- package/dist/src/scripts/module-delete.js +0 -147
- package/dist/src/scripts/module-list.d.ts +0 -2
- package/dist/src/scripts/module-list.d.ts.map +0 -1
- package/dist/src/scripts/module-list.js +0 -61
- package/dist/src/scripts/module-remove.d.ts +0 -2
- package/dist/src/scripts/module-remove.d.ts.map +0 -1
- package/dist/src/scripts/module-remove.js +0 -311
- package/dist/src/scripts/readme-build.d.ts +0 -2
- package/dist/src/scripts/readme-build.d.ts.map +0 -1
- package/dist/src/scripts/readme-build.js +0 -39
- package/dist/src/scripts/script-runner.d.ts +0 -5
- package/dist/src/scripts/script-runner.d.ts.map +0 -1
- package/dist/src/scripts/script-runner.js +0 -25
- package/dist/src/templates/AuthGuidePage.d.ts +0 -2
- package/dist/src/templates/AuthGuidePage.d.ts.map +0 -1
- package/dist/src/templates/AuthGuidePage.js +0 -9
- package/dist/src/templates/DefaultDoc.d.ts +0 -2
- package/dist/src/templates/DefaultDoc.d.ts.map +0 -1
- package/dist/src/templates/DefaultDoc.js +0 -240
- package/dist/src/templates/DocPage.d.ts +0 -17
- package/dist/src/templates/DocPage.d.ts.map +0 -1
- package/dist/src/templates/DocPage.js +0 -193
- package/dist/src/templates/DocsPageWithModules.d.ts +0 -2
- package/dist/src/templates/DocsPageWithModules.d.ts.map +0 -1
- package/dist/src/templates/DocsPageWithModules.js +0 -8
- package/dist/src/templates/MigrationsGuidePage.d.ts +0 -2
- package/dist/src/templates/MigrationsGuidePage.d.ts.map +0 -1
- package/dist/src/templates/MigrationsGuidePage.js +0 -11
- package/dist/src/templates/ModuleGuidePage.d.ts +0 -2
- package/dist/src/templates/ModuleGuidePage.d.ts.map +0 -1
- package/dist/src/templates/ModuleGuidePage.js +0 -14
- package/dist/src/templates/SimpleDocPage.d.ts +0 -2
- package/dist/src/templates/SimpleDocPage.d.ts.map +0 -1
- package/dist/src/templates/SimpleDocPage.js +0 -28
- package/dist/src/templates/SimpleHomePage.d.ts +0 -6
- package/dist/src/templates/SimpleHomePage.d.ts.map +0 -1
- package/dist/src/templates/SimpleHomePage.js +0 -7
- package/dist/src/types/menu.d.ts +0 -23
- package/dist/src/types/menu.d.ts.map +0 -1
- package/dist/src/types/menu.js +0 -1
- package/dist/templates/HomePage.d.ts +0 -6
- package/dist/templates/HomePage.d.ts.map +0 -1
- package/dist/templates/HomePage.js +0 -6
- package/dist/templates/components/AppAside.d.ts +0 -6
- package/dist/templates/components/AppAside.d.ts.map +0 -1
- package/dist/templates/components/AppAside.js +0 -9
- package/dist/templates/layouts/admin-layout.d.ts +0 -4
- package/dist/templates/layouts/admin-layout.d.ts.map +0 -1
- package/dist/templates/layouts/admin-layout.js +0 -6
- package/dist/templates/layouts/auth-layout.d.ts +0 -4
- package/dist/templates/layouts/auth-layout.d.ts.map +0 -1
- package/dist/templates/layouts/auth-layout.js +0 -6
- 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.
|
|
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.
|
|
45
|
-
"@lastbrain/ui": "^2.0.
|
|
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 =
|
|
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
|
+
}
|
package/src/config/version.ts
CHANGED
|
@@ -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.
|
|
9
|
-
"@lastbrain-labs/module-cj-analyzer-pro": "^0.1.
|
|
10
|
-
"@lastbrain-labs/module-core-cart-pro": "^2.0.
|
|
11
|
-
"@lastbrain-labs/module-core-commerce-pro": "^2.0.
|
|
12
|
-
"@lastbrain-labs/module-core-order-pro": "^2.0.
|
|
13
|
-
"@lastbrain-labs/module-core-payment-pro": "^2.0.
|
|
14
|
-
"@lastbrain-labs/module-core-product-pro": "^2.0.
|
|
15
|
-
"@lastbrain-labs/module-recipes-pro": "^2.0.
|
|
16
|
-
"@lastbrain-labs/module-shop-pro": "^0.1.
|
|
17
|
-
"@lastbrain/app": "^2.0.
|
|
18
|
-
"@lastbrain/core": "^2.0.
|
|
19
|
-
"@lastbrain/module-ai": "^2.0.
|
|
20
|
-
"@lastbrain/module-auth": "^2.0.
|
|
21
|
-
"@lastbrain/module-legal": "^2.0.
|
|
22
|
-
"@lastbrain/module-project-board": "^2.0.
|
|
23
|
-
"@lastbrain/module-tasks": "^2.0.
|
|
24
|
-
"@lastbrain/ui": "^2.0.
|
|
25
|
-
"apps/recipe": "^2.0.
|
|
26
|
-
"
|
|
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
|
+
}
|