From bce557cc2dc767628bed6aac87301a1be7c5431b Mon Sep 17 00:00:00 2001 From: rxliuli Date: Tue, 4 Nov 2025 05:03:50 +0800 Subject: init commit --- .../amp-localization/amp-localization.js | 371 +++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 node_modules/@jet-app/app-store/tmp/src/foundation/amp-localization/amp-localization.js (limited to 'node_modules/@jet-app/app-store/tmp/src/foundation/amp-localization/amp-localization.js') diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/amp-localization/amp-localization.js b/node_modules/@jet-app/app-store/tmp/src/foundation/amp-localization/amp-localization.js new file mode 100644 index 0000000..df04983 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/foundation/amp-localization/amp-localization.js @@ -0,0 +1,371 @@ +/** + * 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 \ No newline at end of file -- cgit v1.2.3