@lastbrain/app 2.0.23 → 2.0.31
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/components/LanguageSwitcher.d.ts +3 -1
- package/dist/components/LanguageSwitcher.d.ts.map +1 -1
- package/dist/components/LanguageSwitcher.js +50 -21
- package/dist/config/version.js +19 -19
- package/dist/i18n/server-lang.d.ts +1 -1
- package/dist/i18n/server-lang.d.ts.map +1 -1
- package/dist/i18n/types.d.ts +1 -1
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/layouts/AppProviders.d.ts +3 -1
- package/dist/layouts/AppProviders.d.ts.map +1 -1
- package/dist/layouts/AppProviders.js +2 -2
- package/dist/scripts/init-app.d.ts.map +1 -1
- package/dist/scripts/init-app.js +46 -4
- package/dist/scripts/module-build.d.ts.map +1 -1
- package/dist/scripts/module-build.js +407 -17
- package/package.json +23 -25
- package/src/components/LanguageSwitcher.tsx +85 -52
- package/src/config/version.ts +19 -19
- package/src/i18n/server-lang.ts +2 -1
- package/src/i18n/types.ts +2 -1
- package/src/layouts/AppProviders.tsx +8 -1
- package/src/scripts/init-app.ts +62 -4
- package/src/scripts/module-build.ts +495 -17
|
@@ -319,6 +319,11 @@ export default function ${wrapperName}${hasDynamicParams ? "(props: Record<strin
|
|
|
319
319
|
.filter((seg) => seg.startsWith("[") && seg.endsWith("]"))
|
|
320
320
|
.map((seg) => seg.slice(1, -1)); // Enlève les []
|
|
321
321
|
|
|
322
|
+
// Détecter si le module path lui-même a des params dynamiques (pas seulement [lang])
|
|
323
|
+
const moduleHasDynamicParams = segments.some(
|
|
324
|
+
(seg) => seg.startsWith("[") && seg.endsWith("]")
|
|
325
|
+
);
|
|
326
|
+
|
|
322
327
|
// Générer le type des params si nécessaire
|
|
323
328
|
const paramsType =
|
|
324
329
|
dynamicParamNames.length > 0
|
|
@@ -333,23 +338,60 @@ export default function ${wrapperName}${hasDynamicParams ? "(props: Record<strin
|
|
|
333
338
|
// Détecter si c'est un Server Component (entryPoint: "server")
|
|
334
339
|
const isServerComponent = page.entryPoint === "server";
|
|
335
340
|
|
|
336
|
-
// Les Server Components reçoivent params en props
|
|
341
|
+
// Les Server Components reçoivent params en props si la page a des params dynamiques
|
|
337
342
|
// Les Client Components utilisent useParams() en interne
|
|
338
|
-
const componentProps =
|
|
339
|
-
? "params={params}"
|
|
340
|
-
: "";
|
|
343
|
+
const componentProps =
|
|
344
|
+
isServerComponent && hasDynamicParams ? "params={params}" : "";
|
|
341
345
|
|
|
342
346
|
// Si params existe mais n'est pas passé au composant, on doit le unwrap quand même
|
|
343
347
|
const awaitParams =
|
|
344
|
-
hasDynamicParams && !
|
|
348
|
+
hasDynamicParams && !componentProps
|
|
345
349
|
? "await params; // Unwrap params for Next.js 15\n "
|
|
346
350
|
: "";
|
|
347
351
|
|
|
352
|
+
// Calculer le chemin relatif correct pour locales.generated
|
|
353
|
+
// routeDir = appDirectory + sectionPath + segments
|
|
354
|
+
// on doit remonter de (sectionPath.length + segments.length) niveaux pour atteindre appDirectory
|
|
355
|
+
// puis accéder à config/locales.generated
|
|
356
|
+
const pathDepth = sectionPath.length + segments.length;
|
|
357
|
+
const localeImportPath =
|
|
358
|
+
Array(pathDepth + 1)
|
|
359
|
+
.fill("..")
|
|
360
|
+
.join("/") + "/config/locales.generated";
|
|
361
|
+
|
|
362
|
+
// Pour les pages publiques (section: "public"), importer et passer LOCALE_CONFIG
|
|
363
|
+
const localeImport =
|
|
364
|
+
page.section === "public"
|
|
365
|
+
? `\nimport { LOCALE_CONFIG } from "${localeImportPath}";\n`
|
|
366
|
+
: "";
|
|
367
|
+
|
|
368
|
+
const localeProps =
|
|
369
|
+
page.section === "public" && isServerComponent
|
|
370
|
+
? "localeConfig={LOCALE_CONFIG}"
|
|
371
|
+
: "";
|
|
372
|
+
|
|
373
|
+
const allComponentProps = [componentProps, localeProps]
|
|
374
|
+
.filter(Boolean)
|
|
375
|
+
.join(" ");
|
|
376
|
+
|
|
377
|
+
// Pour les pages publiques avec metadataExport, créer une fonction wrapper qui passe LOCALE_CONFIG
|
|
378
|
+
// On utilise un alias pour l'import afin d'éviter le conflit de nom avec la fonction exportée
|
|
379
|
+
const metadataImportAlias = page.metadataExport
|
|
380
|
+
? `${page.metadataExport} as ${page.metadataExport}Original`
|
|
381
|
+
: "";
|
|
382
|
+
const metadataExport =
|
|
383
|
+
page.section === "public" && page.metadataExport && localeImport
|
|
384
|
+
? `\nexport async function generateMetadata(props: any) {
|
|
385
|
+
return ${page.metadataExport}Original({ ...props, localeConfig: LOCALE_CONFIG });
|
|
386
|
+
}`
|
|
387
|
+
: page.metadataExport
|
|
388
|
+
? `\nexport { ${page.metadataExport}Original as generateMetadata };`
|
|
389
|
+
: "";
|
|
390
|
+
|
|
348
391
|
content = `// GENERATED BY LASTBRAIN MODULE BUILD
|
|
349
|
-
import { ${page.componentExport} } from "${importPath}"
|
|
350
|
-
${page.metadataExport ? `\nexport { ${page.metadataExport} as generateMetadata } from "${importPath}";\n` : ""}
|
|
392
|
+
import { ${page.componentExport}${page.metadataExport ? `, ${metadataImportAlias}` : ""} } from "${importPath}";${localeImport}${metadataExport}
|
|
351
393
|
export default ${hasDynamicParams ? "async " : ""}function ${wrapperName}${propsSignature} {
|
|
352
|
-
${awaitParams}return <${page.componentExport} ${
|
|
394
|
+
${awaitParams}return <${page.componentExport} ${allComponentProps} />;
|
|
353
395
|
}
|
|
354
396
|
`;
|
|
355
397
|
}
|
|
@@ -511,6 +553,7 @@ export interface MenuItem {
|
|
|
511
553
|
component?: React.ComponentType<any>;
|
|
512
554
|
componentExport?: string;
|
|
513
555
|
entryPoint?: string;
|
|
556
|
+
badge?: string;
|
|
514
557
|
}
|
|
515
558
|
|
|
516
559
|
export interface MenuConfig {
|
|
@@ -1004,10 +1047,12 @@ export function ClientLayout({
|
|
|
1004
1047
|
children,
|
|
1005
1048
|
lang = "fr",
|
|
1006
1049
|
translations = {},
|
|
1050
|
+
availableLanguages = ["fr", "en"],
|
|
1007
1051
|
}: {
|
|
1008
1052
|
children: ReactNode;
|
|
1009
1053
|
lang?: Language;
|
|
1010
1054
|
translations?: Record<string, string>;
|
|
1055
|
+
availableLanguages?: string[];
|
|
1011
1056
|
}) {
|
|
1012
1057
|
const router = useLocalizedRouter();
|
|
1013
1058
|
|
|
@@ -1019,7 +1064,7 @@ export function ClientLayout({
|
|
|
1019
1064
|
enableSystem={false}
|
|
1020
1065
|
storageKey="lastbrain-theme"
|
|
1021
1066
|
>
|
|
1022
|
-
<AppProviders lang={lang} translations={translations}>
|
|
1067
|
+
<AppProviders lang={lang} translations={translations} availableLanguages={availableLanguages}>
|
|
1023
1068
|
<AppHeader />
|
|
1024
1069
|
<div className="min-h-screen text-foreground bg-background">
|
|
1025
1070
|
{children}
|
|
@@ -1057,16 +1102,19 @@ export function AppProviders({
|
|
|
1057
1102
|
children,
|
|
1058
1103
|
lang = "fr",
|
|
1059
1104
|
translations = {},
|
|
1105
|
+
availableLanguages = ["fr", "en"],
|
|
1060
1106
|
}: {
|
|
1061
1107
|
children: ReactNode;
|
|
1062
1108
|
lang?: Language;
|
|
1063
1109
|
translations?: Record<string, string>;
|
|
1110
|
+
availableLanguages?: string[];
|
|
1064
1111
|
}) {
|
|
1065
1112
|
return (
|
|
1066
1113
|
<BaseAppProviders
|
|
1067
1114
|
realtimeConfig={realtimeConfig}
|
|
1068
1115
|
lang={lang}
|
|
1069
1116
|
translations={translations}
|
|
1117
|
+
availableLanguages={availableLanguages}
|
|
1070
1118
|
>
|
|
1071
1119
|
{children}
|
|
1072
1120
|
</BaseAppProviders>
|
|
@@ -1090,12 +1138,19 @@ export function AppProviders({
|
|
|
1090
1138
|
import { Header } from "@lastbrain/ui";
|
|
1091
1139
|
import { LanguageSwitcher } from "@lastbrain/app";
|
|
1092
1140
|
import { menuConfig } from "../../config/menu";
|
|
1141
|
+
import { LOCALE_CONFIG } from "../../config/locales.generated";
|
|
1093
1142
|
|
|
1094
1143
|
export function AppHeader() {
|
|
1095
1144
|
return (
|
|
1096
1145
|
<Header
|
|
1097
1146
|
menuConfig={menuConfig}
|
|
1098
|
-
languageSwitcher={
|
|
1147
|
+
languageSwitcher={
|
|
1148
|
+
<LanguageSwitcher
|
|
1149
|
+
variant="minimal"
|
|
1150
|
+
availableLanguages={[...LOCALE_CONFIG.languages]}
|
|
1151
|
+
localeMap={LOCALE_CONFIG.localeMap}
|
|
1152
|
+
/>
|
|
1153
|
+
}
|
|
1099
1154
|
/>
|
|
1100
1155
|
);
|
|
1101
1156
|
}`;
|
|
@@ -1112,6 +1167,27 @@ export function AppHeader() {
|
|
|
1112
1167
|
const langLayoutContent = `import { loadTranslations } from "@lastbrain/app/i18n/server-lang";
|
|
1113
1168
|
import { ClientLayout } from "../../components/ClientLayout";
|
|
1114
1169
|
|
|
1170
|
+
// Fallback default config in case locales.generated.ts is not available
|
|
1171
|
+
const DEFAULT_LOCALE_CONFIG = {
|
|
1172
|
+
languages: ["fr", "en"] as const,
|
|
1173
|
+
locales: ["fr_FR", "en_US"],
|
|
1174
|
+
localeMap: { fr: "fr_FR", en: "en_US" } as Record<string, string>,
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1177
|
+
// Type for valid languages derived from config
|
|
1178
|
+
type ValidLanguage = (typeof DEFAULT_LOCALE_CONFIG.languages)[number];
|
|
1179
|
+
|
|
1180
|
+
// Helper function to safely load locale config
|
|
1181
|
+
async function getLocaleConfig() {
|
|
1182
|
+
|
|
1183
|
+
const importedConfig = await import("../../config/locales.generated");
|
|
1184
|
+
if (importedConfig?.LOCALE_CONFIG && importedConfig.LOCALE_CONFIG.languages) {
|
|
1185
|
+
return importedConfig.LOCALE_CONFIG;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
return DEFAULT_LOCALE_CONFIG;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1115
1191
|
export default async function LangLayout({
|
|
1116
1192
|
children,
|
|
1117
1193
|
params,
|
|
@@ -1120,13 +1196,22 @@ export default async function LangLayout({
|
|
|
1120
1196
|
params: Promise<{ lang: string }>;
|
|
1121
1197
|
}) {
|
|
1122
1198
|
const { lang } = await params;
|
|
1123
|
-
|
|
1199
|
+
|
|
1200
|
+
// Get the locale config (either from app or fallback)
|
|
1201
|
+
const LOCALE_CONFIG = await getLocaleConfig();
|
|
1202
|
+
|
|
1203
|
+
// Validate lang and cast to ValidLanguage type
|
|
1204
|
+
const isValidLang = (val: string): val is ValidLanguage => {
|
|
1205
|
+
return LOCALE_CONFIG.languages.includes(val as any);
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
const validLang: ValidLanguage = isValidLang(lang) ? lang : "fr";
|
|
1124
1209
|
|
|
1125
1210
|
// Charger les traductions pour cette langue
|
|
1126
1211
|
const translations = await loadTranslations(validLang);
|
|
1127
1212
|
|
|
1128
1213
|
return (
|
|
1129
|
-
<ClientLayout lang={validLang} translations={translations}>
|
|
1214
|
+
<ClientLayout lang={validLang} translations={translations} availableLanguages={[...LOCALE_CONFIG.languages]}>
|
|
1130
1215
|
<div className="min-h-screen pt-16">
|
|
1131
1216
|
{children}
|
|
1132
1217
|
</div>
|
|
@@ -1379,6 +1464,157 @@ async function generateUserTabsConfig(moduleConfigs: ModuleBuildConfig[]) {
|
|
|
1379
1464
|
}
|
|
1380
1465
|
}
|
|
1381
1466
|
|
|
1467
|
+
async function generateAuthDashboard(moduleConfigs: ModuleBuildConfig[]) {
|
|
1468
|
+
try {
|
|
1469
|
+
const dashboards = moduleConfigs
|
|
1470
|
+
.flatMap((config) =>
|
|
1471
|
+
(config.authDashboard ?? []).map((dashboard) => ({
|
|
1472
|
+
...dashboard,
|
|
1473
|
+
moduleName: config.moduleName,
|
|
1474
|
+
}))
|
|
1475
|
+
)
|
|
1476
|
+
.sort((a, b) => (a.order ?? 999) - (b.order ?? 999));
|
|
1477
|
+
|
|
1478
|
+
const timestamp = new Date().toISOString();
|
|
1479
|
+
|
|
1480
|
+
let configContent: string;
|
|
1481
|
+
|
|
1482
|
+
if (dashboards.length === 0) {
|
|
1483
|
+
configContent = `// GENERATED FILE - DO NOT EDIT MANUALLY
|
|
1484
|
+
// Auth dashboard configuration
|
|
1485
|
+
// Generated at: ${timestamp}
|
|
1486
|
+
|
|
1487
|
+
"use client";
|
|
1488
|
+
|
|
1489
|
+
import type React from "react";
|
|
1490
|
+
|
|
1491
|
+
export interface ModuleAuthDashboard {
|
|
1492
|
+
key: string;
|
|
1493
|
+
title: string;
|
|
1494
|
+
icon?: string;
|
|
1495
|
+
order?: number;
|
|
1496
|
+
component: React.ComponentType<Record<string, never>>;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
export const moduleAuthDashboards: ModuleAuthDashboard[] = [];
|
|
1500
|
+
|
|
1501
|
+
export default moduleAuthDashboards;
|
|
1502
|
+
`;
|
|
1503
|
+
} else {
|
|
1504
|
+
const dynamicImports = dashboards
|
|
1505
|
+
.map(
|
|
1506
|
+
(dashboard, index) =>
|
|
1507
|
+
`const AuthDashboardComponent${index} = dynamic(() => import("${dashboard.moduleName}").then(mod => ({ default: (mod as any)["${dashboard.componentExport}"] })), { ssr: true });`
|
|
1508
|
+
)
|
|
1509
|
+
.join("\n");
|
|
1510
|
+
|
|
1511
|
+
const dashboardsArray = dashboards
|
|
1512
|
+
.map(
|
|
1513
|
+
(dashboard, index) => ` {
|
|
1514
|
+
key: "${dashboard.key}",
|
|
1515
|
+
title: "${dashboard.title}",
|
|
1516
|
+
icon: "${dashboard.icon ?? ""}",
|
|
1517
|
+
order: ${dashboard.order ?? 999},
|
|
1518
|
+
component: AuthDashboardComponent${index},
|
|
1519
|
+
}`
|
|
1520
|
+
)
|
|
1521
|
+
.join(",\n");
|
|
1522
|
+
|
|
1523
|
+
configContent = `// GENERATED FILE - DO NOT EDIT MANUALLY
|
|
1524
|
+
// Auth dashboard configuration
|
|
1525
|
+
// Generated at: ${timestamp}
|
|
1526
|
+
|
|
1527
|
+
"use client";
|
|
1528
|
+
|
|
1529
|
+
import dynamic from "next/dynamic";
|
|
1530
|
+
import type React from "react";
|
|
1531
|
+
|
|
1532
|
+
${dynamicImports}
|
|
1533
|
+
|
|
1534
|
+
export interface ModuleAuthDashboard {
|
|
1535
|
+
key: string;
|
|
1536
|
+
title: string;
|
|
1537
|
+
icon?: string;
|
|
1538
|
+
order?: number;
|
|
1539
|
+
component: React.ComponentType<Record<string, never>>;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
export const moduleAuthDashboards: ModuleAuthDashboard[] = [
|
|
1543
|
+
${dashboardsArray}
|
|
1544
|
+
];
|
|
1545
|
+
|
|
1546
|
+
export default moduleAuthDashboards;
|
|
1547
|
+
`;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
const configPath = path.join(projectRoot, "config", "auth-dashboard.ts");
|
|
1551
|
+
ensureDirectory(path.dirname(configPath));
|
|
1552
|
+
fs.writeFileSync(configPath, configContent);
|
|
1553
|
+
|
|
1554
|
+
if (isDebugMode) {
|
|
1555
|
+
console.log(
|
|
1556
|
+
`✅ Generated auth dashboard configuration: ${configPath} (${dashboards.length} item(s))`
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
const pagePath = path.join(
|
|
1561
|
+
appDirectory,
|
|
1562
|
+
"[lang]",
|
|
1563
|
+
"auth",
|
|
1564
|
+
"dashboard",
|
|
1565
|
+
"page.tsx"
|
|
1566
|
+
);
|
|
1567
|
+
|
|
1568
|
+
ensureDirectory(path.dirname(pagePath));
|
|
1569
|
+
|
|
1570
|
+
const pageContent = `// GENERATED BY LASTBRAIN MODULE BUILD
|
|
1571
|
+
"use client";
|
|
1572
|
+
|
|
1573
|
+
import { Suspense } from "react";
|
|
1574
|
+
import { moduleAuthDashboards } from "../../../../config/auth-dashboard";
|
|
1575
|
+
|
|
1576
|
+
export const dynamic = "force-dynamic";
|
|
1577
|
+
|
|
1578
|
+
|
|
1579
|
+
export default function AuthDashboardPage() {
|
|
1580
|
+
const dashboards = moduleAuthDashboards || [];
|
|
1581
|
+
|
|
1582
|
+
if (dashboards.length === 0) {
|
|
1583
|
+
return <div className="space-y-4">Aucun dashboard disponible.</div>;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
return (
|
|
1587
|
+
<div className="space-y-6">
|
|
1588
|
+
{dashboards.map((dashboard) => {
|
|
1589
|
+
|
|
1590
|
+
const Component = dashboard.component;
|
|
1591
|
+
|
|
1592
|
+
return (
|
|
1593
|
+
<section
|
|
1594
|
+
key={dashboard.key}
|
|
1595
|
+
|
|
1596
|
+
>
|
|
1597
|
+
<Suspense fallback={<div>Chargement...</div>}>
|
|
1598
|
+
<Component />
|
|
1599
|
+
</Suspense>
|
|
1600
|
+
</section>
|
|
1601
|
+
);
|
|
1602
|
+
})}
|
|
1603
|
+
</div>
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
`;
|
|
1607
|
+
|
|
1608
|
+
fs.writeFileSync(pagePath, pageContent);
|
|
1609
|
+
|
|
1610
|
+
if (isDebugMode) {
|
|
1611
|
+
console.log(`🧭 Generated auth dashboard page: ${pagePath}`);
|
|
1612
|
+
}
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
console.error("❌ Error generating auth dashboard configuration:", error);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1382
1618
|
async function generateBucketsConfig(moduleConfigs: ModuleBuildConfig[]) {
|
|
1383
1619
|
try {
|
|
1384
1620
|
// Extraire les configurations storage des modules
|
|
@@ -1602,9 +1838,9 @@ export async function GET(
|
|
|
1602
1838
|
// c'est une image avec format court qui nécessite le préfixe userId
|
|
1603
1839
|
let actualStoragePath = storagePath;
|
|
1604
1840
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1841
|
+
|
|
1842
|
+
actualStoragePath = \`\${user.id}/\${storagePath}\`;
|
|
1843
|
+
|
|
1608
1844
|
|
|
1609
1845
|
// Vérifier que l'utilisateur a accès à cette image
|
|
1610
1846
|
// Format: {userId}/recipe/{recipeId}/{filename} ou {userId}/product/{productId}/{filename}
|
|
@@ -1700,8 +1936,6 @@ async function generateFooterConfig(moduleConfigs: ModuleBuildConfig[]) {
|
|
|
1700
1936
|
import type { FooterConfig } from "@lastbrain/ui";
|
|
1701
1937
|
|
|
1702
1938
|
export const footerConfig: FooterConfig = {
|
|
1703
|
-
companyName: "LastBrain",
|
|
1704
|
-
companyDescription: "Plateforme de développement rapide d'applications",
|
|
1705
1939
|
links: ${JSON.stringify(allFooterLinks, null, 2)},
|
|
1706
1940
|
social: [],
|
|
1707
1941
|
};
|
|
@@ -1724,9 +1958,129 @@ export const footerConfig: FooterConfig = {
|
|
|
1724
1958
|
|
|
1725
1959
|
/**
|
|
1726
1960
|
* Génère les fichiers i18n en concaténant les traductions des modules
|
|
1961
|
+
* et en incluant les traductions personnalisées de i18n/default
|
|
1962
|
+
*/
|
|
1963
|
+
async function generateLocaleConfig() {
|
|
1964
|
+
const appI18nDir = path.join(projectRoot, "i18n");
|
|
1965
|
+
const appConfigDir = path.join(projectRoot, "config");
|
|
1966
|
+
|
|
1967
|
+
// Mapping standard des codes de langue vers les locales
|
|
1968
|
+
const LOCALE_MAP: Record<string, string> = {
|
|
1969
|
+
fr: "fr_FR",
|
|
1970
|
+
en: "en_US",
|
|
1971
|
+
es: "es_ES",
|
|
1972
|
+
de: "de_DE",
|
|
1973
|
+
it: "it_IT",
|
|
1974
|
+
pt: "pt_PT",
|
|
1975
|
+
nl: "nl_NL",
|
|
1976
|
+
ru: "ru_RU",
|
|
1977
|
+
ja: "ja_JP",
|
|
1978
|
+
zh: "zh_CN",
|
|
1979
|
+
ar: "ar_SA",
|
|
1980
|
+
hi: "hi_IN",
|
|
1981
|
+
};
|
|
1982
|
+
|
|
1983
|
+
try {
|
|
1984
|
+
// Scanner les fichiers i18n disponibles
|
|
1985
|
+
let languages: string[] = ["fr"]; // Fallback par défaut
|
|
1986
|
+
|
|
1987
|
+
if (fs.existsSync(appI18nDir)) {
|
|
1988
|
+
const files = fs.readdirSync(appI18nDir);
|
|
1989
|
+
languages = files
|
|
1990
|
+
.filter((f) => f.endsWith(".json"))
|
|
1991
|
+
.map((f) => f.replace(".json", ""))
|
|
1992
|
+
.filter((lang) => LOCALE_MAP[lang])
|
|
1993
|
+
.sort();
|
|
1994
|
+
|
|
1995
|
+
if (isDebugMode) {
|
|
1996
|
+
console.log(` 📋 Detected languages: ${languages.join(", ")}`);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
const locales = languages.map((lang) => LOCALE_MAP[lang]);
|
|
2001
|
+
const localeMap = languages.reduce(
|
|
2002
|
+
(acc, lang) => {
|
|
2003
|
+
acc[lang] = LOCALE_MAP[lang];
|
|
2004
|
+
return acc;
|
|
2005
|
+
},
|
|
2006
|
+
{} as Record<string, string>
|
|
2007
|
+
);
|
|
2008
|
+
|
|
2009
|
+
// Générer le fichier TypeScript dans l'app (ex: apps/recipe/config/)
|
|
2010
|
+
ensureDirectory(appConfigDir);
|
|
2011
|
+
|
|
2012
|
+
// Générer le fichier dans config/
|
|
2013
|
+
const outputPath = path.join(appConfigDir, "locales.generated.ts");
|
|
2014
|
+
const content = `// This file is auto-generated by module-build.ts
|
|
2015
|
+
// Do not edit manually - regenerate with 'pnpm build:modules'
|
|
2016
|
+
|
|
2017
|
+
export interface LocaleConfig {
|
|
2018
|
+
languages: string[];
|
|
2019
|
+
locales: string[];
|
|
2020
|
+
localeMap: Record<string, string>;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
export const LOCALE_CONFIG: LocaleConfig = ${JSON.stringify(
|
|
2024
|
+
{
|
|
2025
|
+
languages,
|
|
2026
|
+
locales,
|
|
2027
|
+
localeMap,
|
|
2028
|
+
},
|
|
2029
|
+
null,
|
|
2030
|
+
2
|
|
2031
|
+
)} as const;
|
|
2032
|
+
|
|
2033
|
+
/**
|
|
2034
|
+
* Convert language code to locale (e.g., "fr" -> "fr_FR")
|
|
1727
2035
|
*/
|
|
2036
|
+
export function langToLocale(lang: string): string {
|
|
2037
|
+
return LOCALE_CONFIG.localeMap[lang] || "fr_FR";
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
/**
|
|
2041
|
+
* Get all locales except the given one
|
|
2042
|
+
*/
|
|
2043
|
+
export function getAlternateLocales(lang: string): string[] {
|
|
2044
|
+
const currentLocale = LOCALE_CONFIG.localeMap[lang] || "fr_FR";
|
|
2045
|
+
return LOCALE_CONFIG.locales.filter((l) => l !== currentLocale);
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
/**
|
|
2049
|
+
* Get locale map for metadata generation
|
|
2050
|
+
*/
|
|
2051
|
+
export function getLocaleMap(): Record<string, string> {
|
|
2052
|
+
return LOCALE_CONFIG.localeMap;
|
|
2053
|
+
}
|
|
2054
|
+
`;
|
|
2055
|
+
|
|
2056
|
+
writeScaffoldFile(outputPath, content, "locale configuration");
|
|
2057
|
+
|
|
2058
|
+
// Générer aussi un fichier de ré-export dans le répertoire racine de l'app pour faciliter l'import
|
|
2059
|
+
const rootExportPath = path.join(projectRoot, "locale-helpers.ts");
|
|
2060
|
+
const rootExportContent = `// Re-export locale helpers from config for easy import in packages
|
|
2061
|
+
export { langToLocale, getAlternateLocales, getLocaleMap, LOCALE_CONFIG } from "./config/locales.generated";
|
|
2062
|
+
export type { LocaleConfig } from "./config/locales.generated";
|
|
2063
|
+
`;
|
|
2064
|
+
|
|
2065
|
+
writeScaffoldFile(
|
|
2066
|
+
rootExportPath,
|
|
2067
|
+
rootExportContent,
|
|
2068
|
+
"locale helpers re-export"
|
|
2069
|
+
);
|
|
2070
|
+
|
|
2071
|
+
if (isDebugMode) {
|
|
2072
|
+
console.log(
|
|
2073
|
+
` ✅ Generated locale config with ${languages.length} language(s)`
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
} catch (error) {
|
|
2077
|
+
console.error("❌ Error generating locale config:", error);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
|
|
1728
2081
|
async function generateI18nFiles() {
|
|
1729
2082
|
const appI18nDir = path.join(projectRoot, "i18n");
|
|
2083
|
+
const appI18nDefaultDir = path.join(projectRoot, "i18n", "default");
|
|
1730
2084
|
const packagesDir = path.join(_monorepoRoot, "packages");
|
|
1731
2085
|
|
|
1732
2086
|
interface TranslationMap {
|
|
@@ -1789,6 +2143,41 @@ async function generateI18nFiles() {
|
|
|
1789
2143
|
}
|
|
1790
2144
|
}
|
|
1791
2145
|
|
|
2146
|
+
// Charger les traductions personnalisées de i18n/default
|
|
2147
|
+
if (fs.existsSync(appI18nDefaultDir)) {
|
|
2148
|
+
// Découvrir dynamiquement les fichiers de langue disponibles
|
|
2149
|
+
const defaultFiles = fs
|
|
2150
|
+
.readdirSync(appI18nDefaultDir)
|
|
2151
|
+
.filter((file) => file.endsWith(".json"));
|
|
2152
|
+
|
|
2153
|
+
for (const defaultFile of defaultFiles) {
|
|
2154
|
+
const defaultFilePath = path.join(appI18nDefaultDir, defaultFile);
|
|
2155
|
+
|
|
2156
|
+
if (fs.existsSync(defaultFilePath)) {
|
|
2157
|
+
const lang = defaultFile.replace(".json", "");
|
|
2158
|
+
|
|
2159
|
+
if (!translations[lang]) {
|
|
2160
|
+
translations[lang] = {};
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
try {
|
|
2164
|
+
const content = JSON.parse(
|
|
2165
|
+
fs.readFileSync(defaultFilePath, "utf-8")
|
|
2166
|
+
);
|
|
2167
|
+
|
|
2168
|
+
if (isDebugMode) {
|
|
2169
|
+
console.log(` ✓ app defaults (${lang})`);
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// Ajouter ou surcharger les traductions personnalisées
|
|
2173
|
+
Object.assign(translations[lang], content);
|
|
2174
|
+
} catch (error) {
|
|
2175
|
+
console.warn(` ⚠️ Error reading ${defaultFilePath}:`, error);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
|
|
1792
2181
|
// Créer le dossier i18n s'il n'existe pas
|
|
1793
2182
|
ensureDirectory(appI18nDir);
|
|
1794
2183
|
|
|
@@ -1820,6 +2209,77 @@ function extractModuleNameFromPath(filePath: string): string {
|
|
|
1820
2209
|
return "unknown";
|
|
1821
2210
|
}
|
|
1822
2211
|
|
|
2212
|
+
/**
|
|
2213
|
+
* Génère la page d'accueil app/[lang]/page.tsx si un module définit path: "/"
|
|
2214
|
+
*/
|
|
2215
|
+
function generateHomePage(moduleConfigs: ModuleBuildConfig[]) {
|
|
2216
|
+
// Chercher une page avec section: "public" et path: "/"
|
|
2217
|
+
let homePageModule: ModuleBuildConfig | null = null;
|
|
2218
|
+
let homePageConfig: ModulePageConfig | null = null;
|
|
2219
|
+
|
|
2220
|
+
for (const moduleConfig of moduleConfigs) {
|
|
2221
|
+
const homePage = moduleConfig.pages.find(
|
|
2222
|
+
(page) => page.section === "public" && page.path === "/"
|
|
2223
|
+
);
|
|
2224
|
+
if (homePage) {
|
|
2225
|
+
homePageModule = moduleConfig;
|
|
2226
|
+
homePageConfig = homePage;
|
|
2227
|
+
break;
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
if (!homePageModule || !homePageConfig) {
|
|
2232
|
+
if (isDebugMode) {
|
|
2233
|
+
console.log(" ℹ️ No homepage (/) definition found in modules");
|
|
2234
|
+
}
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
// Générer la home dans le segment public: app/[lang]/(public)/page.tsx
|
|
2239
|
+
const homePageDir = path.join(appDirectory, "[lang]", "(public)");
|
|
2240
|
+
const homePagePath = path.join(homePageDir, "page.tsx");
|
|
2241
|
+
ensureDirectory(homePageDir);
|
|
2242
|
+
|
|
2243
|
+
// Déterminer le chemin d'import
|
|
2244
|
+
const importPath = homePageConfig.entryPoint
|
|
2245
|
+
? `${homePageModule.moduleName}/${homePageConfig.entryPoint}`
|
|
2246
|
+
: homePageModule.moduleName;
|
|
2247
|
+
|
|
2248
|
+
// Éviter les collisions de nom (HomePage importé et wrapper exporté)
|
|
2249
|
+
const importComponentName = homePageConfig.componentExport;
|
|
2250
|
+
const wrapperComponentName = `${importComponentName}AppWrapper`;
|
|
2251
|
+
const metadataImportAlias = homePageConfig.metadataExport
|
|
2252
|
+
? `${homePageConfig.metadataExport} as ${homePageConfig.metadataExport}Original`
|
|
2253
|
+
: "";
|
|
2254
|
+
|
|
2255
|
+
const content = `// GENERATED BY LASTBRAIN MODULE BUILD
|
|
2256
|
+
// Homepage from ${homePageModule.moduleName}
|
|
2257
|
+
import { ${importComponentName} as ModuleHomePage${homePageConfig.metadataExport ? `, ${metadataImportAlias}` : ""} } from "${importPath}";
|
|
2258
|
+
import { footerConfig } from "../../../config/footer";
|
|
2259
|
+
import { LOCALE_CONFIG } from "../../../config/locales.generated";
|
|
2260
|
+
${
|
|
2261
|
+
homePageConfig.metadataExport
|
|
2262
|
+
? `\nexport async function generateMetadata(props: any) {
|
|
2263
|
+
return ${homePageConfig.metadataExport}Original({ ...props, localeConfig: LOCALE_CONFIG });
|
|
2264
|
+
}`
|
|
2265
|
+
: ""
|
|
2266
|
+
}
|
|
2267
|
+
export default function ${wrapperComponentName}() {
|
|
2268
|
+
return <ModuleHomePage footerConfig={footerConfig} localeConfig={LOCALE_CONFIG} />;
|
|
2269
|
+
}
|
|
2270
|
+
`;
|
|
2271
|
+
|
|
2272
|
+
fs.writeFileSync(homePagePath, content, "utf-8");
|
|
2273
|
+
|
|
2274
|
+
if (isDebugMode) {
|
|
2275
|
+
console.log(
|
|
2276
|
+
` ✅ Generated homepage: ${homePagePath} (from ${homePageModule.moduleName})`
|
|
2277
|
+
);
|
|
2278
|
+
} else {
|
|
2279
|
+
console.log(`🏠 Generated homepage from ${homePageModule.moduleName}`);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
|
|
1823
2283
|
export async function runModuleBuild() {
|
|
1824
2284
|
ensureDirectory(appDirectory);
|
|
1825
2285
|
|
|
@@ -1886,6 +2346,12 @@ export async function runModuleBuild() {
|
|
|
1886
2346
|
generateLayouts();
|
|
1887
2347
|
copyModuleMigrations(moduleConfigs);
|
|
1888
2348
|
|
|
2349
|
+
// Générer la page d'accueil "/" si un module la définit
|
|
2350
|
+
if (isDebugMode) {
|
|
2351
|
+
console.log("🏠 Checking for homepage (/) definition...");
|
|
2352
|
+
}
|
|
2353
|
+
generateHomePage(moduleConfigs);
|
|
2354
|
+
|
|
1889
2355
|
// Générer la configuration realtime
|
|
1890
2356
|
if (isDebugMode) {
|
|
1891
2357
|
console.log("🔄 Generating realtime configuration...");
|
|
@@ -1898,6 +2364,12 @@ export async function runModuleBuild() {
|
|
|
1898
2364
|
}
|
|
1899
2365
|
await generateUserTabsConfig(moduleConfigs);
|
|
1900
2366
|
|
|
2367
|
+
// Générer la configuration et la page auth dashboard
|
|
2368
|
+
if (isDebugMode) {
|
|
2369
|
+
console.log("📊 Generating auth dashboard configuration...");
|
|
2370
|
+
}
|
|
2371
|
+
await generateAuthDashboard(moduleConfigs);
|
|
2372
|
+
|
|
1901
2373
|
// Générer la configuration des buckets storage
|
|
1902
2374
|
if (isDebugMode) {
|
|
1903
2375
|
console.log("🗄️ Generating storage buckets configuration...");
|
|
@@ -1922,6 +2394,12 @@ export async function runModuleBuild() {
|
|
|
1922
2394
|
}
|
|
1923
2395
|
await generateI18nFiles();
|
|
1924
2396
|
|
|
2397
|
+
// Générer la configuration des locales
|
|
2398
|
+
if (isDebugMode) {
|
|
2399
|
+
console.log("🌐 Generating locale configuration...");
|
|
2400
|
+
}
|
|
2401
|
+
await generateLocaleConfig();
|
|
2402
|
+
|
|
1925
2403
|
// Message de succès final
|
|
1926
2404
|
if (!isDebugMode) {
|
|
1927
2405
|
console.log("\n✅ Module build completed successfully!");
|