1 Locale and Parameter Negotiation

The constructors for the objects providing locale sensitive services, Collator, NumberFormat, DateTimeFormat, PluralRules, and RelativeTimeFormat, use a common pattern to negotiate the requests represented by the locales and options arguments against the actual capabilities of their implementations. The common behaviour is described here in terms of internal slots describing the capabilities and of abstract operations using these internal slots.

1.1 Internal slots of Service Constructors

The constructors Intl.Collator, Intl.NumberFormat, Intl.DateTimeFormat, Intl.PluralRules, and Intl.RelativeTimeFormat have the following internal slots:

  • [[AvailableLocales]] is a List that contains structurally valid () and canonicalized () Unicode BCP 47 locale identifiers identifying the locales for which the implementation provides the functionality of the constructed objects. Language tags on the list must not have a Unicode locale extension sequence. The list must include the value returned by the DefaultLocale abstract operation (), and must not include duplicates. Implementations must include in [[AvailableLocales]] locales that can serve as fallbacks in the algorithm used to resolve locales (see 1.2.7). For example, implementations that provide a "de-DE" locale must include a "de" locale that can serve as a fallback for requests such as "de-AT" and "de-CH". For locales that in current usage would include a script subtag (such as Chinese locales), old-style language tags without script subtags must be included such that, for example, requests for "zh-TW" and "zh-HK" lead to output in traditional Chinese rather than the default simplified Chinese. The ordering of the locales within [[AvailableLocales]] is irrelevant.
  • [[RelevantExtensionKeys]] is a List of keys of the language tag extensions defined in Unicode Technical Standard 35 that are relevant for the functionality of the constructed objects.
  • [[SortLocaleData]] and [[SearchLocaleData]] (for Intl.Collator) and [[LocaleData]] (for Intl.NumberFormat, Intl.DateTimeFormat, Intl.PluralRules, and Intl.RelativeTimeFormat) are records that have fields for each locale contained in [[AvailableLocales]]. The value of each of these fields must be a record that has fields for each key contained in [[RelevantExtensionKeys]]. The value of each of these fields must be a non-empty list of those values defined in Unicode Technical Standard 35 for the given key that are supported by the implementation for the given locale, with the first element providing the default value.

EXAMPLE An implementation of DateTimeFormat might include the language tag "th" in its [[AvailableLocales]] internal slot, and must (according to ) include the key "ca" in its [[RelevantExtensionKeys]] internal slot. For Thai, the "buddhist" calendar is usually the default, but an implementation might also support the calendars "gregory", "chinese", and "islamicc" for the locale "th". The [[LocaleData]] internal slot would therefore at least include {[[th]]: {[[ca]]: « "buddhist", "gregory", "chinese", "islamicc" »}}.

1.2 Abstract Operations

Where the following abstract operations take an availableLocales argument, it must be an [[AvailableLocales]] List as specified in 1.1.

1.2.1 CanonicalizeLocaleList ( locales )

The abstract operation CanonicalizeLocaleList takes the following steps:

  1. If locales is undefined, then
    1. Return a new empty List.
  2. Let seen be a new empty List.
  3. If Type(locales) is String or Type(locales) is Object and locales has an [[InitializedLocale]] internal slot, then
    1. Let O be CreateArrayFromListlocales »).
  4. Else,
    1. Let O be ? ToObject(locales).
  5. Let len be ? ToLength(? Get(O, "length")).
  6. Let k be 0.
  7. Repeat, while k < len
    1. Let Pk be ToString(k).
    2. Let kPresent be ? HasProperty(O, Pk).
    3. If kPresent is true, then
      1. Let kValue be ? Get(O, Pk).
      2. If Type(kValue) is not String or Object, throw a TypeError exception.
      3. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
        1. Let tag be kValue.[[Locale]].
      4. Else,
        1. Let tag be ? ToString(kValue).
      5. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
      6. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
      7. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
    4. Increase k by 1.
  8. Return seen.
