Add i18n support: per-locale catalogs, URL-driven locale, translated core and examples
CI / full-gate (push) Successful in 2m37s

This commit is contained in:
2026-08-03 22:37:27 +02:00
parent c30cd95ebd
commit 245d1ad5b5
93 changed files with 2480 additions and 464 deletions
+6
View File
@@ -10,6 +10,12 @@ export type { RequestContext, User } from "../http/context.ts";
export type { PageChrome } from "../ui/chrome.ts";
export type { NavNode } from "../ui/nav.ts";
export { can, check, GuardError, requireSession } from "../auth/guards.ts";
// Translation: `ctx.t` and the view-level `t(...)` do the work at runtime — these are for
// authoring a plugin's own catalogs (plugins/<id>/i18n/<locale>.ts) and for building a translator
// in a unit test. `PluralMessage` types a message that varies with a count.
export { createTranslator } from "../i18n/translate.ts";
export type { Translate, TranslateVars } from "../i18n/translate.ts";
export type { Catalog, PluralMessage } from "../i18n/catalog.ts";
export { parseListQuery } from "../ui/list-query.ts";
export { paginate } from "../ui/paginate.ts";
export type { PageModel } from "../ui/paginate.ts";
+2 -1
View File
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { test, type TestContext } from "node:test";
import { fileURLToPath } from "node:url";
import { ENGLISH_LOCALS } from "../i18n/view-locals.ts";
import { renderPluginView, resolveViewPath } from "./view-resolver.ts";
const coreViewsDir = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "views");
@@ -30,7 +31,7 @@ test("renderPluginView: a (nested) view includes a core building-block partial a
);
const render = renderPluginView({ cache: false, coreViewsDir, pluginsDir });
const html = await render("demo", "sub/page", { who: "Plug" });
const html = await render("demo", "sub/page", { ...ENGLISH_LOCALS, who: "Plug" }); // the host merges these into every view's data
assert.match(html, /role="radiogroup"/); // core partial, resolved from coreViewsDir
assert.match(html, /<span class=local>Plug<\/span>/); // the plugin's own partial, with data
});