/* [RFC-0174] Binding-language policy resolver for legal documents. Pure functions that turn a page's authored `translation` frontmatter block into a render-time decision: is this the binding-language version, what is the locale's translation status, must the mandatory language notice + "unofficial translation" indicator be shown, and where does the binding document live. Do not read content or the route registry — the binding URL is injected by the caller. Do not render markup — that is @warpgogol/werkstatt-site/ui's translation-notice component. Do not translate copy — the notice component owns localized microcopy. RFC-0174: initial binding-language policy resolver. */ /** Per-locale translation status for a legal document. */ export type TranslationStatus = "official" | "unofficial" | "disabled"; export const TRANSLATION_STATUSES: readonly TranslationStatus[] = [ "official", "unofficial", "disabled", ] as const; /** * Authored `translation` block, declared once in the binding-language page file * (e.g. `pages/de/datenschutz.md`). Lives in the client-editable `pages` surface * (DNA-22) so the client toggles it without engineering. */ export interface PageTranslationPolicy { /** Legally binding language code for this page (e.g. "de"). */ binding: string; /** pageId of the binding document; defaults to the declaring page's id. */ bindingPageId?: string; /** Localized display name of the binding document, e.g. "Datenschutzerklärung". */ bindingDocLabel?: string; /** * Mandatory language notice. Default true. The validator forbids `false` while * any non-binding locale is `unofficial`. */ notice?: boolean; /** Persistent "unofficial translation" indicator. Default true. */ indicator?: boolean; /** * Per-locale status. A locale absent here that has a translation file is treated * as "unofficial". The binding language itself is always treated as official. */ locales?: Record; } /** Render-time decision for the current (lang, pageId). */ export interface ResolvedTranslationContext { /** Is the current render the binding-language version? */ isBinding: boolean; /** Effective status of the current locale. */ status: TranslationStatus; /** Binding language code (for dual-language notice copy). */ bindingLang: string; /** Localized name of the binding document for the notice link text. */ bindingDocLabel?: string; /** Absolute or localized URL of the binding document (notice link + redirect target). */ bindingUrl: string; /** Inject the mandatory language-notice banner. */ showNotice: boolean; /** Render the persistent "unofficial translation" indicator. */ showIndicator: boolean; /** * When set, this locale is `disabled` and the route should redirect to the * binding-language document instead of serving the untrusted translation. */ redirectTo?: string; } function normalizeStatus(value: unknown): TranslationStatus | undefined { return TRANSLATION_STATUSES.includes(value as TranslationStatus) ? (value as TranslationStatus) : undefined; } /** * Resolve the binding-language decision for one render. * * Returns `null` when the page declares no `translation.binding` (i.e. it is not a * legal page under policy) — callers then render normally with no notice. * * @param resolveBindingUrl Callback that maps (bindingPageId, bindingLang) to the * binding document's URL. Injected so this module stays free of the route * registry / content dependencies. */ export function resolveTranslationContext(args: { lang: string; pageId: string; policy: PageTranslationPolicy | undefined | null; resolveBindingUrl: (bindingPageId: string, bindingLang: string) => string | null; }): ResolvedTranslationContext | null { const { lang, pageId, policy, resolveBindingUrl } = args; if (!policy || typeof policy.binding !== "string" || policy.binding.length === 0) { return null; } const bindingLang = policy.binding; const bindingPageId = policy.bindingPageId ?? pageId; const isBinding = lang === bindingLang; // The binding-language render is the authoritative text — never gets a notice. const status: TranslationStatus = isBinding ? "official" : (normalizeStatus(policy.locales?.[lang]) ?? "unofficial"); const bindingUrl = resolveBindingUrl(bindingPageId, bindingLang) ?? ""; const noticeEnabled = policy.notice !== false; const indicatorEnabled = policy.indicator !== false; const showNotice = !isBinding && status === "unofficial" && noticeEnabled; const showIndicator = !isBinding && status === "unofficial" && indicatorEnabled; const redirectTo = !isBinding && status === "disabled" ? bindingUrl : undefined; return { isBinding, status, bindingLang, bindingDocLabel: policy.bindingDocLabel, bindingUrl, showNotice, showIndicator, ...(redirectTo ? { redirectTo } : {}), }; }