Note 1
Non-normative summary: The abstract operation interprets the locales argument as an array and copies its elements into a List, validating the elements as structurally valid language tags and canonicalizing them, and omitting duplicates.
Note 2
Requiring kValue to be a String or Object means that the Number value NaN will not be interpreted as the language tag "nan", which stands for Min Nan Chinese.

1.2.2 BestAvailableLocale ( availableLocales, locale )

The BestAvailableLocale abstract operation compares the provided argument locale, which must be a String value with a structurally valid and canonicalized Unicode BCP 47 locale identifier, against the locales in availableLocales and returns either the longest non-empty prefix of locale that is an element of availableLocales, or undefined if there is no such element. It uses the fallback mechanism of RFC 4647, section 3.4. The following steps are taken:

  1. Let candidate be locale.
  2. Repeat,
    1. If availableLocales contains an element equal to candidate, return candidate.
    2. Let pos be the character index of the last occurrence of "-" (U+002D) within candidate. If that character does not occur, return undefined.
    3. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, decrease pos by 2.
    4. Let candidate be the substring of candidate from position 0, inclusive, to position pos, exclusive.

1.2.3 LookupMatcher ( availableLocales, requestedLocales )

The LookupMatcher abstract operation compares requestedLocales, which must be a List as returned by CanonicalizeLocaleList, against the locales in availableLocales and determines the best available language to meet the request. The following steps are taken:

  1. Let result be a new Record.
  2. For each element locale of requestedLocales in List order, do
    1. Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences removed.
    2. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
    3. If availableLocale is not undefined, then
      1. Set result.[[locale]] to availableLocale.
      2. If locale and noExtensionsLocale are not the same String value, then
        1. Let extension be the String value consisting of the first substring of locale that is a Unicode locale extension sequence.
        2. Set result.[[extension]] to extension.
      3. Return result.
  3. Let defLocale be DefaultLocale().
  4. Set result.[[locale]] to defLocale.
  5. Return result.
Note
The algorithm is based on the Lookup algorithm described in RFC 4647 section 3.4, but options specified through Unicode locale extension sequences are ignored in the lookup. Information about such subsequences is returned separately. The abstract operation returns a record with a [[locale]] field, whose value is the language tag of the selected locale, which must be an element of availableLocales. If the language tag of the request locale that led to the selected locale contained a Unicode locale extension sequence, then the returned record also contains an [[extension]] field whose value is the first Unicode locale extension sequence within the request locale language tag.

1.2.4 BestFitMatcher ( availableLocales, requestedLocales )

The BestFitMatcher abstract operation compares requestedLocales, which must be a List as returned by CanonicalizeLocaleList, against the locales in availableLocales and determines the best available language to meet the request. The algorithm is implementation dependent, but should produce results that a typical user of the requested locales would perceive as at least as good as those produced by the LookupMatcher abstract operation. Options specified through Unicode locale extension sequences must be ignored by the algorithm. Information about such subsequences is returned separately. The abstract operation returns a record with a [[locale]] field, whose value is the language tag of the selected locale, which must be an element of availableLocales. If the language tag of the request locale that led to the selected locale contained a Unicode locale extension sequence, then the returned record also contains an [[extension]] field whose value is the first Unicode locale extension sequence within the request locale language tag.

1.2.5 UnicodeExtensionComponents( extension )

