119 lines
4.2 KiB
TypeScript
119 lines
4.2 KiB
TypeScript
// 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, string | number>) => 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<Record<KnowledgeRefusal, string>>
|
|
|
|
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<Record<string, string>> =>
|
|
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),
|
|
}
|
|
}
|