// 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 -- the page itself holds no string. // // `t` takes a key relative to the module (`status.queued`); the page binds it to // `i18n.message("documents." + key)`. The two messages that are not this module's own -- the app's // `app.failed`, and the kit's `ui.aiDisclosure` and `ui.page` -- are passed in already looked up. import { DOCUMENT_STATUSES, FAILURE_CODES, LINE_STATUSES } from "../contract/constants.js" import type { DocumentListLabels } from "./document-list.js" import type { ExportLabels } from "./export-button.js" import type { ReviewLabels } from "./line-review.js" import type { UploadLabels } from "./upload-form.js" /** `{ a: f("a"), b: f("b") }` for a list of keys, typed by the keys. */ function byKey(keys: readonly K[], f: (key: K) => string): Record { return Object.fromEntries(keys.map((key) => [key, f(key)])) as Record } export type Translate = (key: string, values?: Record) => string /** The statuses a document shows: the stored ones, and `queued` for an upload nobody has read yet. */ const LISTED_STATUSES = ["queued", ...DOCUMENT_STATUSES] as const export function uploadLabels(t: Translate, failed: string): UploadLabels { return { label: t("upload.label"), hint: t("upload.hint"), submit: t("upload.submit"), busy: t("upload.busy"), done: t("upload.done"), progress: t("upload.progress"), failed, } } export function listLabels(t: Translate): DocumentListLabels { return { caption: t("title"), empty: t("list.empty"), file: t("list.file"), status: t("list.status"), pages: t("list.pages"), pending: t("list.pending"), approved: t("list.approved"), uploaded: t("list.uploaded"), actions: t("list.actions"), // The `{filename}` placeholder is the component's to fill, so it is passed through untouched. review: t("list.review", { filename: "{filename}" }), download: t("list.download", { filename: "{filename}" }), statuses: byKey(LISTED_STATUSES, (status) => t(`status.${status}`)), failures: byKey(FAILURE_CODES, (code) => t(`failure.${code}`)), } } export function reviewLabels( t: Translate, shared: { failed: string; aiDisclosure: string; pageWord: string }, ): ReviewLabels { // A message with a `{label}` or `{page}` or `{model}` placeholder is filled per line by the // component; it is looked up with the placeholder kept in place. const keep = (key: string, name: string) => t(key, { [name]: `{${name}}` }) return { caption: t("review.tableLabel"), empty: t("review.noLines"), label: t("review.label"), value: t("review.value"), page: t("review.page"), source: t("review.source"), confidence: t("review.confidence"), decision: t("review.decision"), actions: t("review.actions"), approve: keep("review.approve", "label"), reject: keep("review.reject", "label"), edit: keep("review.edit", "label"), save: t("review.save"), cancel: t("review.cancel"), approveAll: t("review.approveAll"), needsReview: t("review.needsReview"), scannedQuote: t("review.scannedQuote"), block: keep("review.block", "id"), edited: t("review.edited"), sourceOnPage: keep("review.sourceOnPage", "page"), proposedBy: keep("review.proposedBy", "model"), aiDisclosure: shared.aiDisclosure, pageWord: shared.pageWord, states: byKey(LINE_STATUSES, (status) => t(`review.state.${status}`)), failed: shared.failed, } } export function exportLabels(t: Translate, failed: string): ExportLabels { return { export: t("review.export"), hint: t("review.exportHint"), nothing: t("review.exportNothing"), exported: t("review.exported"), failed, } }