Skip to content

Commit 49347e7

Browse files
authored
Merge pull request #462 from stasm/ts-langneg
Migrate @fluent/langneg to TypeScript
2 parents d6580a5 + d20d33b commit 49347e7

16 files changed

+138
-115
lines changed

eslint_ts.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"./eslint_src.json"
1717
],
1818
"rules": {
19+
"complexity": "off",
1920
"prefer-const": "off",
2021
"no-unused-vars": ["error", {"args": "none"}],
2122
"@typescript-eslint/no-inferrable-types": "off",

fluent-langneg/.esdoc.json

-16
This file was deleted.

fluent-langneg/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
esm/
12
/index.js
23
/compat.js

fluent-langneg/.npmignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
.nyc_output
22
coverage
3-
docs
3+
esm/.compiled
4+
src
45
test
56
makefile
7+
tsconfig.json

fluent-langneg/makefile

+32-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,21 @@ GLOBAL := FluentLangNeg
33

44
include ../common.mk
55

6-
test:
6+
lint:
7+
@eslint --config $(ROOT)/eslint_ts.json --max-warnings 0 src/*.ts
8+
@eslint --config $(ROOT)/eslint_test.json --max-warnings 0 test/
9+
@echo -e " $(OK) lint"
10+
11+
.PHONY: compile
12+
compile: esm/.compiled
13+
14+
esm/.compiled: $(SOURCES)
15+
@tsc
16+
@touch $@
17+
@echo -e " $(OK) esm/ compiled"
18+
19+
.PHONY: test
20+
test: esm/.compiled
721
@nyc --reporter=text --reporter=html mocha \
822
--recursive --ui tdd \
923
--require esm \
@@ -13,7 +27,7 @@ test:
1327
build: index.js compat.js
1428

1529
index.js: $(SOURCES)
16-
@rollup $(CURDIR)/src/index.js \
30+
@rollup $(CURDIR)/esm/index.js \
1731
--config $(ROOT)/bundle_config.js \
1832
--banner "/* $(PACKAGE)@$(VERSION) */" \
1933
--amd.id $(PACKAGE) \
@@ -22,14 +36,26 @@ index.js: $(SOURCES)
2236
@echo -e " $(OK) $@ built"
2337

2438
compat.js: $(SOURCES)
25-
@rollup $(CURDIR)/src/index.js \
39+
@rollup $(CURDIR)/esm/index.js \
2640
--config $(ROOT)/compat_config.js \
2741
--banner "/* $(PACKAGE)@$(VERSION) */" \
2842
--amd.id $(PACKAGE) \
2943
--name $(GLOBAL) \
3044
--output.file $@
3145
@echo -e " $(OK) $@ built"
3246

33-
lint: _lint
34-
html: _html
35-
clean: _clean
47+
html:
48+
@typedoc src \
49+
--out ../html/bundle \
50+
--mode file \
51+
--excludeNotExported \
52+
--excludePrivate \
53+
--logger none \
54+
--hideGenerator
55+
@echo -e " $(OK) html built"
56+
57+
clean:
58+
@rm -f esm/*.js esm/*.d.ts esm/.compiled
59+
@rm -f index.js compat.js
60+
@rm -rf .nyc_output coverage
61+
@echo -e " $(OK) clean"

fluent-langneg/src/accepted_languages.js

-7
This file was deleted.
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function acceptedLanguages(str: string = ""): Array<string> {
2+
if (typeof str !== "string") {
3+
throw new TypeError("Argument must be a string");
4+
}
5+
const tokens = str.split(",").map(t => t.trim());
6+
return tokens.filter(t => t !== "").map(t => t.split(";")[0]);
7+
}

fluent-langneg/src/index.js renamed to fluent-langneg/src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
*
88
*/
99

10-
export { default as negotiateLanguages } from "./negotiate_languages";
11-
export { default as acceptedLanguages } from "./accepted_languages";
10+
export {negotiateLanguages} from "./negotiate_languages";
11+
export {acceptedLanguages} from "./accepted_languages";

fluent-langneg/src/locale.js renamed to fluent-langneg/src/locale.ts

+35-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint no-magic-numbers: 0 */
22

3-
import { getLikelySubtagsMin } from "./subtags";
3+
import {getLikelySubtagsMin} from "./subtags";
44

55
const languageCodeRe = "([a-z]{2,3}|\\*)";
66
const scriptCodeRe = "(?:-([a-z]{4}|\\*))";
@@ -22,9 +22,13 @@ const variantCodeRe = "(?:-(([0-9][a-z0-9]{3}|[a-z0-9]{5,8})|\\*))";
2222
const localeRe = new RegExp(
2323
`^${languageCodeRe}${scriptCodeRe}?${regionCodeRe}?${variantCodeRe}?$`, "i");
2424

25-
export const localeParts = ["language", "script", "region", "variant"];
25+
export class Locale {
26+
isWellFormed: boolean;
27+
language?: string;
28+
script?: string;
29+
region?: string;
30+
variant?: string;
2631

27-
export default class Locale {
2832
/**
2933
* Parses a locale id using the localeRe into an array with four elements.
3034
*
@@ -34,7 +38,7 @@ export default class Locale {
3438
* It also allows skipping the script section of the id, so `en-US` is
3539
* properly parsed as `en-*-US-*`.
3640
*/
37-
constructor(locale) {
41+
constructor(locale: string) {
3842
const result = localeRe.exec(locale.replace(/_/g, "-"));
3943
if (!result) {
4044
this.isWellFormed = false;
@@ -56,38 +60,49 @@ export default class Locale {
5660
this.isWellFormed = true;
5761
}
5862

59-
isEqual(locale) {
60-
return localeParts.every(part => this[part] === locale[part]);
63+
isEqual(other: Locale): boolean {
64+
return this.language === other.language
65+
&& this.script === other.script
66+
&& this.region === other.region
67+
&& this.variant === other.variant;
6168
}
6269

63-
matches(locale, thisRange = false, otherRange = false) {
64-
return localeParts.every(part => {
65-
return ((thisRange && this[part] === undefined) ||
66-
(otherRange && locale[part] === undefined) ||
67-
this[part] === locale[part]);
68-
});
70+
matches(other: Locale, thisRange = false, otherRange = false): boolean {
71+
return (this.language === other.language
72+
|| thisRange && this.language === undefined
73+
|| otherRange && other.language === undefined)
74+
&& (this.script === other.script
75+
|| thisRange && this.script === undefined
76+
|| otherRange && other.script === undefined)
77+
&& (this.region === other.region
78+
|| thisRange && this.region === undefined
79+
|| otherRange && other.region === undefined)
80+
&& (this.variant === other.variant
81+
|| thisRange && this.variant === undefined
82+
|| otherRange && other.variant === undefined);
6983
}
7084

71-
toString() {
72-
return localeParts
73-
.map(part => this[part])
85+
toString(): string {
86+
return [this.language, this.script, this.region, this.variant]
7487
.filter(part => part !== undefined)
7588
.join("-");
7689
}
7790

78-
clearVariants() {
91+
clearVariants(): void {
7992
this.variant = undefined;
8093
}
8194

82-
clearRegion() {
95+
clearRegion(): void {
8396
this.region = undefined;
8497
}
8598

86-
addLikelySubtags() {
99+
addLikelySubtags(): boolean {
87100
const newLocale = getLikelySubtagsMin(this.toString().toLowerCase());
88-
89101
if (newLocale) {
90-
localeParts.forEach(part => this[part] = newLocale[part]);
102+
this.language = newLocale.language;
103+
this.script = newLocale.script;
104+
this.region = newLocale.region;
105+
this.variant = newLocale.variant;
91106
return true;
92107
}
93108
return false;

fluent-langneg/src/matches.js renamed to fluent-langneg/src/matches.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* eslint no-magic-numbers: 0 */
2-
/* eslint complexity: ["error", { "max": 27 }] */
32

4-
import Locale from "./locale";
3+
import {Locale} from "./locale";
54

65
/**
76
* Negotiates the languages between the list of requested locales against
@@ -73,13 +72,13 @@ import Locale from "./locale";
7372
* ignoring script ranges. That means that `sr-Cyrl` will never match
7473
* against `sr-Latn`.
7574
*/
76-
export default function filterMatches(
77-
requestedLocales, availableLocales, strategy
78-
) {
79-
/* eslint complexity: ["error", 31]*/
80-
const supportedLocales = new Set();
81-
82-
const availableLocalesMap = new Map();
75+
export function filterMatches(
76+
requestedLocales: Array<string>,
77+
availableLocales: Array<string>,
78+
strategy: string
79+
): Array<string> {
80+
const supportedLocales: Set<string> = new Set();
81+
const availableLocalesMap: Map<string, Locale> = new Map();
8382

8483
for (let locale of availableLocales) {
8584
let newLocale = new Locale(locale);
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,8 @@
1-
import filterMatches from "./matches";
1+
import {filterMatches} from "./matches";
22

3-
function GetOption(options, property, type, values, fallback) {
4-
let value = options[property];
5-
6-
if (value !== undefined) {
7-
if (type === "boolean") {
8-
value = new Boolean(value);
9-
} else if (type === "string") {
10-
value = String(value);
11-
}
12-
13-
if (values !== undefined && values.indexOf(value) === -1) {
14-
throw new Error("Invalid option value");
15-
}
16-
17-
return value;
18-
}
19-
20-
return fallback;
3+
interface NegotiateLanguagesOptions {
4+
strategy?: "filtering" | "matching" | "lookup";
5+
defaultLocale?: string;
216
}
227

238
/**
@@ -63,38 +48,32 @@ function GetOption(options, property, type, values, fallback) {
6348
*
6449
* This strategy requires defaultLocale option to be set.
6550
*/
66-
export default function negotiateLanguages(
67-
requestedLocales,
68-
availableLocales,
69-
options = {}
70-
) {
71-
72-
const defaultLocale = GetOption(options, "defaultLocale", "string");
73-
const strategy = GetOption(options, "strategy", "string",
74-
["filtering", "matching", "lookup"], "filtering");
75-
76-
if (strategy === "lookup" && !defaultLocale) {
77-
throw new Error("defaultLocale cannot be undefined for strategy `lookup`");
78-
}
79-
80-
const resolvedReqLoc = Array.from(Object(requestedLocales)).map(loc => {
81-
return String(loc);
82-
});
83-
const resolvedAvailLoc = Array.from(Object(availableLocales)).map(loc => {
84-
return String(loc);
85-
});
51+
export function negotiateLanguages(
52+
requestedLocales: Array<string>,
53+
availableLocales: Array<string>,
54+
{
55+
strategy = "filtering",
56+
defaultLocale,
57+
}: NegotiateLanguagesOptions = {}
58+
): Array<string> {
8659

8760
const supportedLocales = filterMatches(
88-
resolvedReqLoc,
89-
resolvedAvailLoc, strategy
61+
Array.from(Object(requestedLocales)).map(String),
62+
Array.from(Object(availableLocales)).map(String),
63+
strategy
9064
);
9165

9266
if (strategy === "lookup") {
67+
if (defaultLocale === undefined) {
68+
throw new Error(
69+
"defaultLocale cannot be undefined for strategy `lookup`");
70+
}
9371
if (supportedLocales.length === 0) {
9472
supportedLocales.push(defaultLocale);
9573
}
9674
} else if (defaultLocale && !supportedLocales.includes(defaultLocale)) {
9775
supportedLocales.push(defaultLocale);
9876
}
77+
9978
return supportedLocales;
10079
}

fluent-langneg/src/subtags.js renamed to fluent-langneg/src/subtags.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Locale from "./locale";
1+
import {Locale} from "./locale";
22

33
/**
44
* Below is a manually a list of likely subtags corresponding to Unicode
@@ -9,7 +9,7 @@ import Locale from "./locale";
99
*
1010
* This version of the list is based on CLDR 30.0.3.
1111
*/
12-
const likelySubtagsMin = {
12+
const likelySubtagsMin: Record<string, string> = {
1313
"ar": "ar-arab-eg",
1414
"az-arab": "az-arab-ir",
1515
"az-ir": "az-arab-ir",
@@ -51,12 +51,12 @@ const regionMatchingLangs = [
5151
"ru",
5252
];
5353

54-
export function getLikelySubtagsMin(loc) {
54+
export function getLikelySubtagsMin(loc: string): Locale | null {
5555
if (likelySubtagsMin.hasOwnProperty(loc)) {
5656
return new Locale(likelySubtagsMin[loc]);
5757
}
5858
const locale = new Locale(loc);
59-
if (regionMatchingLangs.includes(locale.language)) {
59+
if (locale.language && regionMatchingLangs.includes(locale.language)) {
6060
locale.region = locale.language.toUpperCase();
6161
return locale;
6262
}

fluent-langneg/test/headers_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import assert from 'assert';
2-
import acceptedLanguages from '../src/accepted_languages';
2+
import {acceptedLanguages} from '../esm/accepted_languages.js';
33

44
suite('parse headers', () => {
55
test('without quality values', () => {

0 commit comments

Comments
 (0)