The UnicodeExtensionComponents abstract operation returns the attributes and keywords from extension, which must be a Unicode locale extension sequence. If an attribute or a keyword occurs multiple times in extension, only the first occurence is returned. The following steps are taken:

  1. Let attributes be the empty List.
  2. Let keywords be the empty List.
  3. Let isKeyword be false.
  4. Let size be the number of elements in extension.
  5. Let k be 3.
  6. Repeat, while k < size
    1. Let e be ! Call(%StringProto_indexOf%, extension, « "-", k »).
    2. If e = -1, let len be size - k; else let len be e - k.
    3. Let subtag be the String value equal to the substring of extension consisting of the code units at indices k (inclusive) through k + len (exclusive).
    4. If isKeyword is false, then
      1. If len ≠ 2 and subtag is not an element of attributes, then
        1. Append subtag to attributes.
    5. Else,
      1. If len = 2, then
        1. If keywords does not contain an element whose [[Key]] is the same as key, then
          1. Append the Record{[[Key]]: key, [[Value]]: value} to keywords.
      2. Else,
        1. If value is not the empty String, then
          1. Let value be the string-concatenation of value and "-".
        2. Let value be the string-concatenation of value and subtag.
    6. If len = 2, then
      1. Let isKeyword be true.
      2. Let key be subtag.
      3. Let value be the empty String.
    7. Let k be k + len + 1.
  7. If isKeyword is true, then
    1. If keywords does not contain an element whose [[Key]] is the same as key, then
      1. Append the Record{[[Key]]: key, [[Value]]: value} to keywords.
  8. Return the Record{[[Attributes]]: attributes, [[Keywords]]: keywords}.

1.2.6 InsertUnicodeExtensionAndCanonicalize( locale, extension )

The InsertUnicodeExtensionAndCanonicalize abstract operation inserts extension, which must be a Unicode locale extension sequence, into locale, which must be a String value with a structurally valid and canonicalized Unicode BCP 47 locale identifier. The following steps are taken:

The following algorithm refers to UTS 35's Unicode Language and Locale Identifiers grammar.

  1. Assert: locale does not contain a substring that is a Unicode locale extension sequence.
  2. Assert: extension is a Unicode locale extension sequence.
  3. Assert: tag matches the unicode_locale_id production.
  4. Let privateIndex be ! Call(%StringProto_indexOf%, locale, « "-x-" »).
  5. If privateIndex = -1, then
    1. Let locale be the string-concatenation of locale and extension.
  6. Else,
    1. Let preExtension be the substring of locale from position 0, inclusive, to position privateIndex, exclusive.
    2. Let postExtension be the substring of locale from position privateIndex to the end of the string.
    3. Let locale be the string-concatenation of preExtension, extension, and postExtension.
  7. Assert: IsStructurallyValidLanguageTag(locale) is true.
  8. Return ! CanonicalizeUnicodeLocaleId(locale).

1.2.7 ResolveLocale ( availableLocales, requestedLocales, options, relevantExtensionKeys, localeData )

The ResolveLocale abstract operation compares a BCP 47 language priority list requestedLocales against the locales in availableLocales and determines the best available language to meet the request. availableLocales, requestedLocales, and relevantExtensionKeys must be provided as List values, options and localeData as Records.

