From bce557cc2dc767628bed6aac87301a1be7c5431b Mon Sep 17 00:00:00 2001 From: rxliuli Date: Tue, 4 Nov 2025 05:03:50 +0800 Subject: init commit --- .../tmp/src/foundation/wrappers/localization.js | 437 +++++++++++++++++++++ 1 file changed, 437 insertions(+) create mode 100644 node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/localization.js (limited to 'node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/localization.js') diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/localization.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/localization.js new file mode 100644 index 0000000..d7e0ee8 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/localization.js @@ -0,0 +1,437 @@ +import { makeMetatype } from "@jet/environment/util/metatype"; +import { AmpLocalization } from "../amp-localization/amp-localization"; +import { isNullOrEmpty } from "../json-parsing/server-data"; +import { Wrapper } from "./wrapper"; +import { isSome } from "@jet/environment/types/optional"; +import * as validation from "@jet/environment/json/validation"; +/** + * Wrapper around an object or `Localization` type. + * The wrapped object is implemented as part of a native app. + */ +export class LocalizationWrapper extends Wrapper { + constructor(loc, objectGraph) { + super(loc); + /** + * Path to localization file. + * The path is set when the localization file is loaded + * for the first time. + */ + this.locFile = null; + /** + * Instance of `AMPLocalization` providing server-side + * values for localized stirngs. + */ + this.ampLoc = new AmpLocalization(); + /** + * Localization strings cache. + * Cache and reuse values returned by + * wrapped `Localization` implementation. + */ + this.LOC_STRING_CACHE = {}; + this.objectGraph = objectGraph; + } + /** + * Returns the wrapped localization's identifier. + */ + get identifier() { + return this.implementation.identifier; + } + /** + * Returns the wrapped localization's safe identifier. + */ + get safeIdentifier() { + return this.implementation.identifier.split("_")[0]; + } + // endregion + // region Localization + /** + * 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 + */ + string(key, defaultValue) { + return this.implementation.string(key); + } + /** + * Localizes a string, and logs & throws an error if the key is a screamer. + * @param key The loc key to look up + * @return The localized string + */ + tryString(key) { + const value = this.implementation.string(key); + if (value === key || value === `**${key}**`) { + validation.context("tryString", () => { + validation.unexpectedType("coercedValue", "Localization key", key, null); + }); + throw new Error(`No value exists for localization key '${key}'`); + } + return value; + } + /** + * Localizes a string, and logs an error if the key is a screamer. + * @param key The loc key to look up + * @param fallback The fallback value for unlocalized keys, e.g. English value + * @return The localized string + */ + stringWithFallback(key, fallback) { + const value = this.implementation.string(key); + return value === `**AppStore.${key}**` ? fallback : value; + } + /** + * Localizes a string using a preferred locale. + * + * The implementation of this function is similar to `string(key:)` above, but with the option + * to prefer a particular locale. If one is provided, we augment the lookup key in the cache + * with the `locale` value. If one is not provided, we fallback to `string(key:)`. This makes it + * much easier to use this function directly, in place of `string(key:)` where necessary. + + * @param objectGraph The object graph, used to forward this call on if the required native function is unavailable. + * Can be removed for 2023. + * @param key The loc key to look up. + * @param locale The preferred locale to use for look up. Falls back to default, if unavailable. + * @param defaultValue A default value to use if nothing is found. + */ + stringForPreferredLocale(objectGraph, key, locale, defaultValue) { + if (isNullOrEmpty(locale)) { + return this.string(key, defaultValue); + } + const cacheKey = `${key}_${locale}`; + let value = this.LOC_STRING_CACHE[cacheKey]; + if (!value) { + value = this.implementation.stringForPreferredLocale(key, locale); + if (value && value !== key) { + this.LOC_STRING_CACHE[cacheKey] = value; + } + else { + const serverValue = this.ampLoc.localize(key); + if (serverValue !== key) { + value = serverValue; + } + else if (defaultValue) { + value = defaultValue; + } + else { + value = key; + } + } + } + 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 + */ + stringWithCount(key, count, params) { + let value = this.implementation.stringWithCount(key, count); + if (!value || value === key) { + const serverValue = this.ampLoc.localizeWithCount(this.objectGraph, key, count, params); + if (serverValue) { + value = serverValue; + } + } + return value; + } + /** + * A variation of `stringWithCount` that supports multiple plural forms. + * @param key base loc key + * @param counts numbers used to determine the plural forms + * @param params (optional) substitution keys and values, "count" will be added if it's not already present + * @return localized string + */ + stringWithCounts(key, counts, params) { + return this.implementation.stringWithCounts(key, counts); + } + /** + * Converts a string into its uppercased form. + * @param value The string to apply the uppercase to. + * @returns {string} The loacle-specific, uppercased form of the string. If for whatever reason the upper-casing cannot + * be applied, this function simply returns the input value. + */ + uppercased(value) { + if (!value) { + return null; + } + return value.toLocaleUpperCase(this.safeIdentifier); + } + /** + * Converts a number into a localized string + * + * @param n The number to convert + * @param decimalPlaces The number of decimal places to include. + * @return {string} The localized version of the number + */ + decimal(n, decimalPlaces) { + let value = this.implementation.decimal(n, decimalPlaces); + if (!value) { + if (typeof n === "number") { + value = `* ${n.toString()} *`; + } + else { + value = this.nullString(); + } + } + return value; + } + /** + * Converts a number of bytes into a localized file size string + * + * @param bytes The number of bytes to convert + * @return The localized file size string + */ + fileSize(bytes) { + let value = this.implementation.fileSize(bytes); + if (!value) { + value = this.nullString(); + } + return value; + } + /** + * Converts a number of bytes into a localized file size string + * + * @param count The number of bytes to convert + * @return The localized file size string + */ + formattedCount(count) { + let value = this.implementation.formattedCount(count); + if (!value) { + value = this.nullString(); + } + return value; + } + /** + * Converts a number into a formatted string representation using the preferred locale identifier. + * + * @param count The number to format. + * @param locale The locale identifier to prefer for lookup. + * @return The localized string. + */ + formattedCountForPreferredLocale(objectGraph, count, locale) { + if (isNullOrEmpty(locale)) { + return this.formattedCount(count); + } + let value = this.implementation.formattedCountForPreferredLocale(count, locale); + if (!value) { + value = this.nullString(); + } + return value; + } + /** + * Converts a date into a time ago label, showing how long ago + * the date occurred + * + * @param date The date object to convert + * @param context The context in which the date should be formatted + * @return The localized string representing the amount of time that has passed + */ + timeAgoWithContext(date, context) { + let value = this.implementation.timeAgoWithContext(date, context); + if (!value) { + value = this.nullString(); + } + return value; + } + /** + * Converts a date into a localized date string using the provided format + * + * @param format The format string describing how the date should be formatted + * @param date The date object to convert + * @return The localized string representing the date + */ + formatDate(format, date) { + let value = this.implementation.formatDate(format, date); + if (!value) { + value = this.nullString(); + } + return value; + } + formatDateWithContext(format, date, context) { + let value = this.implementation.formatDateWithContext(format, date, context); + if (!value) { + value = this.nullString(); + } + return value; + } + formatDateInSentence(sentence, format, date) { + let value = this.implementation.formatDateInSentence(sentence, format, date); + if (!value) { + value = this.nullString(); + } + return value; + } + /** + * Converts a date into a relative date, showing how long ago + * the date occurred + * + * @param date The date object to convert + * @return The localized string representing the amount of time that has passed + */ + relativeDate(date) { + let value = this.implementation.relativeDate(date); + if (!value) { + value = this.nullString(); + } + return value; + } + formatDuration(value, unit) { + let result = this.implementation.formatDuration(value, unit); + if (!result) { + result = this.nullString(); + } + return result; + } + // endregion + // region Private Methods + /** + * Applies a new localization file and localization dictionary as the new set of localizations + * + * @param file The file name of the file that is loaded + * @param localizations The localizations dictionary to use + * @param locale Current normalized locale which includes language code + */ + applyLocalizations(file, localizations, locale) { + if (this.isLocFileLoaded(file)) { + return; + } + this.locFile = file; + // The first 2 characters of normalized locale are the language code. + this.ampLoc.updateLocalizationData(localizations, locale.slice(0, 2)); + } + /** + * Checks if the named loc file is already loaded + * @param file The file name to check + * @return {boolean} True or false depending on whether the file name is loaded + */ + isLocFileLoaded(file) { + return this.locFile === file; + } + /** + * Normalizes the given bag language into a locale. + * @param language The language to normalize. + * @param storefrontIdentifier The storefront identifier to use. + * @return A locale to use. + */ + normalizedLocale(objectGraph, language, storefrontIdentifier) { + language = language.toLowerCase(); + switch (language) { + case "yue-hant": { + // Country code from the bag is not available in JS, so we use the storefront. + const macauStoreFrontIdentifier = objectGraph.props.asString("macauStorefrontIdentifier"); + if (typeof storefrontIdentifier === "string" && + typeof macauStoreFrontIdentifier === "string" && + storefrontIdentifier.indexOf(macauStoreFrontIdentifier) !== -1) { + return "zh-ma"; + } + else { + return "zh-hk"; + } + } + default: + return language; + } + } + nullString() { + return "* null *"; + } + // endregion + // region API + /** + * Loads the localizations from the JetPack. + */ + load(objectGraph) { + // Sanity check + if (objectGraph.bag.language === undefined || objectGraph.bag.language === null) { + throw new Error("Bag language is not available. Unable to load localizations."); + } + const locale = this.normalizedLocale(objectGraph, objectGraph.bag.language, objectGraph.client.storefrontIdentifier); + const locName = `local/${locale}`; + // Load localizations if needed + if (!this.isLocFileLoaded(locName)) { + const localizations = objectGraph.props.asDictionary(`localizations.${locale}`); + if (localizations !== undefined && localizations !== null) { + this.applyLocalizations(locName, localizations, locale); + } + else { + // Fallback to english + const fallbackLocalizations = objectGraph.props.asDictionary(`localizations.en-us`); + if (fallbackLocalizations !== undefined && fallbackLocalizations !== null) { + this.applyLocalizations(locName, fallbackLocalizations, locale); + } + } + } + } + /** + * Returns the localized name for the provided device type. + * The company policy seems to be to always use the branded, + * non-localized text, but this is just-in-case. + * @returns {string} The display name for the device. + */ + deviceDisplayName(objectGraph) { + if (objectGraph.client.isVision && isSome(objectGraph.host.deviceMarketingFamilyName)) { + return objectGraph.host.deviceMarketingFamilyName; + } + if (objectGraph.host.deviceLocalizedModel) { + return objectGraph.host.deviceLocalizedModel; + } + // TODO: Jet: Remove brand loc fallbacks + switch (objectGraph.client.deviceType) { + case "phone": + const localizedPhoneName = this.string("IPHONE_BRAND_NAME"); + if (localizedPhoneName === "IPHONE_BRAND_NAME") { + return "iPhone"; + } + return localizedPhoneName; + case "pad": + const localizedPadName = this.string("IPAD_BRAND_NAME"); + if (localizedPadName === "IPAD_BRAND_NAME") { + return "iPad"; + } + return localizedPadName; + case "tv": + const localizedTvName = this.string("APPLE_TV_BRAND_NAME"); + if (localizedTvName === "APPLE_TV_BRAND_NAME") { + return "Apple\u00a0TV"; + } + return localizedTvName; + case "watch": + const localizedWatchName = this.string("APPLE_WATCH_BRAND_NAME"); + if (localizedWatchName === "APPLE_WATCH_BRAND_NAME") { + return "Apple\u00a0Watch"; + } + return localizedWatchName; + case "mac": + const localizedMacName = this.string("MAC_BRAND_NAME"); + if (localizedMacName === "MAC_BRAND_NAME") { + return "Mac"; + } + return localizedMacName; + default: + return null; + } + } +} +// region Properties +/** + * Localization wrapper metatype to use with object graph. + */ +LocalizationWrapper.type = makeMetatype("app-store:loc-wrapper"); +//# sourceMappingURL=localization.js.map \ No newline at end of file -- cgit v1.2.3