Files
plainpages/src/i18n/runtime.ts
T

44 lines
1.9 KiB
TypeScript

// The loaded catalogs as the host uses them per request: resolve the locale, then hand out a
// translator for it. A plugin's translator looks in its own catalogs first, so a plugin may shadow
// any core string; both fall back to en-US per key, so an untranslated corner is English, never blank.
// Translators are memoised — catalogs are immutable after boot, so there is one per locale+plugin.
import { DEFAULT_LOCALE, type Catalog } from "./catalog.ts";
import type { LoadedI18n } from "./load.ts";
import { resolveLocale, type ResolvedLocale } from "./locale.ts";
import { createTranslator, type Translate } from "./translate.ts";
export interface ResolveRequest {
acceptLanguage?: string | undefined;
param?: string | null | undefined;
}
export interface I18n {
available: string[];
resolve(request: ResolveRequest): ResolvedLocale;
translator(locale: string, pluginId?: string): Translate;
}
export function createI18n(loaded: LoadedI18n): I18n {
const memo = new Map<string, Translate>();
return {
available: loaded.available,
resolve: ({ acceptLanguage, param }) => resolveLocale({ acceptLanguage, available: loaded.available, param }),
translator: (locale, pluginId) => {
const key = `${locale} ${pluginId ?? ""}`;
let translate = memo.get(key);
if (!translate) {
translate = createTranslator({ catalogs: chain(loaded, locale, pluginId), locale });
memo.set(key, translate);
}
return translate;
},
};
}
function chain(loaded: LoadedI18n, locale: string, pluginId: string | undefined): Catalog[] {
const plugin = pluginId === undefined ? undefined : loaded.plugins.get(pluginId);
const catalogs = [plugin?.get(locale), plugin?.get(DEFAULT_LOCALE), loaded.core.get(locale), loaded.core.get(DEFAULT_LOCALE)];
return [...new Set(catalogs.filter((catalog): catalog is Catalog => catalog !== undefined))];
}