The following steps are taken:

  1. Let matcher be options.[[localeMatcher]].
  2. If matcher is "lookup", then
    1. Let r be LookupMatcher(availableLocales, requestedLocales).
  3. Else,
    1. Let r be BestFitMatcher(availableLocales, requestedLocales).
  4. Let foundLocale be r.[[locale]].
  5. Let result be a new Record.
  6. Set result.[[dataLocale]] to foundLocale.
  7. If r has an [[extension]] field, then
    1. Let components be ! UnicodeExtensionComponents(r.[[extension]]).
    2. Let keywords be components.[[Keywords]].
  8. Let supportedExtension be "-u".
  9. For each element key of relevantExtensionKeys in List order, do
    1. Let foundLocaleData be localeData.[[<foundLocale>]].
    2. Assert: Type(foundLocaleData) is Record.
    3. Let keyLocaleData be foundLocaleData.[[<key>]].
    4. Assert: Type(keyLocaleData) is List.
    5. Let value be keyLocaleData[0].
    6. Assert: Type(value) is either String or Null.
    7. Let supportedExtensionAddition be "".
    8. If r has an [[extension]] field, then
      1. If keywords contains an element whose [[Key]] is the same as key, then
        1. Let entry be the element of keywords whose [[Key]] is the same as key.
        2. Let requestedValue be entry.[[Value]].
        3. If requestedValue is not the empty String, then
          1. If keyLocaleData contains requestedValue, then
            1. Let value be requestedValue.
            2. Let supportedExtensionAddition be the string-concatenation of "-", key, "-", and value.
        4. Else if keyLocaleData contains "true", then
          1. Let value be "true".
          2. Let supportedExtensionAddition be the string-concatenation of "-" and key.
    9. If options has a field [[<key>]], then
      1. Let optionsValue be options.[[<key>]].
      2. Assert: Type(optionsValue) is either String, Undefined, or Null.
      3. If Type(optionsValue) is String, then
        1. Let optionsValue be the string optionsValue after performing the algorithm steps to transform Unicode extension values to canonical syntax per Unicode Technical Standard #35 LDML § 3.2.1 Canonical Unicode Locale Identifiers, treating key as ukey and optionsValue as uvalue productions.
        2. Let optionsValue be the string optionsValue after performing the algorithm steps to replace Unicode extension values with their canonical form per Unicode Technical Standard #35 LDML § 3.2.1 Canonical Unicode Locale Identifiers, treating key as ukey and optionsValue as uvalue productions.
        3. If optionsValue is the empty String, then
          1. Let optionsValue be "true".
      4. If keyLocaleData contains optionsValue, then
        1. If SameValue(optionsValue, value) is false, then
          1. Let value be optionsValue.
          2. Let supportedExtensionAddition be "".
    10. Set result.[[<key>]] to value.
    11. Append supportedExtensionAddition to supportedExtension.
  10. If the number of elements in supportedExtension is greater than 2, then
    1. Let foundLocale be InsertUnicodeExtensionAndCanonicalize(foundLocale, supportedExtension).
  11. Set result.[[locale]] to foundLocale.
  12. Return result.
Note
Non-normative summary: Two algorithms are available to match the locales: the Lookup algorithm described in RFC 4647 section 3.4, and an implementation dependent best-fit algorithm. Independent of the locale matching algorithm, options specified through Unicode locale extension sequences are negotiated separately, taking the caller's relevant extension keys and locale data as well as client-provided options into consideration. The abstract operation returns a record with a [[locale]] field whose value is the language tag of the selected locale, and fields for each key in relevantExtensionKeys providing the selected value for that key.

1.2.8 LookupSupportedLocales ( availableLocales, requestedLocales )

The LookupSupportedLocales abstract operation returns the subset of the provided BCP 47 language priority list requestedLocales for which availableLocales has a matching locale when using the BCP 47 Lookup algorithm. Locales appear in the same order in the returned list as in requestedLocales. The following steps are taken:

  1. Let subset be a new empty List.
  2. For each element locale of requestedLocales in List order, do
    1. Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences removed.
    2. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
    3. If availableLocale is not undefined, append locale to the end of subset.
  3. Return subset.

1.2.9 BestFitSupportedLocales ( availableLocales, requestedLocales )

The BestFitSupportedLocales abstract operation returns the subset of the provided BCP 47 language priority list requestedLocales for which availableLocales has a matching locale when using the Best Fit Matcher algorithm. Locales appear in the same order in the returned list as in requestedLocales. The steps taken are implementation dependent.

1.2.10 SupportedLocales ( availableLocales, requestedLocales, options )

The SupportedLocales abstract operation returns the subset of the provided BCP 47 language priority list requestedLocales for which availableLocales has a matching locale. Two algorithms are available to match the locales: the Lookup algorithm described in RFC 4647 section 3.4, and an implementation dependent best-fit algorithm. Locales appear in the same order in the returned list as in requestedLocales. The following steps are taken:

  1. If options is not undefined, then
    1. Let options be ? ToObject(options).
    2. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
  2. Else, let matcher be "best fit".
  3. If matcher is "best fit", then
    1. Let supportedLocales be BestFitSupportedLocales(availableLocales, requestedLocales).
  4. Else,
    1. Let supportedLocales be LookupSupportedLocales(availableLocales, requestedLocales).
  5. Return CreateArrayFromList(supportedLocales).

