/**
* 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