This commit is contained in:
1 parent
8efd032cc6
commit
df38dccbf1
735 files changed
+128321
-120870
No files matched your search
+89
-76
@@ -4,93 +4,106 @@ import { getRequestConfig } from "next-intl/server";
|
||||
// i18n WITHOUT routing: the locale is chosen by a `NEXT_LOCALE` cookie, then
|
||||
// the Accept-Language header, and finally English as fallback.
|
||||
export const SUPPORTED_LOCALES = [
|
||||
"en",
|
||||
"it",
|
||||
"nl",
|
||||
"de",
|
||||
"fr",
|
||||
"es",
|
||||
"pt",
|
||||
"pl",
|
||||
"sv",
|
||||
"tr",
|
||||
"ro",
|
||||
"hu",
|
||||
"cs",
|
||||
"sk",
|
||||
"da",
|
||||
"no",
|
||||
"el",
|
||||
"bg",
|
||||
"hr",
|
||||
"sr",
|
||||
"uk",
|
||||
"ru",
|
||||
"en",
|
||||
"it",
|
||||
"nl",
|
||||
"de",
|
||||
"fr",
|
||||
"es",
|
||||
"pt",
|
||||
"pl",
|
||||
"sv",
|
||||
"tr",
|
||||
"ro",
|
||||
"hu",
|
||||
"cs",
|
||||
"sk",
|
||||
"da",
|
||||
"no",
|
||||
"el",
|
||||
"bg",
|
||||
"hr",
|
||||
"sr",
|
||||
"uk",
|
||||
"ru",
|
||||
] as const;
|
||||
export type AppLocale = (typeof SUPPORTED_LOCALES)[number];
|
||||
export const DEFAULT_LOCALE: AppLocale = "en";
|
||||
|
||||
export function isSupportedLocale(value: string | undefined): value is AppLocale {
|
||||
return !!value && (SUPPORTED_LOCALES as readonly string[]).includes(value);
|
||||
export function isSupportedLocale(
|
||||
value: string | undefined,
|
||||
): value is AppLocale {
|
||||
return !!value && (SUPPORTED_LOCALES as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
function detectLocaleFromAcceptLanguage(acceptLanguage: string | null): AppLocale | null {
|
||||
if (!acceptLanguage) return null;
|
||||
const locales = acceptLanguage
|
||||
.split(",")
|
||||
.map((entry) => {
|
||||
const [tag, qPart] = entry.split(";");
|
||||
const quality = qPart ? parseFloat(qPart.replace("q=", "")) : 1;
|
||||
const lang = tag?.trim().split("-")[0] ?? "";
|
||||
return { lang, quality };
|
||||
})
|
||||
.filter((l) => l.lang)
|
||||
.sort((a, b) => b.quality - a.quality);
|
||||
function detectLocaleFromAcceptLanguage(
|
||||
acceptLanguage: string | null,
|
||||
): AppLocale | null {
|
||||
if (!acceptLanguage) return null;
|
||||
const locales = acceptLanguage
|
||||
.split(",")
|
||||
.map((entry) => {
|
||||
const [tag, qPart] = entry.split(";");
|
||||
const quality = qPart ? parseFloat(qPart.replace("q=", "")) : 1;
|
||||
const lang = tag?.trim().split("-")[0] ?? "";
|
||||
return { lang, quality };
|
||||
})
|
||||
.filter((l) => l.lang)
|
||||
.sort((a, b) => b.quality - a.quality);
|
||||
|
||||
for (const { lang } of locales) {
|
||||
if (isSupportedLocale(lang)) return lang;
|
||||
}
|
||||
return null;
|
||||
for (const { lang } of locales) {
|
||||
if (isSupportedLocale(lang)) return lang;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export default getRequestConfig(async () => {
|
||||
const store = await cookies();
|
||||
const cookieLocale = store.get("NEXT_LOCALE")?.value;
|
||||
const store = await cookies();
|
||||
const cookieLocale = store.get("NEXT_LOCALE")?.value;
|
||||
|
||||
let locale: AppLocale;
|
||||
if (isSupportedLocale(cookieLocale)) {
|
||||
locale = cookieLocale;
|
||||
} else {
|
||||
const headersList = await headers();
|
||||
const detected = detectLocaleFromAcceptLanguage(headersList.get("Accept-Language"));
|
||||
locale = detected ?? DEFAULT_LOCALE;
|
||||
}
|
||||
let locale: AppLocale;
|
||||
if (isSupportedLocale(cookieLocale)) {
|
||||
locale = cookieLocale;
|
||||
} else {
|
||||
const headersList = await headers();
|
||||
const detected = detectLocaleFromAcceptLanguage(
|
||||
headersList.get("Accept-Language"),
|
||||
);
|
||||
locale = detected ?? DEFAULT_LOCALE;
|
||||
}
|
||||
|
||||
// Safe: `locale` is validated against SUPPORTED_LOCALES above (only en/it/nl/de/fr/es).
|
||||
const messages = (await import(`../messages/${locale}.json`)).default;
|
||||
// English is the source of truth; fall back to it for any key missing from a
|
||||
// translation so the UI never shows a raw key path.
|
||||
const fallback =
|
||||
locale === DEFAULT_LOCALE ? messages : (await import(`../messages/${DEFAULT_LOCALE}.json`)).default;
|
||||
// Safe: `locale` is validated against SUPPORTED_LOCALES above (only en/it/nl/de/fr/es).
|
||||
const messages = (await import(`../messages/${locale}.json`)).default;
|
||||
// English is the source of truth; fall back to it for any key missing from a
|
||||
// translation so the UI never shows a raw key path.
|
||||
const fallback =
|
||||
locale === DEFAULT_LOCALE
|
||||
? messages
|
||||
: (await import(`../messages/${DEFAULT_LOCALE}.json`)).default;
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages,
|
||||
// Never throw on a missing message — use the English value, or a humanised
|
||||
// key as a last resort.
|
||||
getMessageFallback({ key }: { key: string }) {
|
||||
const fromEnglish = key
|
||||
.split(".")
|
||||
.reduce<unknown>(
|
||||
(o, k) => (o && typeof o === "object" ? (o as Record<string, unknown>)[k] : undefined),
|
||||
fallback,
|
||||
);
|
||||
if (typeof fromEnglish === "string") return fromEnglish;
|
||||
const seg = key.split(".").pop() ?? key;
|
||||
return seg.replace(/[._-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
},
|
||||
onError() {
|
||||
// Swallow MISSING_MESSAGE etc. — getMessageFallback already degrades.
|
||||
},
|
||||
};
|
||||
return {
|
||||
locale,
|
||||
messages,
|
||||
// Never throw on a missing message — use the English value, or a humanised
|
||||
// key as a last resort.
|
||||
getMessageFallback({ key }: { key: string }) {
|
||||
const fromEnglish = key
|
||||
.split(".")
|
||||
.reduce<unknown>(
|
||||
(o, k) =>
|
||||
o && typeof o === "object"
|
||||
? (o as Record<string, unknown>)[k]
|
||||
: undefined,
|
||||
fallback,
|
||||
);
|
||||
if (typeof fromEnglish === "string") return fromEnglish;
|
||||
const seg = key.split(".").pop() ?? key;
|
||||
return seg
|
||||
.replace(/[._-]/g, " ")
|
||||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
},
|
||||
onError() {
|
||||
// Swallow MISSING_MESSAGE etc. — getMessageFallback already degrades.
|
||||
},
|
||||
};
|
||||
});
|
||||
Reference in new issue
Block a user