/** * Build-time i18n entry point. Imported from .astro frontmatter, never from an island. * * Every catalog is imported statically here, which is exactly what we want at build * time (one process renders every locale) and exactly what we do not want in the * browser. Islands import `./client` instead, which reads the one locale the page * already carries. Nothing in this file reaches a bundle: Astro frontmatter runs on the * build machine only. * * Every locale in `LOCALES` needs a line in both places below. A locale listed in the * table with no catalog here does not fail: it silently renders English under its own * `lang` and URL prefix, which is worse than failing. `test/i18n-wiring.test.mjs` * fails instead. */ import en from './en.json'; import es from './es.json'; import nl from './nl.json'; import zh from './zh.json'; import hi from './hi.json'; import ar from './ar.json'; import fr from './fr.json'; import pt from './pt.json'; import it from './it.json'; import tr from './tr.json'; import pl from './pl.json'; import uk from './uk.json'; import ro from './ro.json'; import el from './el.json'; import cs from './cs.json'; import sv from './sv.json'; import da from './da.json'; import id from './id.json'; import ur from './ur.json'; import ru from './ru.json'; import de from './de.json'; import ja from './ja.json'; import areg from './ar-eg.json'; import vi from './vi.json'; import { CLIENT_NAMESPACES, DEFAULT_LOCALE, LOCALE_CODES, isLocale, type Locale } from './config.js'; import { createTranslator, type Catalog, type Translator } from './translate.js'; export * from './config.js'; export * from './routing.js'; export type { Translator, Vars } from './translate.js'; const CATALOGS: Record = { en: en as Catalog, es: es as Catalog, nl: nl as Catalog, zh: zh as Catalog, hi: hi as Catalog, ar: ar as Catalog, fr: fr as Catalog, pt: pt as Catalog, it: it as Catalog, tr: tr as Catalog, pl: pl as Catalog, uk: uk as Catalog, ro: ro as Catalog, el: el as Catalog, cs: cs as Catalog, sv: sv as Catalog, da: da as Catalog, id: id as Catalog, ur: ur as Catalog, ru: ru as Catalog, de: de as Catalog, ja: ja as Catalog, 'ar-eg': areg as Catalog, vi: vi as Catalog, }; /** The English catalog, which is what every other locale falls back to key by key. */ export const BASE_CATALOG: Catalog = CATALOGS[DEFAULT_LOCALE]!; export function catalogFor(locale: Locale): Catalog { return CATALOGS[locale] ?? BASE_CATALOG; } const translators = new Map(); /** * The translator for one locale. Cached, because every component on every page asks * for it and there are ~180 pages. */ export function useI18n(locale: Locale): Translator { const code = isLocale(locale) ? locale : DEFAULT_LOCALE; let t = translators.get(code); if (!t) { t = createTranslator(code, catalogFor(code), BASE_CATALOG); translators.set(code, t); } return t; } /** * Which keys are missing from which locale, as the build reports them. * * English is the source of truth: a key it does not have is not a key. Exported so * both the build warning in `astro.config.mjs` and `scripts/check-i18n.mjs` answer the * question the same way. */ export function missingKeys(): Record { const expected = Object.keys(BASE_CATALOG); const out: Record = {}; for (const code of LOCALE_CODES) { if (code === DEFAULT_LOCALE) continue; const catalog = catalogFor(code); const missing = expected.filter((key) => catalog[key] === undefined); if (missing.length > 0) out[code] = missing; } return out; } /** * The subset of a catalog an island can reach, resolved against English first. * * Two things happen here that matter. Keys outside `CLIENT_NAMESPACES` are dropped, so * the Spanish home page does not inline the Dutch legal pages or its own. And every * key that survives is resolved: a key this locale is missing arrives already filled * with the English string, so the browser needs no fallback catalog and ships exactly * one language. */ export function clientCatalog(locale: Locale): Catalog { const catalog = catalogFor(locale); const out: Catalog = {}; for (const key of Object.keys(BASE_CATALOG)) { const namespace = key.split('.')[0] ?? ''; if (!(CLIENT_NAMESPACES as readonly string[]).includes(namespace)) continue; out[key] = catalog[key] ?? BASE_CATALOG[key]!; } return out; }