@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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 all three locales) 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.
|
||||
*/
|
||||
import en from './en.json';
|
||||
import es from './es.json';
|
||||
import nl from './nl.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<string, Catalog> = {
|
||||
en: en as Catalog,
|
||||
es: es as Catalog,
|
||||
nl: nl 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<string, Translator>();
|
||||
|
||||
/**
|
||||
* 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<string, string[]> {
|
||||
const expected = Object.keys(BASE_CATALOG);
|
||||
const out: Record<string, string[]> = {};
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user