@fluent/langneg
Version:
Language Negotiation API for Fluent
72 lines (71 loc) • 3 kB
TypeScript
/**
* Negotiates the languages between the list of requested locales against
* a list of available locales.
*
* The algorithm is based on the BCP4647 3.3.2 Extended Filtering algorithm,
* with several modifications:
*
* 1) available locales are treated as ranges
*
* This change allows us to match a more specific request against
* more generic available locale.
*
* For example, if the available locale list provides locale `en`,
* and the requested locale is `en-US`, we treat the available locale as
* a locale that matches all possible english requests.
*
* This means that we expect available locale ID to be as precize as
* the matches they want to cover.
*
* For example, if there is only `sr` available, it's ok to list
* it in available locales. But once the available locales has both,
* Cyrl and Latn variants, the locale IDs should be `sr-Cyrl` and `sr-Latn`
* to avoid any `sr-*` request to match against whole `sr` range.
*
* What it does ([requested] * [available] = [supported]):
*
* ['en-US'] * ['en'] = ['en']
*
* 2) likely subtags from LDML 4.3 Likely Subtags has been added
*
* The most obvious likely subtag that can be computed is a duplication
* of the language field onto region field (`fr` => `fr-FR`).
*
* On top of that, likely subtags may use a list of mappings, that
* allow the algorithm to handle non-obvious matches.
* For example, making sure that we match `en` to `en-US` or `sr` to
* `sr-Cyrl`, while `sr-RU` to `sr-Latn-RU`.
*
* This list can be taken directly from CLDR Supplemental Data.
*
* What it does ([requested] * [available] = [supported]):
*
* ['fr'] * ['fr-FR'] = ['fr-FR']
* ['en'] * ['en-US'] = ['en-US']
* ['sr'] * ['sr-Latn', 'sr-Cyrl'] = ['sr-Cyrl']
*
* 3) variant/region range check has been added
*
* Lastly, the last form of check is against the requested locale ID
* but with the variant/region field replaced with a `*` range.
*
* The rationale here laid out in LDML 4.4 Language Matching:
* "(...) normally the fall-off between the user's languages is
* substantially greated than regional variants."
*
* In other words, if we can't match for the given region, maybe
* we can match for the same language/script but other region, and
* it will in most cases be preferred over falling back on the next
* language.
*
* What it does ([requested] * [available] = [supported]):
*
* ['en-AU'] * ['en-US'] = ['en-US']
* ['sr-RU'] * ['sr-Latn-RO'] = ['sr-Latn-RO'] // sr-RU -> sr-Latn-RU
*
* It works similarly to getParentLocales algo, except that we stop
* after matching against variant/region ranges and don't try to match
* ignoring script ranges. That means that `sr-Cyrl` will never match
* against `sr-Latn`.
*/
export declare function filterMatches(requestedLocales: Array<string>, availableLocales: Array<string>, strategy: string): Array<string>;