44 lines
1.9 KiB
TypeScript
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))];
|
|
}
|