UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

487 lines (439 loc) 16.9 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ //Provides the locale object sap.ui.core.Locale sap.ui.define(['sap/ui/base/Object', 'sap/base/assert', './CalendarType'], function(BaseObject, assert, CalendarType) { "use strict"; /** * A regular expression that describes language tags according to BCP-47. * @see BCP47 "Tags for Identifying Languages" (http://www.ietf.org/rfc/bcp/bcp47.txt) * * The matching groups are * 0=all * 1=language (shortest ISO639 code + ext. language sub tags | 4digits (reserved) | registered language sub tags) * 2=script (4 letters) * 3=region (2 letter language or 3 digits) * 4=variants (separated by '-', Note: capturing group contains leading '-' to shorten the regex!) * 5=extensions (including leading singleton, multiple extensions separated by '-'.Note: capturing group contains leading '-' to shorten the regex!) * 6=private use section (including leading 'x', multiple sections separated by '-') * * [-------------------- language ----------------------][--- script ---][------- region --------][------------- variants --------------][----------- extensions ------------][------ private use -------] */ var rLocale = /^((?:[A-Z]{2,3}(?:-[A-Z]{3}){0,3})|[A-Z]{4}|[A-Z]{5,8})(?:-([A-Z]{4}))?(?:-([A-Z]{2}|[0-9]{3}))?((?:-[0-9A-Z]{5,8}|-[0-9][0-9A-Z]{3})*)((?:-[0-9A-WYZ](?:-[0-9A-Z]{2,8})+)*)(?:-(X(?:-[0-9A-Z]{1,8})+))?$/i; /** * Creates an instance of the Locale. * * @class Locale represents a locale setting, consisting of a language, script, region, variants, extensions and private use section. * * @param {string} sLocaleId the locale identifier, in format en-US or en_US. * * @extends sap.ui.base.Object * @author SAP SE * @version 1.111.5 * @public * @alias sap.ui.core.Locale */ var Locale = BaseObject.extend("sap.ui.core.Locale", /** @lends sap.ui.core.Locale.prototype */ { constructor : function(sLocaleId) { BaseObject.apply(this); var aResult = rLocale.exec(sLocaleId.replace(/_/g, "-")); // If the given Locale string cannot be parsed by the regular expression above, // we should at least tell the developer why the Core fails to load. if (aResult === null ) { throw new TypeError("The given language '" + sLocaleId + "' does not adhere to BCP-47."); } this.sLocaleId = sLocaleId; this.sLanguage = aResult[1] || null; this.sScript = aResult[2] || null; this.sRegion = aResult[3] || null; this.sVariant = (aResult[4] && aResult[4].slice(1)) || null; // remove leading dash from capturing group this.sExtension = (aResult[5] && aResult[5].slice(1)) || null; // remove leading dash from capturing group this.sPrivateUse = aResult[6] || null; // convert subtags according to the BCP47 recommendations // - language: all lower case // - script: lower case with the first letter capitalized // - region: all upper case if ( this.sLanguage ) { this.sLanguage = this.sLanguage.toLowerCase(); } if ( this.sScript ) { this.sScript = this.sScript.toLowerCase().replace(/^[a-z]/, function($) { return $.toUpperCase(); }); } if ( this.sRegion ) { this.sRegion = this.sRegion.toUpperCase(); } }, /** * Get the locale language. * * Note that the case might differ from the original script tag * (Lower case is enforced as recommended by BCP47/ISO639). * * @returns {string} the language code * @public */ getLanguage : function() { return this.sLanguage; }, /** * Get the locale script or <code>null</code> if none was specified. * * Note that the case might differ from the original language tag * (Upper case first letter and lower case reminder enforced as * recommended by BCP47/ISO15924) * * @returns {string|null} the script code or <code>null</code> * @public */ getScript : function() { return this.sScript; }, /** * Get the locale region or <code>null</code> if none was specified. * * Note that the case might differ from the original script tag * (Upper case is enforced as recommended by BCP47/ISO3166-1). * * @returns {string} the ISO3166-1 region code (2-letter or 3-digits) * @public */ getRegion : function() { return this.sRegion; }, /** * Get the locale variants as a single string or <code>null</code>. * * Multiple variants are separated by a dash '-'. * * @returns {string|null} the variant or <code>null</code> * @public */ getVariant : function() { return this.sVariant; }, /** * Get the locale variants as an array of individual variants. * * The separating dashes are not part of the result. * If there is no variant section in the locale tag, an empty array is returned. * * @returns {string[]} the individual variant sections * @public */ getVariantSubtags : function() { return this.sVariant ? this.sVariant.split('-') : []; }, /** * Get the locale extension as a single string or <code>null</code>. * * The extension always consists of a singleton character (not 'x'), * a dash '-' and one or more extension token, each separated * again with a dash. * * Use {@link #getExtensions} to get the individual extension tokens as an array. * * @returns {string|null} the extension or <code>null</code> * @public */ getExtension : function() { return this.sExtension; }, /** * Get the locale extensions as an array of tokens. * * The leading singleton and the separating dashes are not part of the result. * If there is no extensions section in the locale tag, an empty array is returned. * * @returns {string[]} the individual extension sections * @public */ getExtensionSubtags : function() { return this.sExtension ? this.sExtension.slice(2).split('-') : []; }, /** * Get the locale private use section or <code>null</code>. * * @returns {string} the private use section * @public */ getPrivateUse : function() { return this.sPrivateUse; }, /** * Get the locale private use section as an array of tokens. * * The leading singleton and the separating dashes are not part of the result. * If there is no private use section in the locale tag, an empty array is returned. * * @returns {string[]} the tokens of the private use section * @public */ getPrivateUseSubtags : function() { return this.sPrivateUse ? this.sPrivateUse.slice(2).split('-') : []; }, hasPrivateUseSubtag : function(sSubtag) { assert(sSubtag && sSubtag.match(/^[0-9A-Z]{1,8}$/i), "subtag must be a valid BCP47 private use tag"); return this.getPrivateUseSubtags().indexOf(sSubtag) >= 0; }, toString : function() { return join( this.sLanguage, this.sScript, this.sRegion, this.sVariant, this.sExtension, this.sPrivateUse); }, /** * @returns {string} the modern language tag * @private * @ui5-restricted sap.ui.core.Configuration */ toLanguageTag : function() { var sLanguage = this.getModernLanguage(); var sScript = this.sScript; // special case for "sr_Latn" language: "sh" should then be used // This method is used to set the Accept-Language HTTP Header for ODataModel // requests and .hdbtextbundle resource bundles. // It has to remain backward compatible if (sLanguage === "sr" && sScript === "Latn") { sLanguage = "sh"; sScript = null; } return join( sLanguage, sScript, this.sRegion, this.sVariant, this.sExtension, this.sPrivateUse); }, /** * @returns {string} the modern language * @private * @ui5-restricted sap.ui.core.LocaleData */ getModernLanguage: function() { return M_ISO639_OLD_TO_NEW[this.sLanguage] || this.sLanguage; }, /** * Best guess to get a proper SAP Logon Language for this locale. * * Conversions taken into account: * <ul> * <li>use the language part only</li> * <li>convert old ISO639 codes to newer ones (e.g. 'iw' to 'he')</li> * <li>for Chinese, map 'Traditional Chinese' or region 'TW' to SAP proprietary code 'zf'</li> * <li>map private extensions x-saptrc, x-sappsd and saprigi to SAP pseudo languages '1Q', '2Q' and '3Q'</li> * <li>remove ext. language sub tags</li> * <li>convert to uppercase</li> * </ul> * * Note that the conversion also returns a result for languages that are not * supported by the default set of SAP languages. This method has no knowledge * about the concrete languages of any given backend system. * * @returns {string} a language code that should * @public * @since 1.17.0 * @deprecated As of 1.44, use {@link sap.ui.core.Configuration#getSAPLogonLanguage} instead * as that class allows to configure an SAP Logon language. */ getSAPLogonLanguage : function() { return this._getSAPLogonLanguage(); }, /** * Best guess to get a proper SAP Logon Language for this locale. * * Conversions taken into account: * <ul> * <li>use the language part only</li> * <li>convert old ISO639 codes to newer ones (e.g. 'iw' to 'he')</li> * <li>for Chinese, map 'Traditional Chinese' or region 'TW' to SAP proprietary code 'zf'</li> * <li>map private extensions x-saptrc, x-sappsd and saprigi to SAP pseudo languages '1Q', '2Q' and '3Q'</li> * <li>remove ext. language sub tags</li> * <li>convert to uppercase</li> * </ul> * * Note that the conversion also returns a result for languages that are not * supported by the default set of SAP languages. This method has no knowledge * about the concrete languages of any given backend system. * * @returns {string} a language code that should * @private * @ui5-restricted sap.ui.core.Configuration **/ _getSAPLogonLanguage : function() { var sLanguage = this.sLanguage || ""; // cut off any ext. language sub tags if ( sLanguage.indexOf("-") >= 0 ) { sLanguage = sLanguage.slice(0, sLanguage.indexOf("-")); } // convert to new ISO codes sLanguage = M_ISO639_OLD_TO_NEW[sLanguage] || sLanguage; // handle special case for Chinese: region TW implies Traditional Chinese (ZF) if ( sLanguage === "zh" && !this.sScript && this.sRegion === "TW" ) { return "ZF"; } return ( M_LOCALE_TO_ABAP_LANGUAGE[join(sLanguage, this.sScript)] || M_LOCALE_TO_ABAP_LANGUAGE[join(sLanguage, this.sRegion)] || M_LOCALE_TO_ABAP_LANGUAGE[getPseudoLanguageTag(this.sPrivateUse)] || sLanguage.toUpperCase() ); }, /** * * @returns {sap.ui.core.CalendarType} The preferred Calendar type. * @private * @ui5-restricted sap.ui.core */ getPreferredCalendarType: function() { return Locale._mPreferredCalendar[this.getLanguage() + "-" + this.getRegion()] || Locale._mPreferredCalendar[this.getLanguage()] || Locale._mPreferredCalendar["default"]; } }); /* * Maps wellknown private use extensions to pseudo language tags. */ function getPseudoLanguageTag(sPrivateUse) { if ( sPrivateUse ) { var m = /-(saptrc|sappsd|saprigi)(?:-|$)/i.exec(sPrivateUse); return m && "en-US-x-" + m[1].toLowerCase(); } } var M_ISO639_OLD_TO_NEW = { "iw" : "he", "ji" : "yi" }; // Note: keys must be uppercase var M_ABAP_LANGUAGE_TO_LOCALE = { "ZH" : "zh-Hans", "ZF" : "zh-Hant", "SH" : "sr-Latn", "6N" : "en-GB", "1P" : "pt-PT", "1X" : "es-MX", "3F" : "fr-CA", "1Q" : "en-US-x-saptrc", "2Q" : "en-US-x-sappsd", "3Q" : "en-US-x-saprigi" }; var M_LOCALE_TO_ABAP_LANGUAGE = inverse(M_ABAP_LANGUAGE_TO_LOCALE); /** * Helper to analyze and parse designtime (aka buildtime) variables * * At buildtime, the build can detect a pattern like $some-variable-name:some-value$ * and replace 'some-value' with a value determined at buildtime (here: the actual list of locales). * * At runtime, this method removes the surrounding pattern ('$some-variable-name:' and '$') and leaves only the 'some-value'. * Additionally, this value is parsed as a comma-separated list (because this is the only use case here). * * The mimic of the comments is borrowed from the CVS (Concurrent Versions System), * see http://web.mit.edu/gnu/doc/html/cvs_17.html. * * If no valid <code>sValue</code> is given, <code>null</code> is returned * * @param {string} sValue The raw designtime property e.g. $cldr-rtl-locales:ar,fa,he$ * @returns {string[]|null} The designtime property e.g. ['ar', 'fa', 'he'] * @private */ function getDesigntimePropertyAsArray(sValue) { var m = /\$([-a-z0-9A-Z._]+)(?::([^$]*))?\$/.exec(sValue); return (m && m[2]) ? m[2].split(/,/) : null; } /** * A list of locales for which the CLDR specifies "right-to-left" * as the character orientation. * * The string literal below is substituted during the build. * The value is determined from the CLDR JSON files which are * bundled with the UI5 runtime. */ var A_RTL_LOCALES = getDesigntimePropertyAsArray("$cldr-rtl-locales:ar,fa,he$") || []; /** * A list of locales for which CLDR data is bundled with the UI5 runtime. * @private */ Locale._cldrLocales = getDesigntimePropertyAsArray("$cldr-locales:ar,ar_EG,ar_SA,bg,ca,cy,cs,da,de,de_AT,de_CH,el,el_CY,en,en_AU,en_GB,en_HK,en_IE,en_IN,en_NZ,en_PG,en_SG,en_ZA,es,es_AR,es_BO,es_CL,es_CO,es_MX,es_PE,es_UY,es_VE,et,fa,fi,fr,fr_BE,fr_CA,fr_CH,fr_LU,he,hi,hr,hu,id,it,it_CH,ja,kk,ko,lt,lv,ms,nb,nl,nl_BE,pl,pt,pt_PT,ro,ru,ru_UA,sk,sl,sr,sr_Latn,sv,th,tr,uk,vi,zh_CN,zh_HK,zh_SG,zh_TW$"); /** * A map of preferred Calendar types according to the language. * @private */ Locale._mPreferredCalendar = { "ar-SA": CalendarType.Islamic, "fa": CalendarType.Persian, "th": CalendarType.Buddhist, "default": CalendarType.Gregorian }; /** * List of locales for which translated texts have been bundled with the UI5 runtime. * @private */ Locale._coreI18nLocales = getDesigntimePropertyAsArray("$core-i18n-locales:,ar,bg,ca,cs,da,de,el,en,en_GB,es,es_MX,et,fi,fr,hi,hr,hu,it,iw,ja,kk,ko,lt,lv,ms,nl,no,pl,pt,ro,ru,sh,sk,sl,sv,th,tr,uk,vi,zh_CN,zh_TW$"); /** * Checks whether the given language tag implies a character orientation * of 'right-to-left' ('RTL'). * * The implementation of this method and the configuration above assume * that when a language (e.g. 'ar') is marked as 'RTL', then all language/region * combinations for that language (e.g. 'ar_SA') will be 'RTL' as well, * even if the combination is not mentioned in the above configuration. * There is no means to define RTL=false for a language/region, when RTL=true for * the language alone. * * As of 3/2013 this is true for all locales/regions supported by UI5. * * @param {string|sap.ui.core.Locale} vLanguage Locale or language to check * @returns {boolean} <code>true</code> if <code>vLanguage</code> implies RTL, * otherwise <code>false</code> * @private */ Locale._impliesRTL = function(vLanguage) { var oLocale = vLanguage instanceof Locale ? vLanguage : new Locale(vLanguage); var sLanguage = oLocale.getLanguage() || ""; sLanguage = (sLanguage && M_ISO639_OLD_TO_NEW[sLanguage]) || sLanguage; var sRegion = oLocale.getRegion() || ""; if ( sRegion && A_RTL_LOCALES.indexOf(sLanguage + "_" + sRegion) >= 0 ) { return true; } return A_RTL_LOCALES.indexOf(sLanguage) >= 0; }; /** * Retrieves a Locale for the given SAP logon language or BCP47 tag. * * @param {string} sSAPLogonLanguage * A SAP logon language, e.g. "ZF" or a BCP47 language tag * @returns {sap.ui.core.Locale | undefined} * The Locale or <code>undefined</code>, if the given string is neither a known * SAP logon language nor a valid BCP47 tag * @private * @ui5-restricted sap.ui.core.Configuration */ Locale.fromSAPLogonLanguage = function (sSAPLogonLanguage) { if (sSAPLogonLanguage && typeof sSAPLogonLanguage === 'string') { sSAPLogonLanguage = M_ABAP_LANGUAGE_TO_LOCALE[sSAPLogonLanguage.toUpperCase()] || sSAPLogonLanguage; try { return new Locale(sSAPLogonLanguage); } catch (e) { // ignore } } }; function join() { return Array.prototype.filter.call(arguments, Boolean).join("-"); } function inverse(obj) { return Object.keys(obj).reduce(function(inv, key) { inv[obj[key]] = key; return inv; }, {}); } return Locale; });