UNPKG

@j2inn/i18n

Version:

J2 Innovations internationalization library

230 lines (229 loc) 9.05 kB
/* * Copyright (c) 2025, J2 Innovations. All Rights Reserved */ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var _I18n_localeMap; /** * Process the locale data into a map. */ function processLocaleDataIntoMap(baseKey, localeMap, localeData) { for (const key of Object.keys(localeData)) { const mapKey = baseKey ? `${baseKey}.${key}` : key; const value = localeData[key]; if (typeof value === 'string') { localeMap[mapKey] = value; } else if (value && typeof value === 'object') { processLocaleDataIntoMap(mapKey, localeMap, value); } } } /** * Loads, caches and queries localization data. */ export class I18n { constructor(localeData = {}, antdLocaleData, locale = antdLocaleData?.locale) { /** * Cached locale strings. */ _I18n_localeMap.set(this, {} /** * Cached locale data for ant design. */ ); /** * The loading flag. */ this.loading = false; /** * Get a localized string value. * * @param key The language key. * @param values The handlebar values to be substitued. * @returns The localized string value. */ this.get = (key, values) => { if (typeof __classPrivateFieldGet(this, _I18n_localeMap, "f")[key] === 'string') { // If we have a value for this key return it. return I18n.replaceDynamicValues(String(__classPrivateFieldGet(this, _I18n_localeMap, "f")[key]), values); } else if (typeof __classPrivateFieldGet(this, _I18n_localeMap, "f")[key.toLowerCase()] === 'string') { // If we have a value for this key (but lowercased) return it. return I18n.replaceDynamicValues(String(__classPrivateFieldGet(this, _I18n_localeMap, "f")[key.toLowerCase()]), values); } // Otherwise, return the key but print a warning about missing key. console.warn(`Missing translation key for: "${key}"`); return I18n.replaceDynamicValues(key, values); }; /** * Get a localized string value. * * ```typescript * const {t} = useI18n() * const localizedStr = t('foo.bar') * ``` * * @param key The language key. * @param values The handlebar values to be substitued. * @returns The localized string value. */ this.t = (key, values) => { return this.get(key, values); }; /** * @returns All the keys used for localization. */ this.keys = () => { return Object.keys(__classPrivateFieldGet(this, _I18n_localeMap, "f")); }; /** * Return true key is available. * * @param key The key to test. * @returns true if the specified key is available. */ this.has = (key) => { return (typeof __classPrivateFieldGet(this, _I18n_localeMap, "f")[key] === 'string' || typeof __classPrivateFieldGet(this, _I18n_localeMap, "f")[key.toLowerCase()] === 'string'); }; /** * Import the locale data from the server for the given path. * * @param locale The language locale to use. * @param path The path URI. * @param fetch An optional alternative fetch function to use for * the network request. */ this.import = async (locale, path, fetchFunc = fetch) => { try { this.loading = true; this.locale = I18n.toLocaleIdentifier(locale); this.intl = new Intl.Locale(locale); __classPrivateFieldSet(this, _I18n_localeMap, {}, "f"); this.antdLocaleData = undefined; path = I18n.replaceDynamicValues(path, { locale: path.includes('fin5Lang') ? I18n.getLanguageCode(locale) : locale, }); const res = await fetchFunc(path); let localeData; // Use the response's content type to decide how to decode the data. if (res.headers .get('content-type') ?.toLowerCase() .includes('text/plain')) { localeData = I18n.parseFin5LanguageFile(await res.text()); } else { localeData = await res.json(); } processLocaleDataIntoMap('', __classPrivateFieldGet(this, _I18n_localeMap, "f"), localeData); try { const antLocaleCode = I18n.toLocaleProvider(locale); const data = await import(`antd/es/locale-provider/${antLocaleCode}.js`); this.antdLocaleData = data.default; } catch (ignore) { } } finally { this.loading = false; } }; processLocaleDataIntoMap('', __classPrivateFieldGet(this, _I18n_localeMap, "f"), localeData); this.antdLocaleData = antdLocaleData; this.locale = locale; this.intl = locale === undefined ? undefined : new Intl.Locale(locale); } /** * @returns A copy of the internal locale string map. */ get internalMap() { return { ...__classPrivateFieldGet(this, _I18n_localeMap, "f") }; } /** * Replace the values in the string with the supplied values. * * The string should use handlebars style syntax to define the * variables to be replaced. For example, `Hello {{world}} will have * `world` replaced by an object that has a `world` property. * * @param str The string to update. * @param values The values to be used in the update process. * @returns The updated string. */ static replaceDynamicValues(str, values) { let res = str; if (values) { for (const key of Object.keys(values)) { res = res.replace(`{{${key}}}`, values[key]); } } return res; } /** * Parse a FIN5 language file and return the locale data. * * @param input The input text. * @returns The locale data. */ static parseFin5LanguageFile(input) { const localeData = {}; const lines = input.split('\n'); for (let i = 0, len = lines.length; i < len; i++) { const line = lines[i].trim(); if (line.length < 1 || line.startsWith('//')) { continue; } const equalIndex = line.indexOf('='); const key = line.slice(0, equalIndex).trim(); const value = line.slice(equalIndex + 1).trim(); localeData[key] = value; } return localeData; } /** * Extracts the ISO 639-1 langage code from the locale. * * @param locale The locale. * @returns The language code. */ static getLanguageCode(locale) { return locale.replace(/^(\w+)[_-]\w+$/, '$1'); } /** * Convert the locale to a valid unicode locale identifier. * * @param locale The locale tag to convert. * @returns The language tag. */ static toLocaleIdentifier(locale) { return locale.replace(/_/g, '-'); } /** * Convert the locale into an ant design compatible locale provider. * * @param locale The locale tag to convert. * @returns The locale provider. */ static toLocaleProvider(locale) { const parts = locale.split('-'); // If no region is specified, add a default region. if (parts.length === 1) { parts.push(parts[0]); } parts[1] = parts[1].toUpperCase(); return parts.join('_'); } } _I18n_localeMap = new WeakMap();