1.2.11 GetOption ( options, property, type, values, fallback )

The abstract operation GetOption extracts the value of the property named property from the provided options object, converts it to the required type, checks whether it is one of a List of allowed values, and fills in a fallback value if necessary. If values is undefined, there is no fixed set of values and any is permitted.

  1. Let value be ? Get(options, property).
  2. If value is not undefined, then
    1. Assert: type is "boolean" or "string".
    2. If type is "boolean", then
      1. Let value be ToBoolean(value).
    3. If type is "string", then
      1. Let value be ? ToString(value).
    4. If values is not undefined, then
      1. If values does not contain an element equal to value, throw a RangeError exception.
    5. Return value.
  3. Else, return fallback.

1.2.12 GetStringOrBooleanOption ( options, property, values, trueValue, falsyValue, fallback )

The abstract operation GetStringOrBooleanOption extracts the value of the property named property from the provided options object. It returns either trueValue, falsyValue, fallback, or one of the elements of the List values. The following steps are taken:

  1. Let value be ? Get(options, property).
  2. If value is undefined, return fallback.
  3. If value is true, return trueValue.
  4. Let valueBoolean be ToBoolean(value).
  5. If valueBoolean is false, return falsyValue.
  6. Let value be ? ToString(value).
  7. If values does not contain an element equal to value, return fallback.
  8. Return value.

1.2.13 DefaultNumberOption ( value, minimum, maximum, fallback )

The abstract operation DefaultNumberOption converts value to a Number value, checks whether it is in the allowed range, and fills in a fallback value if necessary.

  1. If value is not undefined, then
    1. Let value be ? ToNumber(value).
    2. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
    3. Return floor(value).
  2. Else, return fallback.

1.2.14 GetNumberOption ( options, property, minimum, maximum, fallback )

The abstract operation GetNumberOption extracts the value of the property named property from the provided options object, converts it to a Number value, checks whether it is in the allowed range, and fills in a fallback value if necessary.

  1. Let value be ? Get(options, property).
  2. Return ? DefaultNumberOption(value, minimum, maximum, fallback).

1.2.15 PartitionPattern ( pattern )

The PartitionPattern abstract operation is called with argument pattern. This abstract operation parses an abstract pattern string into a list of Records with two fields, [[Type]] and [[Value]]. The [[Value]] field will be a String value if [[Type]] is "literal", and undefined otherwise. The syntax of the abstract pattern strings is an implementation detail and is not exposed to users of ECMA-402. The following steps are taken:

  1. Let result be a new empty List.
  2. Let beginIndex be ! Call(%StringProto_indexOf%, pattern, « "{", 0 »).
  3. Let endIndex be 0.
  4. Let nextIndex be 0.
  5. Let length be the number of code units in pattern.
  6. Repeat, while beginIndex is an integer index into pattern
    1. Set endIndex to ! Call(%StringProto_indexOf%, pattern, « "}", beginIndex »).
    2. Assert: endIndex is greater than beginIndex.
    3. If beginIndex is greater than nextIndex, then
      1. Let literal be a substring of pattern from position nextIndex, inclusive, to position beginIndex, exclusive.
      2. Append a new Record { [[Type]]: "literal", [[Value]]: literal } as the last element of the list result.
    4. Let p be the substring of pattern from position beginIndex, exclusive, to position endIndex, exclusive.
    5. Append a new Record { [[Type]]: p, [[Value]]: undefined } as the last element of the list result.
    6. Set nextIndex to endIndex + 1.
    7. Set beginIndex to ! Call(%StringProto_indexOf%, pattern, « "{", nextIndex »).
  7. If nextIndex is less than length, then
    1. Let literal be the substring of pattern from position nextIndex, inclusive, to position length, exclusive.
    2. Append a new Record { [[Type]]: "literal", [[Value]]: literal } as the last element of the list result.
  8. Return result.