@shoelace-style/localize
Version:
A micro library for localizing custom elements using Lit's Reactive Controller model.
112 lines (111 loc) • 4.08 kB
JavaScript
const connectedElements = new Set();
const translations = new Map();
let fallback;
let documentDirection = 'ltr';
let documentLanguage = 'en';
const isClient = (typeof MutationObserver !== "undefined" && typeof document !== "undefined" && typeof document.documentElement !== "undefined");
if (isClient) {
const documentElementObserver = new MutationObserver(update);
documentDirection = document.documentElement.dir || 'ltr';
documentLanguage = document.documentElement.lang || navigator.language;
documentElementObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['dir', 'lang']
});
}
export function registerTranslation(...translation) {
translation.map(t => {
const code = t.$code.toLowerCase();
if (translations.has(code)) {
translations.set(code, Object.assign(Object.assign({}, translations.get(code)), t));
}
else {
translations.set(code, t);
}
if (!fallback) {
fallback = t;
}
});
update();
}
export function update() {
if (isClient) {
documentDirection = document.documentElement.dir || 'ltr';
documentLanguage = document.documentElement.lang || navigator.language;
}
[...connectedElements.keys()].map((el) => {
if (typeof el.requestUpdate === 'function') {
el.requestUpdate();
}
});
}
export class LocalizeController {
constructor(host) {
this.host = host;
this.host.addController(this);
}
hostConnected() {
connectedElements.add(this.host);
}
hostDisconnected() {
connectedElements.delete(this.host);
}
dir() {
return `${this.host.dir || documentDirection}`.toLowerCase();
}
lang() {
return `${this.host.lang || documentLanguage}`.toLowerCase();
}
getTranslationData(lang) {
var _a, _b;
const locale = new Intl.Locale(lang.replace(/_/g, '-'));
const language = locale === null || locale === void 0 ? void 0 : locale.language.toLowerCase();
const region = (_b = (_a = locale === null || locale === void 0 ? void 0 : locale.region) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
const primary = translations.get(`${language}-${region}`);
const secondary = translations.get(language);
return { locale, language, region, primary, secondary };
}
exists(key, options) {
var _a;
const { primary, secondary } = this.getTranslationData((_a = options.lang) !== null && _a !== void 0 ? _a : this.lang());
options = Object.assign({ includeFallback: false }, options);
if ((primary && primary[key]) ||
(secondary && secondary[key]) ||
(options.includeFallback && fallback && fallback[key])) {
return true;
}
return false;
}
term(key, ...args) {
const { primary, secondary } = this.getTranslationData(this.lang());
let term;
if (primary && primary[key]) {
term = primary[key];
}
else if (secondary && secondary[key]) {
term = secondary[key];
}
else if (fallback && fallback[key]) {
term = fallback[key];
}
else {
console.error(`No translation found for: ${String(key)}`);
return String(key);
}
if (typeof term === 'function') {
return term(...args);
}
return term;
}
date(dateToFormat, options) {
dateToFormat = new Date(dateToFormat);
return new Intl.DateTimeFormat(this.lang(), options).format(dateToFormat);
}
number(numberToFormat, options) {
numberToFormat = Number(numberToFormat);
return isNaN(numberToFormat) ? '' : new Intl.NumberFormat(this.lang(), options).format(numberToFormat);
}
relativeTime(value, unit, options) {
return new Intl.RelativeTimeFormat(this.lang(), options).format(value, unit);
}
}