1 Locale and Parameter Negotiation

Service constructors use a common pattern to negotiate the requests represented by their locales and options arguments against the actual capabilities of their implementations. That common behaviour is explained here in terms of internal slots describing the capabilities and abstract operations using these internal slots.

1.1 Internal slots of Service Constructors

Each service constructor has the following internal slots:

  • [[AvailableLocales]] is a List that contains structurally valid () and canonicalized () language tags 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 include a script subtag in addition to language and region, the corresponding locale without a script subtag must also be supported; that is, if an implementation recognizes "zh-Hant-TW", it is also expected to recognize "zh-TW". 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 every other service constructor) 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.
Note
For 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 CreateArrayFromList(« locales »).
  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, do
    1. Let noExtensionsLocale be the String value that is locale with any 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 substring of the Unicode locale extension sequence within locale.
        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 substring of the 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 substring of the 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 String value whose contents are 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 a new empty List.
  2. Let keywords be a new empty List.
  3. Let keyword be undefined.
  4. Let size be the length of extension.
  5. Let k be 3.
  6. Repeat, while k < size,
    1. Let e be StringIndexOf(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 keyword is undefined and len ≠ 2, then
      1. If subtag is not an element of attributes, then
        1. Append subtag to attributes.
    5. Else if len = 2, then
      1. If keyword is not undefined and keywords does not contain an element whose [[Key]] is the same as keyword.[[Key]], then
        1. Append keyword to keywords.
      2. Set keyword to the Record { [[Key]]: subtag, [[Value]]: "" }.
    6. Else,
      1. If keyword.[[Value]] is the empty String, then
        1. Set keyword.[[Value]] to subtag.
      2. Else,
        1. Set keyword.[[Value]] to the string-concatenation of keyword.[[Value]], "-", and subtag.
    7. Let k be k + len + 1.
  7. If keyword is not undefined and keywords does not contain an element whose [[Key]] is the same as keyword.[[Key]], then
    1. Append keyword 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 matches the unicode_locale_id production.
  2. Assert: locale does not contain a Unicode locale extension sequence.
  3. Assert: extension is a Unicode locale extension sequence.
  4. Let privateIndex be StringIndexOf(locale, "-x-", 0).
  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, 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, do
    1. Let noExtensionsLocale be the String value that is locale with any 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. Set options to ? CoerceOptionsToObject(options).
  2. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "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 GetOptionsObject ( options )

The abstract operation GetOptionsObject returns an Object suitable for use with GetOption, either options itself or a default empty Object. It throws a TypeError if options is not undefined and not an Object.

  1. If options is undefined, then
    1. Return OrdinaryObjectCreate(null).
  2. If Type(options) is Object, then
    1. Return options.
  3. Throw a TypeError exception.

1.2.12 CoerceOptionsToObject ( options )

The abstract operation CoerceOptionsToObject coerces options into an Object suitable for use with GetOption, defaulting to an empty Object. Because it coerces non-null primitive values into objects, its use is discouraged for new functionality in favour of GetOptionsObject.

  1. If options is undefined, then
    1. Return OrdinaryObjectCreate(null).
  2. Return ? ToObject(options).

1.2.13 GetOption ( options, property, type, values, default )

The abstract operation GetOption takes arguments options (an Object), property (a property key), type (boolean, number, or string), values (empty or a List of ECMAScript language values), and default (required or an ECMAScript language value). It extracts the value of the specified property of options, converts it to the required type, checks whether it is allowed by values if values is not empty, and substitutes default if the value is undefined. It performs the following steps when called:

  1. Let value be ? Get(options, property).
  2. If value is undefined, then
    1. If default is required, throw a RangeError exception.
    2. Return default.
  3. If type is boolean, then
    1. Set value to ToBoolean(value).
  4. Else if type is number, then
    1. Set value to ? ToNumber(value).
    2. If value is NaN, throw a RangeError exception.
  5. Else,
    1. Assert: type is string.
    2. Set value to ? ToString(value).
  6. If values is not empty and values does not contain an element equal to value, throw a RangeError exception.
  7. Return value.

1.2.14 GetBooleanOrStringNumberFormatOption ( options, property, stringValues, fallback )

The abstract operation GetBooleanOrStringNumberFormatOption takes arguments options (an Object), property (a property key), stringValues (a List of Strings), and fallback (an ECMAScript language value) and returns either a normal completion containing either a Boolean, String, or fallback, or a throw completion. It extracts the value of the property named property from the provided options object. It returns fallback if that value is undefined, true if that value is true, false if that value coerces to false, and otherwise coerces it to a String and returns the result if it is allowed by stringValues. It performs the following steps when called:

  1. Let value be ? Get(options, property).
  2. If value is undefined, return fallback.
  3. If value is true, return true.
  4. If ToBoolean(value) is false, return false.
  5. Let value be ? ToString(value).
  6. If stringValues does not contain value, throw a RangeError exception.
  7. Return value.

1.2.15 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 undefined, return fallback.
  2. Set value to ? ToNumber(value).
  3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
  4. Return floor(value).

1.2.16 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. Assert: Type(options) is Object.
  2. Let value be ? Get(options, property).
  3. Return ? DefaultNumberOption(value, minimum, maximum, fallback).

1.2.17 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 StringIndexOf(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 StringIndexOf(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 StringIndexOf(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.