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