/** * A type encapsulating localization logic for strings * returned by localization server. * * Replacement for 'its' namespace from @onyx/localization package. */ export class AmpLocalization { constructor() { // region Properties /** * Localization JSON dictionary loaded from localization file. */ this.locData = {}; /** * The 2-letter code for current device language. */ this.language = "en"; // endregion } // endregion // region API /** * Updates the localization data for the device. * @param localizations A JSON dictionary representing the localized strings. * @param language Language code for current device language. */ updateLocalizationData(localizations, language) { this.locData = localizations; this.language = language; } /** * Localizes a string replacing placehoders in key with values in the parameters dictionary * @param key The loc key to look up * @param params Parameters to replace in the loc string * @return The localized string */ localize(key, params) { let value = this.locData[key]; if (value === undefined || typeof value !== "string") { value = key; } if (params) { value = this.replaceTokens(value, params); } value = this.replaceMarkupTokens(value, params); return value; } /** * Localize with appropriate plural form based on the count. * * Some languages have more plural forms than others. * The full set of categories is "zero", "one", "two", "few", "many", and "other". * * The base loc key is used for "other" (the default). * Otherwise the category is appended to the base loc key with a "." separator. * * http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html * http://cldr.unicode.org/index/cldr-spec/plural-rules#TOC-Determining-Plural-Categories * http://unicode.org/repos/cldr/trunk/specs/ldml/tr35-numbers.html#Language_Plural_Rules * * English loc keys * key = "@@count@@ dogs"; // default, aka "plural" * key.zero = "no dogs"; // used when count is 0 * key.one = "@@count@@ dog"; // used when count is 1, aka "singular" * * @param key base loc key * @param count number used to determine the plural form * @param params (optional) substitution keys and values, "count" will be added if it's not already present * @return localized string */ localizeWithCount(objectGraph, key, count, params) { let keyToUse = null; let pluralFormKey; let category; if (count === 0) { // Special case zero for all languages so we can have strings like // "you have no messages" instead of "you have 0 messages". pluralFormKey = `${key}.zero`; if (this.isLocalized(objectGraph, pluralFormKey)) { keyToUse = pluralFormKey; } } if (keyToUse === null) { keyToUse = key; category = this.pluralCategory(objectGraph, count); if (category !== "other") { pluralFormKey = `${key}.${category}`; if (this.isLocalized(objectGraph, pluralFormKey)) { keyToUse = pluralFormKey; } } } if (!params) { params = {}; } // @@count@@ is the standard, but @@number@@ is frequently used too. if (params.count === undefined) { params.count = this.formatNumber(count.toString()); } if (params.number === undefined) { params.number = this.formatNumber(count.toString()); } return this.localize(keyToUse !== null && keyToUse !== void 0 ? keyToUse : key, params); } // endregion // region Private Methods replaceTokens(text, values) { Object.entries(values).forEach(([key, value]) => { const subKey = "@@" + key + "@@"; text = this.replaceSubstring(text, subKey, value); }); return text; } replaceMarkupTokens(text, values) { if (text.indexOf("##") <= -1) { return text; } // Resolve token properties. let markupParams; if (values) { // Create a copy to avoid mutating defaults. markupParams = { ...AmpLocalization.MARKUP_PARAMS }; Object.entries(values).forEach(([key, value]) => { markupParams[key] = value; }); } else { markupParams = AmpLocalization.MARKUP_PARAMS; } Object.entries(markupParams).forEach(([key, value]) => { const token = "##" + key + "##"; text = text.replace(new RegExp(token, "gi"), value); }); // Replace any remaining standard markup tags like
etc. text = text.replace(/##([^##]+)##/gi, "<$1>"); return text; } /** * Searches "str" for "substr" and replaces each occurrence with "replacement" * @param str input string * @param substr the string to search for in the input string * @param replacement value to use for the replacement * * JavaScript String.replace has a misfeature where "$" in the replacement is always interpreted as a meta char, * i.e. you have to use "$$" in the replacement to get a single "$" in the resulting string * which lead to tv episode title containing "$&" not displaying correctly * Using split and join is faster than substituting $$ for $ in the replacement. */ replaceSubstring(str, substr, replacement) { return str.split(substr).join(replacement); } formatNumber(value) { let decimalSeparator = this.locData["_decimalSeparator"]; if (decimalSeparator === undefined || typeof decimalSeparator !== "string") { decimalSeparator = "."; } let thousandsSeparator = this.locData["_thousandsSeparator"]; if (thousandsSeparator === undefined || typeof thousandsSeparator !== "string") { thousandsSeparator = "."; } const parts = parseFloat(value).toString().split("."); const chars = parts[0].split(""); for (let i = chars.length - 3; i > 0; i -= 3) { chars.splice(i, 0, thousandsSeparator); } parts[0] = chars.join(""); return parts.join(decimalSeparator); } /** * Check whether the given key is localized or not. * @param key The key to check. */ isLocalized(objectGraph, key) { const value = this.locData[key]; if (value === undefined || typeof value !== "string") { return false; } else if (key.indexOf(".") === -1) { // Simple localization keys such as "OK". return true; } else if (value === key || (value.indexOf("**") === 0 && value.lastIndexOf("**") === value.length - 2)) { objectGraph.console.error("Unlocalized key in keys dictionary", key); return false; } return true; } /** * Returns localization plural category for given number. * @param num The number to return plural category for. * @returns Plural category for specified number or "other" * if there's no plural category function available for current language. * * @see: * - http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html * - http://cldr.unicode.org/index/cldr-spec/plural-rules#TOC-Determining-Plural-Categories * - http://unicode.org/repos/cldr/trunk/specs/ldml/tr35-numbers.html#Language_Plural_Rules */ pluralCategory(objectGraph, num) { const categoryFn = AmpLocalization.pluralCategoryFnByLanguage[this.language]; if (categoryFn !== undefined) { return categoryFn(num); } else { objectGraph.console.warn("Missing plural category function for: " + this.language); return "other"; } } } /** * Markup parameters used for replacing markup tokens in text. */ AmpLocalization.MARKUP_PARAMS = { nbsp: " ", gt: ">", lt: "<", copy: "\u00a9" }; // endregion // regions Plurals AmpLocalization.pluralCategoryDefault = function (num) { return "other"; }; AmpLocalization.pluralCategoryOne = function (num) { if (num === 1) { return "one"; } return "other"; }; AmpLocalization.pluralCategoryArabic = function (num) { const n = num >> 0; if (n !== num) { // non integer return "other"; } if (n === 0) { return "zero"; } if (n === 1) { return "one"; } if (n === 2) { return "two"; } const m100 = n % 100; if (m100 >= 11) { // n mod 100 in 11..99 return "many"; } if (m100 >= 3) { // n mod 100 in 3..10 return "few"; } return "other"; }; AmpLocalization.pluralCategoryFrench = function (num) { // Do not check for integer, French includes fractional values! if (num < 2 && num >= 0) { return "one"; } return "other"; }; AmpLocalization.pluralCategoryHebrew = function (num) { const n = num >> 0; if (n !== num) { // non integer return "other"; } if (n === 1) { return "one"; } if (n === 2) { return "two"; } const m10 = n % 10; if (m10 === 0 && n > 10) { // n mod 10 is 0 and n != 0..10 return "many"; } return "other"; }; AmpLocalization.pluralCategoryPolish = function (num) { const n = num >> 0; if (n !== num) { // non integer return "other"; } if (n === 1) { return "one"; } const m10 = n % 10; if (m10 <= 4 && m10 >= 2) { const m100 = n % 100; if (m100 > 14 || m100 < 12) { // n mod 10 in 2..4 and n mod 100 not in 12..14 return "few"; } } return "many"; }; AmpLocalization.pluralCategoryRomanian = function (num) { const n = num >> 0; if (n !== num) { // non integer return "few"; } if (n === 0) { return "few"; } if (n === 1) { return "one"; } const m100 = num % 100; if (m100 <= 19 && m100 >= 1) { // n mod 100 in 1..19 return "few"; } return "other"; }; AmpLocalization.pluralCategoryRussian = function (num) { const n = num >> 0; if (n !== num) { // non integer return "other"; } const m10 = n % 10; if (m10 >= 5 || m10 === 0) { // n mod 10 is 0 or n mod 10 in 5..9 return "many"; } const m100 = n % 100; if (m100 <= 14 && m100 >= 11) { // n mod 100 in 11..14 return "many"; } if (m10 === 1) { // n mod 10 is 1 and n mod 100 is not 11 return "one"; } // n mod 10 in 2..4 and n mod 100 not in 12..14 return "few"; }; /** * Mapping language code to plural category function. */ AmpLocalization.pluralCategoryFnByLanguage = { zh: AmpLocalization.pluralCategoryDefault, id: AmpLocalization.pluralCategoryDefault, ja: AmpLocalization.pluralCategoryDefault, ko: AmpLocalization.pluralCategoryDefault, ms: AmpLocalization.pluralCategoryDefault, th: AmpLocalization.pluralCategoryDefault, vi: AmpLocalization.pluralCategoryDefault, en: AmpLocalization.pluralCategoryOne, ca: AmpLocalization.pluralCategoryOne, da: AmpLocalization.pluralCategoryOne, nl: AmpLocalization.pluralCategoryOne, de: AmpLocalization.pluralCategoryOne, el: AmpLocalization.pluralCategoryOne, fi: AmpLocalization.pluralCategoryOne, hu: AmpLocalization.pluralCategoryOne, it: AmpLocalization.pluralCategoryOne, nb: AmpLocalization.pluralCategoryOne, no: AmpLocalization.pluralCategoryOne, pt: AmpLocalization.pluralCategoryOne, es: AmpLocalization.pluralCategoryOne, sv: AmpLocalization.pluralCategoryOne, tr: AmpLocalization.pluralCategoryOne, ar: AmpLocalization.pluralCategoryArabic, fr: AmpLocalization.pluralCategoryFrench, iw: AmpLocalization.pluralCategoryHebrew, pl: AmpLocalization.pluralCategoryPolish, ro: AmpLocalization.pluralCategoryRomanian, ru: AmpLocalization.pluralCategoryRussian, // Russian }; //# sourceMappingURL=amp-localization.js.map