Skip to content

Commit 9a925d2

Browse files
zbranieckieemeli
andauthored
feat(langneg)!: Use Intl.Locale internally (#605)
Co-authored-by: Eemeli Aro <eemeli@gmail.com>
1 parent f9ed46e commit 9a925d2

File tree

4 files changed

+192
-278
lines changed

4 files changed

+192
-278
lines changed

fluent-langneg/src/locale.ts

Lines changed: 22 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,29 @@
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;
53

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-
*/
394
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+
}
10923
}
110-
return false;
11124
}
112-
}
11325

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";
17528
}
176-
return null;
17729
}

0 commit comments

Comments
 (0)