@silgi/i18n
The first-party i18n module. Add it to modules and set its options under the i18n config key. A page's locale comes from its URL only: every page is copied per locale (about___fr at /fr/a-propos), and the cookie and Accept-Language only choose where / sends a visitor.
// silgi.config.ts
export default defineConfig({
modules: ["@silgi/i18n"],
i18n: {
locales: ["en", { code: "fr", language: "fr-FR" }, { code: "ar", language: "ar-EG" }],
defaultLocale: "en",
pages: { about: { fr: "/a-propos", ar: "/hawl" } },
},
});
default
The module, as defineModule({ meta: { name: "i18n", configKey: "i18n" } }). Its setup checks the options (a wrong value is a build error naming the key), copies the pages, adds the app plugin that provides useApp().i18n, a global route middleware, and the root redirect on GET /.
I18nOptions, LocaleInput, ResolvedI18nOptions, I18nSetup
| Key | Type | Default |
|---|---|---|
locales | (string | LocaleInput)[] | required |
defaultLocale | string | required |
strategy | "prefix_except_default" | "prefix" | "prefix_except_default" |
baseUrl | string | { runtimeConfig: string } | none |
detect.cookie | string | false | "silgi_locale" |
detect.redirect | "root" | false | "root" |
pages | Record<string, Partial<Record<string, string>>> | {} |
timeZone | string | "UTC" |
fallback | "default" | false | "default" |
LocaleInput is { code, language?, dir?, name? }. ResolvedI18nOptions is the checked form with every default filled in, and I18nSetup is (options: ResolvedI18nOptions, silgi: SilgiContext) => void | Promise<void>, the type of each of the module's internal setup steps.
LocaleInfo
One resolved locale: { code, language, dir, name }. language defaults to code, dir to the language's text direction and name to the language's own name for it ("English", "français (France)").
CurrentLocale
LocaleInfo & { locales: readonly LocaleInfo[] }: the locale of the current page and every configured locale. It is what useLocale() of @silgi/i18n/<library> gives (a ComputedRef in vue, an Accessor in solid, getters in svelte).
I18nRuntime, I18nRouteLocation
useApp().i18n. localeOf(path) is the locale of an app path, localePath(to, code) the same page in another locale (a path no page matches comes back unchanged), switchLocalePath(route, code) the current route in another locale, and routeBaseName("about___fr") is about. I18nRouteLocation is { name, params?, query?, hash? } with a base route name.
interface I18nRuntime {
readonly locales: readonly LocaleInfo[];
readonly defaultLocale: LocaleInfo;
localeOf(path: string): LocaleInfo;
load(code: SilgiLocale): Promise<void>;
translator(code: SilgiLocale): Translator;
localePath(to: string | I18nRouteLocation, code: SilgiLocale): string;
switchLocalePath(route: { path: string; name?: string | null; params: Record<string, string | string[]>; query: Record<string, string | string[]>; hash: string }, code: SilgiLocale): string;
routeBaseName(name: string): string;
}
Translator
{ locale, t(key, args?), n(value, options?), d(value, options?) } for one locale.
SilgiI18nMessages, SilgiI18nLocales, SilgiLocale, I18nKey, I18nArgsTuple
The registries typegen fills (message key → arguments, locale code → true) and the types over them. While a registry is empty, SilgiLocale and I18nKey are string and I18nArgsTuple<K> is [args?: Record<string, unknown>].
@silgi/i18n/server
useTranslation(event)
useTranslation(event: SilgiEvent): Promise<{ locale: LocaleInfo; t; n; d }>. The translator of event.context.locale, which the module's server middleware sets for every request: the first segment of the path below baseURL when it is a locale with a route copy, then ?locale=<code>, the locale cookie, Accept-Language, the default locale. For a page request the route middleware then sets it to the page's locale. An event the middleware did not see resolves by the same order. A locale's messages load once per process. The event is passed explicitly; there is no AsyncLocalStorage.
export default defineMiddleware(async (event) => {
const { t } = await useTranslation(event);
return { message: t("api.saved") };
});
@silgi/i18n/react, @silgi/i18n/vue, @silgi/i18n/preact, @silgi/i18n/svelte, @silgi/i18n/solid, @silgi/i18n/html
One entry per adapter. Each exports useI18n, useLocale and the type CurrentLocale, with the same names in all six. Both read useApp().i18n and useRoute() of the adapter and throw useI18n() needs the "@silgi/i18n" module: app.i18n is missing without the module. The locale is localeOf(route.path), so the route is the only store.
useI18n()
useI18n(): I18nFunctions, where I18nFunctions is { t, n, d, localePath(to), switchLocalePath(code), routeBaseName(name) }. t, n and d are those of the page locale's Translator. localePath(to: string | I18nRouteLocation) is app.i18n.localePath with the current locale, and switchLocalePath(code) is app.i18n.switchLocalePath with the current route. The functions read the route when they are called, so a template, accessor or getter that calls them runs again when a navigation changes the locale.
| Library | Returns |
|---|---|
| react, preact | I18nFunctions; the same object while the locale code is the same |
| vue | I18nFunctions & { locale: ComputedRef<CurrentLocale> } |
| svelte | I18nFunctions & { readonly locale: CurrentLocale }, where locale is a getter |
| solid | I18nFunctions & { locale: Accessor<CurrentLocale> } |
| html | I18nFunctions for this render, with t, n and d returning text escaped for HTML (&, <, >, ", ') |
const { t, n, d, localePath, switchLocalePath } = useI18n();
t("home.hello", { name: "Ada" });
localePath("/about");
switchLocalePath("ar");
useLocale()
useLocale() gives the CurrentLocale of the page: CurrentLocale in react, preact and html; ComputedRef<CurrentLocale> in vue; Accessor<CurrentLocale> in solid; and in svelte an object whose code, language, dir, name and locales are getters. The html value is not escaped.
const { code, language, dir, locales } = useLocale();