72 lines
2.6 KiB
TypeScript
72 lines
2.6 KiB
TypeScript
// The i18n block every rendered view receives. EJS passes a template's locals down into its
|
|
// includes, so injecting this at the top level is what lets any partial — core or plugin — call
|
|
// `t(...)` and read `locale` without its caller threading them through.
|
|
|
|
import { DEFAULT_LOCALE } from "./catalog.ts";
|
|
import { ENGLISH } from "./english.ts";
|
|
import { chosenLocale, localeHref, localeLabel, textDirection } from "./locale.ts";
|
|
import type { Translate } from "./translate.ts";
|
|
|
|
export interface LocaleChoice {
|
|
current: boolean;
|
|
href: string; // this same page in that locale
|
|
label: string; // the locale named in its own language
|
|
tag: string;
|
|
}
|
|
|
|
export interface I18nLocals {
|
|
dir: "ltr" | "rtl";
|
|
// True when switching language cannot stay on this page (its URL answers no GET, so the picker
|
|
// points elsewhere) — the page then says so, because what it leaves behind may be a one-time
|
|
// secret that cannot be shown again.
|
|
leavesPage: boolean;
|
|
locale: string;
|
|
localeHref: (href: string) => string;
|
|
// The locale to carry as a hidden field, or null when the visitor never asked for one. A GET form
|
|
// replaces the whole query string, so a link-carrying wrapper can't reach it — the form must.
|
|
localeParam: string | null;
|
|
localeSwitch: LocaleChoice[];
|
|
locales: string[];
|
|
t: Translate;
|
|
}
|
|
|
|
// Just the request fields a render needs, so this module stays a leaf of src/i18n/ rather than
|
|
// depending on the HTTP layer that calls it.
|
|
export interface I18nRequest {
|
|
locale: string;
|
|
localeHref: (href: string) => string;
|
|
locales: string[];
|
|
// Where the language picker points — "this page", except after a POST, whose URL may answer no
|
|
// GET at all; the host then resolves the nearest page that does (app.ts → switchBase).
|
|
switchBase: string;
|
|
t: Translate;
|
|
url: URL;
|
|
}
|
|
|
|
// For a render with no request behind it — a partial exercised directly, a one-off render: English,
|
|
// left-to-right, no language picker.
|
|
export const ENGLISH_LOCALS: I18nLocals = {
|
|
dir: "ltr",
|
|
leavesPage: false,
|
|
locale: DEFAULT_LOCALE,
|
|
localeHref: (href) => href,
|
|
localeParam: null,
|
|
localeSwitch: [],
|
|
locales: [DEFAULT_LOCALE],
|
|
t: ENGLISH,
|
|
};
|
|
|
|
export function i18nLocals(ctx: I18nRequest): I18nLocals {
|
|
const carried = chosenLocale(ctx);
|
|
return {
|
|
dir: textDirection(ctx.locale),
|
|
leavesPage: ctx.switchBase !== `${ctx.url.pathname}${ctx.url.search}`,
|
|
locale: ctx.locale,
|
|
localeHref: (href) => ctx.localeHref(href),
|
|
localeParam: carried,
|
|
localeSwitch: ctx.locales.map((tag) => ({ current: tag === ctx.locale, href: localeHref(ctx.switchBase, tag), label: localeLabel(tag), tag })),
|
|
locales: ctx.locales,
|
|
t: ctx.t,
|
|
};
|
|
}
|