Files
HealthLog/src/lib/i18n/server-locale.ts
T
Marc-André BombeckandClaude Opus 4.7 02b99553b9 chore(style): repo-wide prettier sweep before v1.4.6 tag
Long-overdue prettier sweep; prettier had drifted vs. checked-in code
across ~130 files (mostly long-argument line-break wrapping in TS and
table-padding in markdown). Pure formatting — no semantic changes —
and required to clear the v1.4.6 release gate's `pnpm format:check`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 02:27:17 +02:00

76 lines
2.3 KiB
TypeScript

import { cookies } from "next/headers";
import type { NextRequest } from "next/server";
import { defaultLocale, locales, type Locale } from "./config";
const LOCALE_COOKIE_NAMES = ["healthlog-locale", "userLocale"] as const;
function isLocale(value: string | null | undefined): value is Locale {
return !!value && (locales as readonly string[]).includes(value);
}
function pickLocaleFromAcceptLanguage(header: string | null): Locale | null {
if (!header) return null;
// Accept-Language: "en-US,en;q=0.9,de;q=0.8"
const candidates = header
.split(",")
.map((part) => {
const [tag, qStr] = part.trim().split(";");
const q = qStr?.startsWith("q=") ? Number(qStr.slice(2)) : 1;
return {
tag: tag.trim().toLowerCase(),
q: Number.isFinite(q) ? q : 0,
};
})
.sort((a, b) => b.q - a.q);
for (const { tag } of candidates) {
const primary = tag.split("-")[0];
if (isLocale(primary)) return primary;
}
return null;
}
interface ResolveOptions {
/** If a NextRequest is available, the Accept-Language header is read from it. */
request?: NextRequest | null;
/** The user-record locale (e.g. User.locale). */
userLocale?: string | null;
/** Explicit override (e.g. ?locale= query param). */
override?: string | null;
}
/**
* Resolve the active locale for a server-side request.
*
* Priority:
* 1. Explicit override (e.g. `?locale=…` query param).
* 2. healthlog-locale / userLocale cookie (set by the client when the user
* switches language).
* 3. The User.locale column (persisted preference).
* 4. Accept-Language header.
* 5. defaultLocale (English).
*/
export async function resolveServerLocale(
options: ResolveOptions = {},
): Promise<Locale> {
if (isLocale(options.override)) return options.override;
try {
const cookieStore = await cookies();
for (const name of LOCALE_COOKIE_NAMES) {
const value = cookieStore.get(name)?.value;
if (isLocale(value)) return value;
}
} catch {
// cookies() throws outside a request context — ignore.
}
if (isLocale(options.userLocale)) return options.userLocale;
const accept = options.request?.headers.get("accept-language") ?? null;
const fromHeader = pickLocaleFromAcceptLanguage(accept);
if (fromHeader) return fromHeader;
return defaultLocale;
}