Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep Locale class up to standard #601

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion fluent-langneg/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Bug Fixes

* Adjust for updated linter rules ([#590](https://github.com/projectfluent/fluent.js/pull/590))
- Adjust for updated linter rules ([#590](https://github.com/projectfluent/fluent.js/pull/590))

## [@fluent/langneg 0.6.1](https://github.com/projectfluent/fluent.js/compare/@fluent/langneg@0.6.0...@fluent/langneg@0.6.1) (2021-12-21)

Expand Down
74 changes: 67 additions & 7 deletions fluent-langneg/src/locale.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
/* eslint no-magic-numbers: 0 */

const languageCodeRe = "([a-z]{2,3}|\\*)";
const extendedCodeRe = "((?:-(?:[a-z]{3})){1,3})";
const scriptCodeRe = "(?:-([a-z]{4}|\\*))";
const regionCodeRe = "(?:-([a-z]{2}|\\*))";
const regionCodeRe = "(?:-([a-z]{2}|[0-9]{3}|\\*))";
const variantCodeRe = "(?:-(([0-9][a-z0-9]{3}|[a-z0-9]{5,8})|\\*))";
const extensionCodeRe = "(-(?:[a-wy-z])(?:-[a-z]{2,8})+)";
const privateCodeRe = "(?:-x((?:-[a-z]{2,8})+))";

/**
* Regular expression splitting locale id into four pieces:
* Regular expression splitting locale id into multiple pieces:
*
* Example: `en-Latn-US-macos`
*
Expand All @@ -18,16 +21,20 @@ const variantCodeRe = "(?:-(([0-9][a-z0-9]{3}|[a-z0-9]{5,8})|\\*))";
* It can also accept a range `*` character on any position.
*/
const localeRe = new RegExp(
`^${languageCodeRe}${scriptCodeRe}?${regionCodeRe}?${variantCodeRe}?$`,
`^${languageCodeRe}${extendedCodeRe}?${scriptCodeRe}?` +
`${regionCodeRe}?${variantCodeRe}?${extensionCodeRe}*${privateCodeRe}?$`,
"i"
);

export class Locale {
isWellFormed: boolean;
language?: string;
extended: string[] = [];
script?: string;
region?: string;
variant?: string;
extension: Map<string, string> = new Map();
priv?: string;

/**
* Parses a locale id using the localeRe into an array with four elements.
Expand All @@ -45,27 +52,44 @@ export class Locale {
return;
}

let [, language, script, region, variant] = result;
let [, language, extended, script, region, variant, extension, priv] =
result;

if (language) {
this.language = language.toLowerCase();
}
if (extended) {
this.extended = extended.substring(1).toLowerCase().split("-");
}
if (script) {
this.script = script[0].toUpperCase() + script.slice(1);
}
if (region) {
this.region = region.toUpperCase();
}
if (extension) {
for (const [, type, code] of extension.matchAll(
/(?:-([a-wy-z])((?:-[a-z]{2,8})+))/g
)) {
this.extension.set(type.toLowerCase(), code.substring(1).toLowerCase());
}
}
if (priv) {
this.priv = priv.substring(1).toLowerCase();
}
this.variant = variant;
this.isWellFormed = true;
}

isEqual(other: Locale): boolean {
return (
this.language === other.language &&
this.extended.every((v, i) => v === other.extended[i]) &&
this.script === other.script &&
this.region === other.region &&
this.variant === other.variant
this.variant === other.variant &&
compareMap(this.extension, other.extension) &&
this.priv === other.priv
);
}

Expand All @@ -74,6 +98,9 @@ export class Locale {
(this.language === other.language ||
(thisRange && this.language === undefined) ||
(otherRange && other.language === undefined)) &&
(this.extended.every((v, i) => v === other.extended[i]) ||
(thisRange && this.extended.length === 0) ||
(otherRange && other.extended.length === 0)) &&
(this.script === other.script ||
(thisRange && this.script === undefined) ||
(otherRange && other.script === undefined)) &&
Expand All @@ -82,12 +109,27 @@ export class Locale {
(otherRange && other.region === undefined)) &&
(this.variant === other.variant ||
(thisRange && this.variant === undefined) ||
(otherRange && other.variant === undefined))
(otherRange && other.variant === undefined)) &&
(compareMap(this.extension, other.extension) ||
(thisRange && this.extension.size === 0) ||
(otherRange && other.extension.size === 0)) &&
(this.priv === other.priv ||
(thisRange && this.priv === undefined) ||
(otherRange && other.priv === undefined))
);
}

toString(): string {
return [this.language, this.script, this.region, this.variant]
const xSubtag = this.priv === undefined ? undefined : `x-${this.priv}`;
return [
this.language,
...this.extended,
this.script,
this.region,
this.variant,
...[...this.extension.entries()].flat(),
xSubtag,
]
.filter(part => part !== undefined)
.join("-");
}
Expand All @@ -104,9 +146,12 @@ export class Locale {
const newLocale = getLikelySubtagsMin(this.toString().toLowerCase());
if (newLocale) {
this.language = newLocale.language;
this.extended = newLocale.extended;
this.script = newLocale.script;
this.region = newLocale.region;
this.variant = newLocale.variant;
this.extension = newLocale.extension;
this.priv = newLocale.priv;
return true;
}
return false;
Expand Down Expand Up @@ -177,3 +222,18 @@ function getLikelySubtagsMin(loc: string): Locale | null {
}
return null;
}

function compareMap<K, V>(map1: Map<K, V>, map2: Map<K, V>): boolean {
if (map1.size !== map2.size) {
return false;
}

for (const [key, value] of map1) {
const other = map2.get(key);
if (other !== value || (other === undefined && !map2.has(key))) {
return false;
}
}

return true;
}
24 changes: 23 additions & 1 deletion fluent-langneg/test/locale_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { Locale } from "../esm/locale.js";

function isLocaleEqual(str, ref) {
const locale = new Locale(str);
return locale.isEqual(ref);
return locale.isEqual({
...{
extended: [],
},
ref,
});
}

suite("Parses simple locales", () => {
Expand All @@ -21,6 +26,15 @@ suite("Parses simple locales", () => {
);
});

test("extended part", () => {
assert.ok(
isLocaleEqual("zh-gan", {
language: "zh",
extended: ["gan"],
})
);
});

test("script part", () => {
assert.ok(
isLocaleEqual("en-Latn", {
Expand Down Expand Up @@ -53,6 +67,14 @@ suite("Parses simple locales", () => {
region: "FA",
})
);

assert.ok(
isLocaleEqual("es-Latn-419", {
language: "es",
script: "Latn",
region: "419",
})
);
});

test("variant part", () => {
Expand Down
5 changes: 3 additions & 2 deletions fluent-react/example/src/l10n.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { FluentBundle, FluentResource } from "@fluent/bundle";
import { ReactLocalization, LocalizationProvider } from "@fluent/react";

const ftl: Record<string, URL> = {
'en-US': new URL( "../public/en-US.ftl", import.meta.url),
pl: new URL( "../public/pl.ftl", import.meta.url) }
"en-US": new URL("../public/en-US.ftl", import.meta.url),
pl: new URL("../public/pl.ftl", import.meta.url),
};

const DEFAULT_LOCALE = "en-US";
const AVAILABLE_LOCALES = {
Expand Down
4 changes: 2 additions & 2 deletions fluent-syntax/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

### Bug Fixes

* Use correct TS type for `clone()` return ([#589](https://github.com/projectfluent/fluent.js/issues/589))
* Adjust for updated linter rules ([#590](https://github.com/projectfluent/fluent.js/pull/590))
- Use correct TS type for `clone()` return ([#589](https://github.com/projectfluent/fluent.js/issues/589))
- Adjust for updated linter rules ([#590](https://github.com/projectfluent/fluent.js/pull/590))

## @fluent/syntax 0.18.0 (September 13, 2021)

Expand Down