// The labels each component takes, built from a message lookup. Every key this module's pages use // is named here and nowhere else, so `__tests__/i18n.test.ts` can run these builders against the // catalogues and fail on a key either language lacks. import { KNOWLEDGE_REFUSALS, type KnowledgeRefusal } from "../contract/index.js" export type Translate = (key: string, values?: Record) => string /** A typed refusal as a page or a form shows it. */ export interface RefusalNotice { readonly code: KnowledgeRefusal readonly text: string } export type RefusalLabels = Readonly> export const refusalLabels = (t: Translate): RefusalLabels => Object.fromEntries(KNOWLEDGE_REFUSALS.map((code) => [code, t(`refusals.${code}`)])) as RefusalLabels /** The typed refusal a procedure answered, or undefined for any other error (which the form words itself). */ export function refusalOf(labels: RefusalLabels, error: unknown): RefusalNotice | undefined { const code = (error as { problem?: { code?: unknown } } | null)?.problem?.code return typeof code === "string" && code in labels ? { code: code as KnowledgeRefusal, text: labels[code as KnowledgeRefusal] } : undefined } /** The words for a failure a document row records: the file's own, or the service's refusal; else the code. */ export function failureText(t: Translate, code: string): string { return failureLabels(t)[code] ?? code } /** The failures a document row can name with words of their own: the file's, and the service's refusals. */ const FILE_FAILURES = ["file_changed", "not_text", "not_indexable", "too_large"] as const /** The words of each failure `failureText` knows, by code, for a page that words a live row in the browser. */ export const failureLabels = (t: Translate): Readonly> => Object.fromEntries([ ...KNOWLEDGE_REFUSALS.map((code) => [code, t(`refusals.${code}`)]), ...FILE_FAILURES.map((code) => [code, t(`failures.${code}`)]), ]) /** The status shape the labels render, declared here: ui reads no core/. */ interface StatusCounts { documents: { queued: number; indexing: number; indexed: number; failed: number; cancelled: number } } export interface StatusLabels { readonly heading: string readonly flagOff: string readonly state: string readonly reasons: string readonly indexing: string readonly documents: string } export function statusLabels(t: Translate, status: StatusCounts): StatusLabels { return { heading: t("status.heading"), flagOff: t("status.flagOff"), state: t("status.state"), reasons: t("status.reasons"), indexing: t("status.indexing"), documents: t("status.documents", status.documents), } } export const serviceStateLabel = (t: Translate, state: string): string => { switch (state) { case "ready": return t("status.stateReady") case "indexing": return t("status.stateIndexing") case "registered": return t("status.stateRegistered") case "degraded": return t("status.stateDegraded") case "disabled": return t("status.stateDisabled") default: return t("status.stateUnavailable") } } export interface SearchFormLabels { readonly heading: string readonly label: string readonly placeholder: string readonly submit: string readonly busy: string readonly empty: string readonly results: string readonly snippet: string readonly source: string readonly chunk: string readonly page: string readonly disclosure: string readonly refusals: RefusalLabels } export function searchFormLabels(t: Translate): SearchFormLabels { return { heading: t("search.heading"), label: t("search.label"), placeholder: t("search.placeholder"), submit: t("search.submit"), busy: t("search.busy"), empty: t("search.empty"), // Not filled in here: only the browser knows the count, and `formatMessage` brings the plural rules. results: t("search.results"), snippet: t("search.snippet"), source: t("search.source"), chunk: t("search.chunk", { chunk: "{chunk}" }), page: t("search.page", { page: "{page}" }), disclosure: t("search.disclosure"), refusals: refusalLabels(t), } }