|
1 | | -const languageCodeRe = "([a-z]{2,3}|\\*)"; |
2 | | -const scriptCodeRe = "(?:-([a-z]{4}|\\*))"; |
3 | | -const regionCodeRe = "(?:-([a-z]{2}|\\*))"; |
4 | | -const variantCodeRe = "(?:-(([0-9][a-z0-9]{3}|[a-z0-9]{5,8})|\\*))"; |
| 1 | +export class LocaleWrapper extends Intl.Locale { |
| 2 | + variants?: string; |
5 | 3 |
|
6 | | -/** |
7 | | - * Regular expression splitting locale id into four pieces: |
8 | | - * |
9 | | - * Example: `en-Latn-US-macos` |
10 | | - * |
11 | | - * language: en |
12 | | - * script: Latn |
13 | | - * region: US |
14 | | - * variant: macos |
15 | | - * |
16 | | - * It can also accept a range `*` character on any position. |
17 | | - */ |
18 | | -const localeRe = new RegExp( |
19 | | - `^${languageCodeRe}${scriptCodeRe}?${regionCodeRe}?${variantCodeRe}?$`, |
20 | | - "i" |
21 | | -); |
22 | | - |
23 | | -export class Locale { |
24 | | - isWellFormed: boolean; |
25 | | - language?: string; |
26 | | - script?: string; |
27 | | - region?: string; |
28 | | - variant?: string; |
29 | | - |
30 | | - /** |
31 | | - * Parses a locale id using the localeRe into an array with four elements. |
32 | | - * |
33 | | - * If the second argument `range` is set to true, it places range `*` char |
34 | | - * in place of any missing piece. |
35 | | - * |
36 | | - * It also allows skipping the script section of the id, so `en-US` is |
37 | | - * properly parsed as `en-*-US-*`. |
38 | | - */ |
39 | 4 | constructor(locale: string) { |
40 | | - const result = localeRe.exec(locale.replace(/_/g, "-")); |
41 | | - if (!result) { |
42 | | - this.isWellFormed = false; |
43 | | - return; |
44 | | - } |
45 | | - |
46 | | - let [, language, script, region, variant] = result; |
47 | | - |
48 | | - if (language) { |
49 | | - this.language = language.toLowerCase(); |
50 | | - } |
51 | | - if (script) { |
52 | | - this.script = script[0].toUpperCase() + script.slice(1); |
53 | | - } |
54 | | - if (region) { |
55 | | - this.region = region.toUpperCase(); |
56 | | - } |
57 | | - this.variant = variant; |
58 | | - this.isWellFormed = true; |
59 | | - } |
60 | | - |
61 | | - isEqual(other: Locale): boolean { |
62 | | - return ( |
63 | | - this.language === other.language && |
64 | | - this.script === other.script && |
65 | | - this.region === other.region && |
66 | | - this.variant === other.variant |
67 | | - ); |
68 | | - } |
69 | | - |
70 | | - matches(other: Locale, thisRange = false, otherRange = false): boolean { |
71 | | - return ( |
72 | | - (this.language === other.language || |
73 | | - (thisRange && this.language === undefined) || |
74 | | - (otherRange && other.language === undefined)) && |
75 | | - (this.script === other.script || |
76 | | - (thisRange && this.script === undefined) || |
77 | | - (otherRange && other.script === undefined)) && |
78 | | - (this.region === other.region || |
79 | | - (thisRange && this.region === undefined) || |
80 | | - (otherRange && other.region === undefined)) && |
81 | | - (this.variant === other.variant || |
82 | | - (thisRange && this.variant === undefined) || |
83 | | - (otherRange && other.variant === undefined)) |
84 | | - ); |
85 | | - } |
86 | | - |
87 | | - toString(): string { |
88 | | - return [this.language, this.script, this.region, this.variant] |
89 | | - .filter(part => part !== undefined) |
90 | | - .join("-"); |
91 | | - } |
92 | | - |
93 | | - clearVariants(): void { |
94 | | - this.variant = undefined; |
95 | | - } |
96 | | - |
97 | | - clearRegion(): void { |
98 | | - this.region = undefined; |
99 | | - } |
100 | | - |
101 | | - addLikelySubtags(): boolean { |
102 | | - const newLocale = getLikelySubtagsMin(this.toString().toLowerCase()); |
103 | | - if (newLocale) { |
104 | | - this.language = newLocale.language; |
105 | | - this.script = newLocale.script; |
106 | | - this.region = newLocale.region; |
107 | | - this.variant = newLocale.variant; |
108 | | - return true; |
| 5 | + let tag = locale |
| 6 | + .replace(/_/g, "-") |
| 7 | + .replace(/^\*/, "und") |
| 8 | + .replace(/-\*/g, ""); |
| 9 | + |
| 10 | + super(tag); |
| 11 | + |
| 12 | + if (!("variants" in this)) { |
| 13 | + // Available on Firefox 141 & later |
| 14 | + let lsrTagLength = this.language.length; |
| 15 | + if (this.script) lsrTagLength += this.script.length + 1; |
| 16 | + if (this.region) lsrTagLength += this.region.length + 1; |
| 17 | + |
| 18 | + if (tag.length > lsrTagLength) { |
| 19 | + let unicodeExtStart: number | undefined = tag.search(/-[a-zA-Z]-/); |
| 20 | + if (unicodeExtStart === -1) unicodeExtStart = undefined; |
| 21 | + this.variants = tag.substring(lsrTagLength + 1, unicodeExtStart); |
| 22 | + } |
109 | 23 | } |
110 | | - return false; |
111 | 24 | } |
112 | | -} |
113 | 25 |
|
114 | | -/** |
115 | | - * Below is a manually a list of likely subtags corresponding to Unicode |
116 | | - * CLDR likelySubtags list. |
117 | | - * This list is curated by the maintainers of Project Fluent and is |
118 | | - * intended to be used in place of the full likelySubtags list in use cases |
119 | | - * where full list cannot be (for example, due to the size). |
120 | | - * |
121 | | - * This version of the list is based on CLDR 30.0.3. |
122 | | - */ |
123 | | -const likelySubtagsMin: Record<string, string> = { |
124 | | - ar: "ar-arab-eg", |
125 | | - "az-arab": "az-arab-ir", |
126 | | - "az-ir": "az-arab-ir", |
127 | | - be: "be-cyrl-by", |
128 | | - da: "da-latn-dk", |
129 | | - el: "el-grek-gr", |
130 | | - en: "en-latn-us", |
131 | | - fa: "fa-arab-ir", |
132 | | - ja: "ja-jpan-jp", |
133 | | - ko: "ko-kore-kr", |
134 | | - pt: "pt-latn-br", |
135 | | - sr: "sr-cyrl-rs", |
136 | | - "sr-ru": "sr-latn-ru", |
137 | | - sv: "sv-latn-se", |
138 | | - ta: "ta-taml-in", |
139 | | - uk: "uk-cyrl-ua", |
140 | | - zh: "zh-hans-cn", |
141 | | - "zh-hant": "zh-hant-tw", |
142 | | - "zh-hk": "zh-hant-hk", |
143 | | - "zh-mo": "zh-hant-mo", |
144 | | - "zh-tw": "zh-hant-tw", |
145 | | - "zh-gb": "zh-hant-gb", |
146 | | - "zh-us": "zh-hant-us", |
147 | | -}; |
148 | | - |
149 | | -const regionMatchingLangs = [ |
150 | | - "az", |
151 | | - "bg", |
152 | | - "cs", |
153 | | - "de", |
154 | | - "es", |
155 | | - "fi", |
156 | | - "fr", |
157 | | - "hu", |
158 | | - "it", |
159 | | - "lt", |
160 | | - "lv", |
161 | | - "nl", |
162 | | - "pl", |
163 | | - "ro", |
164 | | - "ru", |
165 | | -]; |
166 | | - |
167 | | -function getLikelySubtagsMin(loc: string): Locale | null { |
168 | | - if (Object.prototype.hasOwnProperty.call(likelySubtagsMin, loc)) { |
169 | | - return new Locale(likelySubtagsMin[loc]); |
170 | | - } |
171 | | - const locale = new Locale(loc); |
172 | | - if (locale.language && regionMatchingLangs.includes(locale.language)) { |
173 | | - locale.region = locale.language.toUpperCase(); |
174 | | - return locale; |
| 26 | + get language(): string { |
| 27 | + return super.language ?? "und"; |
175 | 28 | } |
176 | | - return null; |
177 | 29 | } |
0 commit comments