Wire root layout provider stack and locale-scoped layout with i18n
Root layout wraps children with LoggerProvider, ApiClientProvider, and ErrorBoundary. Locale layout validates lang param against 9 supported languages and provides a request-scoped I18nProvider.
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@modern-js/app-tools": "2.70.8",
|
||||
"@modern-js/runtime": "^3.1.3",
|
||||
"@module-federation/enhanced": "2.3.2",
|
||||
"@module-federation/modern-js": "2.3.2",
|
||||
"@opentelemetry/api": "^1.9.1",
|
||||
|
||||
Generated
+524
-42
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
import { useState, useEffect, type ReactNode } from "react";
|
||||
import { useParams, useNavigate } from "@modern-js/runtime/router";
|
||||
import { isLanguage, type Language } from "@/i18n/resolver";
|
||||
import { createI18nInstance } from "@/i18n/config";
|
||||
import { I18nProvider } from "@/i18n/provider";
|
||||
import type i18next from "i18next";
|
||||
|
||||
/**
|
||||
* Locale-scoped layout. Validates the `lang` URL segment against the
|
||||
* 9 supported languages, creates a request-scoped i18n instance, and
|
||||
* wraps children with `<I18nProvider>`.
|
||||
*
|
||||
* Invalid `lang` values redirect to `/ru/` (default locale).
|
||||
*/
|
||||
export default function LangLayout({ children }: { children: ReactNode }): JSX.Element | null {
|
||||
const params = useParams<{ lang: string }>();
|
||||
const navigate = useNavigate();
|
||||
const lang = params.lang ?? "";
|
||||
|
||||
const [i18n, setI18n] = useState<typeof i18next | null>(null);
|
||||
const [validLang, setValidLang] = useState<Language | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLanguage(lang)) {
|
||||
void navigate("/ru/", { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
setValidLang(lang);
|
||||
|
||||
let cancelled = false;
|
||||
void createI18nInstance({ locale: lang }).then((instance) => {
|
||||
if (!cancelled) {
|
||||
setI18n(instance);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [lang, navigate]);
|
||||
|
||||
if (!validLang || !i18n) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <I18nProvider i18n={i18n}>{children}</I18nProvider>;
|
||||
}
|
||||
+28
-1
@@ -1,5 +1,32 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { ErrorBoundary } from "@/ui/errors/ErrorBoundary";
|
||||
import { LoggerProvider } from "@/observability/logger/provider";
|
||||
import { createRootLogger } from "@/observability/logger/root";
|
||||
import { ApiClientProvider } from "@/shared/api/provider";
|
||||
import { ApiClient } from "@/shared/api/client";
|
||||
|
||||
const logger = createRootLogger();
|
||||
|
||||
const apiClient = new ApiClient({
|
||||
baseUrl: process.env["API_BASE_URL"] ?? "/api",
|
||||
locale: "ru",
|
||||
logger,
|
||||
});
|
||||
|
||||
/**
|
||||
* Root layout — wraps the entire app with global providers.
|
||||
* Provider order (outermost to innermost):
|
||||
* LoggerProvider > ApiClientProvider > ErrorBoundary > children
|
||||
*
|
||||
* The locale-specific I18nProvider lives in src/routes/[lang]/layout.tsx
|
||||
* so that each language segment gets its own i18n instance.
|
||||
*/
|
||||
export default function RootLayout({ children }: { children: ReactNode }): JSX.Element {
|
||||
return <>{children}</>;
|
||||
return (
|
||||
<LoggerProvider logger={logger}>
|
||||
<ApiClientProvider client={apiClient}>
|
||||
<ErrorBoundary>{children}</ErrorBoundary>
|
||||
</ApiClientProvider>
|
||||
</LoggerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user