@sky.ui/i18n
v0.0.2
Published
Framework-agnostic translations for Sky UI apps. One catalog drives page copy and sky-config-provider chrome.
Readme
@sky.ui/i18n
Framework-agnostic translations for a Sky UI page. The package has no Vue, React, or Lit dependency. Your framework re-renders when the catalog changes. The same catalog overrides Sky component chrome through sky-config-provider.
import { bindSkyLocale, createI18n } from "@sky.ui/i18n";
const i18n = createI18n({
locale: "en",
fallbackLocale: "en",
messages: {
en: {
home: { title: "Welcome, {name}" },
"common.close": "Close",
items: { one: "{count} item", other: "{count} items" },
},
hi: {
home: { title: "स्वागत है, {name}" },
"common.close": "बंद करें",
},
},
});
i18n.t("home.title", { name: "Asha" });
i18n.t("items", { count: 2 });
i18n.setLocale("hi");Nested JSON keys become dot ids (home.title). Placeholders accept {name} and {{name}}. count selects id.one / id_one and the other CLDR categories, then other. context selects id_male or id.male. $t(key) nests another entry. {amount, number} uses Intl (number, percent, currency(USD), datetime, date, time, weekday, unit(kilometer), relative, list). measurement is metric or us (en-US starts as us). unit(kilometer) converts when that setting disagrees with the written unit. A Date in {when, relative} is compared with now; a number is seconds from now. {items, list} joins an array with Intl.ListFormat. displayName("de", { type: "language" }) and compare(a, b) use the active language. A missing key returns the id, or defaultValue. fallbackLocale may be one language or a list. ns:key reads another namespace when the text before : is an identifier (common:save). A sentence that contains : or . stays one key. returnObjects: true returns the catalog subtree.
Page sentences belong in their own namespace so they are not copied into skyLocale():
i18n.addMessages("hi", { "View plans": "योजनाएं देखें" }, "page");
i18n.t("View plans", { ns: "page" });Sky components
Assign the catalog to the provider. Chrome ids such as common.close override the English inside the components. Page copy still goes through t().
const provider = document.querySelector("sky-config-provider");
bindSkyLocale(i18n, provider);
i18n.applyDocument();applyDocument() sets lang and dir on <html>. dir follows the language unless you call setDir.
Vue
import { ref } from "vue";
export const locale = ref(i18n.locale);
i18n.subscribe(() => {
locale.value = i18n.locale;
});
export function t(id: string, values?: Record<string, string | number>) {
locale.value;
return i18n.t(id, values);
}React
import { useSyncExternalStore } from "react";
export function useLocale() {
return useSyncExternalStore(i18n.subscribe, () => i18n.locale);
}Call t() during render after useLocale() so a locale change renders again.
Same jobs as a catalog runtime
i18n.t("items", { count: 2 });
i18n.t("friend", { context: "male" });
i18n.t("menu", { returnObjects: true });
i18n.t("common:save");
i18n.exists("home.title");
await i18n.loadNamespace("common");
await i18n.changeLanguage("hi");languageDetector supplies the locale when locale is omitted. backend.read(locale, namespace) loads a namespace that was not in the initial messages. missingKeyHandler runs when a key is missing. postProcess runs after a translation is found. format overrides the built-in {value, number} formatter.
Extract
The runtime does not scan a site. sky-i18n extract reads t("charts.title", { defaultValue: "Charts" }) and a sentence helper such as page("View plans"), then updates one JSON file per locale and namespace. A translation that is already filled in is left as it is. A new key is the English in the source locale and "" in the others, which is the slot a translator fills. Keys that disappeared from the source stay in the file. A call that passes a variable is listed and not invented as a key.
sky-i18n extract --src app --out locales --locale en --locale hi --fn t --fn page:pagepage:page means the function page writes the page namespace. Load locales/hi/page.json with addMessages("hi", page, "page").
Scan site copy
scanProject is exported from @sky.ui/i18n and from @sky.ui/i18n/scan. sky-i18n scan reads visible Vue text (inline tags stay in one sentence), content attributes (label, title, placeholder, alt, aria-label), sentence literals, and JSON fields such as heading, subheading, and description. It writes page.json per locale. The English string is the key. A translation that is already filled stays. New keys are blank in the other locales. A sentence that contains {count} also keeps {count} …_one and {count} …_other so a plural pair can be filled later. Text inside pre and sky-code is skipped, as are paths, icons, class tokens, and commands. applyPageDom also translates a sentence that is split by code or kbd, and keeps those tokens in place.
--include limits the scan to paths under --src. Repeat it to scan several areas. Omit it to scan the whole tree.
sky-i18n scan --src app --out locales --locale en --locale hi --locale ar --locale de --locale es
sky-i18n scan --src app --out locales --locale en --locale hi --include pages/index.vue --include components/publicapplyPageDom(catalog) writes those translations onto matching text in the page and in open shadow roots, including placeholder, title, alt, and aria text. Call it again after render.
