@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.
- 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 +390 -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 +36 -23
- 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 +485 -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,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";
|
|
@@ -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
|
-
);
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
|
|
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
|
-
<
|
|
86
|
-
<
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
{
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
}
|
|
@@ -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
|
-
//
|
|
81
|
-
const
|
|
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={
|
|
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=
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
});
|