@formatjs/intl-locale 3.0.3 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/BUILD +132 -0
  2. package/CHANGELOG.md +436 -0
  3. package/LICENSE.md +0 -0
  4. package/README.md +0 -0
  5. package/get_internal_slots.ts +15 -0
  6. package/index.ts +529 -0
  7. package/package.json +4 -4
  8. package/polyfill-force.ts +8 -0
  9. package/polyfill.ts +10 -0
  10. package/should-polyfill.ts +14 -0
  11. package/tests/index.test.ts +18 -0
  12. package/tests/likely-subtags.test.ts +108 -0
  13. package/tests/minimize.test.ts +35 -0
  14. package/tsconfig.json +5 -0
  15. package/get_internal_slots.d.ts +0 -3
  16. package/get_internal_slots.d.ts.map +0 -1
  17. package/get_internal_slots.js +0 -14
  18. package/index.d.ts +0 -49
  19. package/index.d.ts.map +0 -1
  20. package/index.js +0 -394
  21. package/lib/get_internal_slots.d.ts +0 -3
  22. package/lib/get_internal_slots.d.ts.map +0 -1
  23. package/lib/get_internal_slots.js +0 -11
  24. package/lib/index.d.ts +0 -49
  25. package/lib/index.d.ts.map +0 -1
  26. package/lib/index.js +0 -391
  27. package/lib/polyfill-force.d.ts +0 -2
  28. package/lib/polyfill-force.d.ts.map +0 -1
  29. package/lib/polyfill-force.js +0 -7
  30. package/lib/polyfill.d.ts +0 -2
  31. package/lib/polyfill.d.ts.map +0 -1
  32. package/lib/polyfill.js +0 -10
  33. package/lib/should-polyfill.d.ts +0 -2
  34. package/lib/should-polyfill.d.ts.map +0 -1
  35. package/lib/should-polyfill.js +0 -14
  36. package/polyfill-force.d.ts +0 -2
  37. package/polyfill-force.d.ts.map +0 -1
  38. package/polyfill-force.js +0 -9
  39. package/polyfill.d.ts +0 -2
  40. package/polyfill.d.ts.map +0 -1
  41. package/polyfill.iife.js +0 -6468
  42. package/polyfill.js +0 -12
  43. package/should-polyfill.d.ts +0 -2
  44. package/should-polyfill.d.ts.map +0 -1
  45. package/should-polyfill.js +0 -18
package/index.ts ADDED
@@ -0,0 +1,529 @@
1
+ import {
2
+ GetOption,
3
+ invariant,
4
+ SameValue,
5
+ CoerceOptionsToObject,
6
+ } from '@formatjs/ecma402-abstract'
7
+ import {
8
+ isStructurallyValidLanguageTag,
9
+ isUnicodeLanguageSubtag,
10
+ isUnicodeRegionSubtag,
11
+ parseUnicodeLanguageId,
12
+ UnicodeExtension,
13
+ isUnicodeScriptSubtag,
14
+ emitUnicodeLocaleId,
15
+ parseUnicodeLocaleId,
16
+ emitUnicodeLanguageId,
17
+ getCanonicalLocales,
18
+ UnicodeLanguageId,
19
+ likelySubtags,
20
+ } from '@formatjs/intl-getcanonicallocales'
21
+ import getInternalSlots from './get_internal_slots'
22
+
23
+ export interface IntlLocaleOptions {
24
+ language?: string
25
+ script?: string
26
+ region?: string
27
+ calendar?: string
28
+ collation?: string
29
+ hourCycle?: 'h11' | 'h12' | 'h23' | 'h24'
30
+ caseFirst?: 'upper' | 'lower' | 'false'
31
+ numberingSystem?: string
32
+ numeric?: boolean
33
+ }
34
+
35
+ const RELEVANT_EXTENSION_KEYS = ['ca', 'co', 'hc', 'kf', 'kn', 'nu'] as const
36
+ type RELEVANT_EXTENSION_KEY = typeof RELEVANT_EXTENSION_KEYS[number]
37
+ type ExtensionOpts = Record<RELEVANT_EXTENSION_KEY, string>
38
+
39
+ export interface IntlLocaleInternal extends IntlLocaleOptions {
40
+ locale: string
41
+ initializedLocale: boolean
42
+ }
43
+
44
+ const UNICODE_TYPE_REGEX = /^[a-z0-9]{3,8}(-[a-z0-9]{3,8})*$/i
45
+
46
+ function applyOptionsToTag(tag: string, options: IntlLocaleOptions): string {
47
+ invariant(typeof tag === 'string', 'language tag must be a string')
48
+ invariant(
49
+ isStructurallyValidLanguageTag(tag),
50
+ 'malformed language tag',
51
+ RangeError
52
+ )
53
+ const language = GetOption(
54
+ options,
55
+ 'language',
56
+ 'string',
57
+ undefined,
58
+ undefined
59
+ )
60
+ if (language !== undefined) {
61
+ invariant(
62
+ isUnicodeLanguageSubtag(language),
63
+ 'Malformed unicode_language_subtag',
64
+ RangeError
65
+ )
66
+ }
67
+ const script = GetOption(options, 'script', 'string', undefined, undefined)
68
+ if (script !== undefined) {
69
+ invariant(
70
+ isUnicodeScriptSubtag(script),
71
+ 'Malformed unicode_script_subtag',
72
+ RangeError
73
+ )
74
+ }
75
+ const region = GetOption(options, 'region', 'string', undefined, undefined)
76
+ if (region !== undefined) {
77
+ invariant(
78
+ isUnicodeRegionSubtag(region),
79
+ 'Malformed unicode_region_subtag',
80
+ RangeError
81
+ )
82
+ }
83
+ const languageId = parseUnicodeLanguageId(tag)
84
+ if (language !== undefined) {
85
+ languageId.lang = language
86
+ }
87
+ if (script !== undefined) {
88
+ languageId.script = script
89
+ }
90
+ if (region !== undefined) {
91
+ languageId.region = region
92
+ }
93
+ return ((Intl as any).getCanonicalLocales as typeof getCanonicalLocales)(
94
+ emitUnicodeLocaleId({
95
+ ...parseUnicodeLocaleId(tag),
96
+ lang: languageId,
97
+ })
98
+ )[0]
99
+ }
100
+
101
+ function applyUnicodeExtensionToTag(
102
+ tag: string,
103
+ options: ExtensionOpts,
104
+ relevantExtensionKeys: typeof RELEVANT_EXTENSION_KEYS
105
+ ): ExtensionOpts & {locale: string} {
106
+ let unicodeExtension: UnicodeExtension | undefined
107
+ let keywords: UnicodeExtension['keywords'] = []
108
+ const ast = parseUnicodeLocaleId(tag)
109
+ for (const ext of ast.extensions) {
110
+ if (ext.type === 'u') {
111
+ unicodeExtension = ext
112
+ if (Array.isArray(ext.keywords)) keywords = ext.keywords
113
+ }
114
+ }
115
+
116
+ const result = Object.create(null)
117
+
118
+ for (const key of relevantExtensionKeys) {
119
+ let value, entry
120
+ for (const keyword of keywords) {
121
+ if (keyword[0] === key) {
122
+ entry = keyword
123
+ value = entry[1]
124
+ }
125
+ }
126
+ invariant(key in options, `${key} must be in options`)
127
+ const optionsValue = options[key]
128
+ if (optionsValue !== undefined) {
129
+ invariant(
130
+ typeof optionsValue === 'string',
131
+ `Value for ${key} must be a string`
132
+ )
133
+ value = optionsValue
134
+ if (entry) {
135
+ entry[1] = value
136
+ } else {
137
+ keywords.push([key, value])
138
+ }
139
+ }
140
+ result[key] = value
141
+ }
142
+ if (!unicodeExtension) {
143
+ if (keywords.length) {
144
+ ast.extensions.push({
145
+ type: 'u',
146
+ keywords,
147
+ attributes: [],
148
+ })
149
+ }
150
+ } else {
151
+ unicodeExtension.keywords = keywords
152
+ }
153
+ result.locale = (
154
+ (Intl as any).getCanonicalLocales as typeof getCanonicalLocales
155
+ )(emitUnicodeLocaleId(ast))[0]
156
+ return result
157
+ }
158
+
159
+ function mergeUnicodeLanguageId(
160
+ lang?: string,
161
+ script?: string,
162
+ region?: string,
163
+ variants: string[] = [],
164
+ replacement?: UnicodeLanguageId
165
+ ): UnicodeLanguageId {
166
+ if (!replacement) {
167
+ return {
168
+ lang: lang || 'und',
169
+ script,
170
+ region,
171
+ variants,
172
+ }
173
+ }
174
+ return {
175
+ lang: !lang || lang === 'und' ? replacement.lang : lang,
176
+ script: script || replacement.script,
177
+ region: region || replacement.region,
178
+ variants: [...variants, ...replacement.variants],
179
+ }
180
+ }
181
+
182
+ function addLikelySubtags(tag: string): string {
183
+ const ast = parseUnicodeLocaleId(tag)
184
+ const unicodeLangId = ast.lang
185
+ const {lang, script, region, variants} = unicodeLangId
186
+ if (script && region) {
187
+ const match =
188
+ likelySubtags[
189
+ emitUnicodeLanguageId({lang, script, region, variants: []}) as 'aa'
190
+ ]
191
+ if (match) {
192
+ const parts = parseUnicodeLanguageId(match)
193
+ ast.lang = mergeUnicodeLanguageId(
194
+ undefined,
195
+ undefined,
196
+ undefined,
197
+ variants,
198
+ parts
199
+ )
200
+ return emitUnicodeLocaleId(ast)
201
+ }
202
+ }
203
+ if (script) {
204
+ const match =
205
+ likelySubtags[emitUnicodeLanguageId({lang, script, variants: []}) as 'aa']
206
+ if (match) {
207
+ const parts = parseUnicodeLanguageId(match)
208
+ ast.lang = mergeUnicodeLanguageId(
209
+ undefined,
210
+ undefined,
211
+ region,
212
+ variants,
213
+ parts
214
+ )
215
+ return emitUnicodeLocaleId(ast)
216
+ }
217
+ }
218
+ if (region) {
219
+ const match =
220
+ likelySubtags[emitUnicodeLanguageId({lang, region, variants: []}) as 'aa']
221
+ if (match) {
222
+ const parts = parseUnicodeLanguageId(match)
223
+ ast.lang = mergeUnicodeLanguageId(
224
+ undefined,
225
+ script,
226
+ undefined,
227
+ variants,
228
+ parts
229
+ )
230
+ return emitUnicodeLocaleId(ast)
231
+ }
232
+ }
233
+ const match =
234
+ likelySubtags[lang as 'aa'] ||
235
+ likelySubtags[
236
+ emitUnicodeLanguageId({lang: 'und', script, variants: []}) as 'aa'
237
+ ]
238
+ if (!match) {
239
+ throw new Error(`No match for addLikelySubtags`)
240
+ }
241
+ const parts = parseUnicodeLanguageId(match)
242
+ ast.lang = mergeUnicodeLanguageId(undefined, script, region, variants, parts)
243
+ return emitUnicodeLocaleId(ast)
244
+ }
245
+
246
+ /**
247
+ * From: https://github.com/unicode-org/icu/blob/4231ca5be053a22a1be24eb891817458c97db709/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java#L2395
248
+ * @param tag
249
+ */
250
+ function removeLikelySubtags(tag: string): string {
251
+ let maxLocale = addLikelySubtags(tag)
252
+ if (!maxLocale) {
253
+ return tag
254
+ }
255
+ maxLocale = emitUnicodeLanguageId({
256
+ ...parseUnicodeLanguageId(maxLocale),
257
+ variants: [],
258
+ })
259
+ const ast = parseUnicodeLocaleId(tag)
260
+ const {
261
+ lang: {lang, script, region, variants},
262
+ } = ast
263
+ const trial = addLikelySubtags(emitUnicodeLanguageId({lang, variants: []}))
264
+ if (trial === maxLocale) {
265
+ return emitUnicodeLocaleId({
266
+ ...ast,
267
+ lang: mergeUnicodeLanguageId(lang, undefined, undefined, variants),
268
+ })
269
+ }
270
+ if (region) {
271
+ const trial = addLikelySubtags(
272
+ emitUnicodeLanguageId({lang, region, variants: []})
273
+ )
274
+ if (trial === maxLocale) {
275
+ return emitUnicodeLocaleId({
276
+ ...ast,
277
+ lang: mergeUnicodeLanguageId(lang, undefined, region, variants),
278
+ })
279
+ }
280
+ }
281
+ if (script) {
282
+ const trial = addLikelySubtags(
283
+ emitUnicodeLanguageId({lang, script, variants: []})
284
+ )
285
+ if (trial === maxLocale) {
286
+ return emitUnicodeLocaleId({
287
+ ...ast,
288
+ lang: mergeUnicodeLanguageId(lang, script, undefined, variants),
289
+ })
290
+ }
291
+ }
292
+ return tag
293
+ }
294
+
295
+ export class Locale {
296
+ constructor(tag: string | Locale, opts?: IntlLocaleOptions) {
297
+ // test262/test/intl402/RelativeTimeFormat/constructor/constructor/newtarget-undefined.js
298
+ // Cannot use `new.target` bc of IE11 & TS transpiles it to something else
299
+ const newTarget = this && this instanceof Locale ? this.constructor : void 0
300
+ if (!newTarget) {
301
+ throw new TypeError("Intl.Locale must be called with 'new'")
302
+ }
303
+
304
+ const {relevantExtensionKeys} = Locale
305
+
306
+ const internalSlotsList: Array<keyof IntlLocaleInternal> = [
307
+ 'initializedLocale',
308
+ 'locale',
309
+ 'calendar',
310
+ 'collation',
311
+ 'hourCycle',
312
+ 'numberingSystem',
313
+ ]
314
+
315
+ if (relevantExtensionKeys.indexOf('kf') > -1) {
316
+ internalSlotsList.push('caseFirst')
317
+ }
318
+
319
+ if (relevantExtensionKeys.indexOf('kn') > -1) {
320
+ internalSlotsList.push('numeric')
321
+ }
322
+
323
+ if (tag === undefined) {
324
+ throw new TypeError(
325
+ "First argument to Intl.Locale constructor can't be empty or missing"
326
+ )
327
+ }
328
+
329
+ if (typeof tag !== 'string' && typeof tag !== 'object') {
330
+ throw new TypeError('tag must be a string or object')
331
+ }
332
+
333
+ let internalSlots
334
+ if (
335
+ typeof tag === 'object' &&
336
+ (internalSlots = getInternalSlots(tag)) &&
337
+ internalSlots.initializedLocale
338
+ ) {
339
+ tag = internalSlots.locale
340
+ } else {
341
+ tag = tag.toString() as string
342
+ }
343
+
344
+ internalSlots = getInternalSlots(this)
345
+
346
+ let options = CoerceOptionsToObject<IntlLocaleOptions>(opts)
347
+
348
+ tag = applyOptionsToTag(tag, options)
349
+ const opt = Object.create(null)
350
+ const calendar = GetOption(
351
+ options,
352
+ 'calendar',
353
+ 'string',
354
+ undefined,
355
+ undefined
356
+ )
357
+ if (calendar !== undefined) {
358
+ if (!UNICODE_TYPE_REGEX.test(calendar)) {
359
+ throw new RangeError('invalid calendar')
360
+ }
361
+ }
362
+ opt.ca = calendar
363
+
364
+ const collation = GetOption(
365
+ options,
366
+ 'collation',
367
+ 'string',
368
+ undefined,
369
+ undefined
370
+ )
371
+ if (collation !== undefined) {
372
+ if (!UNICODE_TYPE_REGEX.test(collation)) {
373
+ throw new RangeError('invalid collation')
374
+ }
375
+ }
376
+ opt.co = collation
377
+ const hc = GetOption(
378
+ options,
379
+ 'hourCycle',
380
+ 'string',
381
+ ['h11', 'h12', 'h23', 'h24'],
382
+ undefined
383
+ )
384
+ opt.hc = hc
385
+ const kf = GetOption(
386
+ options,
387
+ 'caseFirst',
388
+ 'string',
389
+ ['upper', 'lower', 'false'],
390
+ undefined
391
+ )
392
+ opt.kf = kf
393
+ const _kn = GetOption(options, 'numeric', 'boolean', undefined, undefined)
394
+ let kn
395
+ if (_kn !== undefined) {
396
+ kn = String(_kn)
397
+ }
398
+ opt.kn = kn
399
+ const numberingSystem = GetOption(
400
+ options,
401
+ 'numberingSystem',
402
+ 'string',
403
+ undefined,
404
+ undefined
405
+ )
406
+ if (numberingSystem !== undefined) {
407
+ if (!UNICODE_TYPE_REGEX.test(numberingSystem)) {
408
+ throw new RangeError('Invalid numberingSystem')
409
+ }
410
+ }
411
+ opt.nu = numberingSystem
412
+ const r = applyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys)
413
+ internalSlots.locale = r.locale
414
+ internalSlots.calendar = r.ca
415
+ internalSlots.collation = r.co
416
+ internalSlots.hourCycle = r.hc as 'h11'
417
+
418
+ if (relevantExtensionKeys.indexOf('kf') > -1) {
419
+ internalSlots.caseFirst = r.kf as 'upper'
420
+ }
421
+ if (relevantExtensionKeys.indexOf('kn') > -1) {
422
+ internalSlots.numeric = SameValue(r.kn, 'true')
423
+ }
424
+ internalSlots.numberingSystem = r.nu
425
+ }
426
+
427
+ /**
428
+ * https://www.unicode.org/reports/tr35/#Likely_Subtags
429
+ */
430
+ public maximize(): Locale {
431
+ const locale = getInternalSlots(this).locale
432
+ try {
433
+ const maximizedLocale = addLikelySubtags(locale)
434
+ return new Locale(maximizedLocale)
435
+ } catch (e) {
436
+ return new Locale(locale)
437
+ }
438
+ }
439
+
440
+ /**
441
+ * https://www.unicode.org/reports/tr35/#Likely_Subtags
442
+ */
443
+ public minimize(): Locale {
444
+ const locale = getInternalSlots(this).locale
445
+ try {
446
+ const minimizedLocale = removeLikelySubtags(locale)
447
+ return new Locale(minimizedLocale)
448
+ } catch (e) {
449
+ return new Locale(locale)
450
+ }
451
+ }
452
+
453
+ public toString() {
454
+ return getInternalSlots(this).locale
455
+ }
456
+
457
+ public get baseName() {
458
+ const locale = getInternalSlots(this).locale
459
+ return emitUnicodeLanguageId(parseUnicodeLanguageId(locale))
460
+ }
461
+
462
+ public get calendar() {
463
+ return getInternalSlots(this).calendar
464
+ }
465
+
466
+ public get collation() {
467
+ return getInternalSlots(this).collation
468
+ }
469
+
470
+ public get hourCycle() {
471
+ return getInternalSlots(this).hourCycle
472
+ }
473
+
474
+ public get caseFirst() {
475
+ return getInternalSlots(this).caseFirst
476
+ }
477
+
478
+ public get numeric() {
479
+ return getInternalSlots(this).numeric
480
+ }
481
+ public get numberingSystem() {
482
+ return getInternalSlots(this).numberingSystem
483
+ }
484
+ /**
485
+ * https://tc39.es/proposal-intl-locale/#sec-Intl.Locale.prototype.language
486
+ */
487
+ public get language() {
488
+ const locale = getInternalSlots(this).locale
489
+ return parseUnicodeLanguageId(locale).lang
490
+ }
491
+ /**
492
+ * https://tc39.es/proposal-intl-locale/#sec-Intl.Locale.prototype.script
493
+ */
494
+ public get script() {
495
+ const locale = getInternalSlots(this).locale
496
+ return parseUnicodeLanguageId(locale).script
497
+ }
498
+ /**
499
+ * https://tc39.es/proposal-intl-locale/#sec-Intl.Locale.prototype.region
500
+ */
501
+ public get region() {
502
+ const locale = getInternalSlots(this).locale
503
+ return parseUnicodeLanguageId(locale).region
504
+ }
505
+
506
+ static relevantExtensionKeys = RELEVANT_EXTENSION_KEYS
507
+ }
508
+
509
+ try {
510
+ if (typeof Symbol !== 'undefined') {
511
+ Object.defineProperty(Locale.prototype, Symbol.toStringTag, {
512
+ value: 'Intl.Locale',
513
+ writable: false,
514
+ enumerable: false,
515
+ configurable: true,
516
+ })
517
+ }
518
+
519
+ Object.defineProperty(Locale.prototype.constructor, 'length', {
520
+ value: 1,
521
+ writable: false,
522
+ enumerable: false,
523
+ configurable: true,
524
+ })
525
+ } catch (e) {
526
+ // Meta fix so we're test262-compliant, not important
527
+ }
528
+
529
+ export default Locale
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formatjs/intl-locale",
3
- "version": "3.0.3",
3
+ "version": "3.0.4",
4
4
  "description": "Intl.Locale polyfill",
5
5
  "keywords": [
6
6
  "intl",
@@ -24,8 +24,8 @@
24
24
  "url": "https://github.com/formatjs/formatjs/issues"
25
25
  },
26
26
  "dependencies": {
27
- "@formatjs/ecma402-abstract": "1.11.8",
28
- "@formatjs/intl-getcanonicallocales": "2.0.2",
27
+ "@formatjs/ecma402-abstract": "1.11.9",
28
+ "@formatjs/intl-getcanonicallocales": "2.0.3",
29
29
  "tslib": "2.4.0"
30
30
  }
31
- }
31
+ }
@@ -0,0 +1,8 @@
1
+ import {Locale} from './'
2
+
3
+ Object.defineProperty(Intl, 'Locale', {
4
+ value: Locale,
5
+ writable: true,
6
+ enumerable: false,
7
+ configurable: true,
8
+ })
package/polyfill.ts ADDED
@@ -0,0 +1,10 @@
1
+ import {Locale} from './'
2
+ import {shouldPolyfill} from './should-polyfill'
3
+ if (shouldPolyfill()) {
4
+ Object.defineProperty(Intl, 'Locale', {
5
+ value: Locale,
6
+ writable: true,
7
+ enumerable: false,
8
+ configurable: true,
9
+ })
10
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * https://bugs.chromium.org/p/v8/issues/detail?id=10682
3
+ */
4
+ function hasIntlGetCanonicalLocalesBug(): boolean {
5
+ try {
6
+ return new (Intl as any).Locale('und-x-private').toString() === 'x-private'
7
+ } catch (e) {
8
+ return true
9
+ }
10
+ }
11
+
12
+ export function shouldPolyfill() {
13
+ return !('Locale' in Intl) || hasIntlGetCanonicalLocalesBug()
14
+ }
@@ -0,0 +1,18 @@
1
+ import '@formatjs/intl-getcanonicallocales/polyfill'
2
+ import {Locale} from '../'
3
+
4
+ describe('intl-locale', () => {
5
+ it('toString', function () {
6
+ // expect(() => IntlLocale.prototype.toString.call(IntlLocale.prototype)).toThrowError(TypeError)
7
+ expect(
8
+ new Locale('en-u-foo-bar-nu-thai-ca-buddhist-kk-true').toString()
9
+ ).toBe('en-u-bar-foo-ca-buddhist-kk-nu-thai')
10
+ })
11
+ it('invalid tag', function () {
12
+ // expect(() => IntlLocale.prototype.toString.call(IntlLocale.prototype)).toThrowError(TypeError)
13
+ expect(() => new Locale({} as any)).toThrowError(RangeError)
14
+ })
15
+ it('und-x-private', function () {
16
+ expect(new Locale('und-x-private').toString()).toBe('und-x-private')
17
+ })
18
+ })
@@ -0,0 +1,108 @@
1
+ import '@formatjs/intl-getcanonicallocales/polyfill'
2
+ import {Locale} from '../'
3
+ const testDataMaximal: Record<string, string> = {
4
+ // Language subtag is present.
5
+ en: 'en-Latn-US',
6
+
7
+ // Language and script subtags are present.
8
+ 'en-Latn': 'en-Latn-US',
9
+ 'en-Shaw': 'en-Shaw-GB',
10
+ 'en-Arab': 'en-Arab-US',
11
+
12
+ // Language and region subtags are present.
13
+ 'en-US': 'en-Latn-US',
14
+ 'en-GB': 'en-Latn-GB',
15
+ 'en-FR': 'en-Latn-FR',
16
+
17
+ // Language, script, and region subtags are present.
18
+ 'it-Kana-CA': 'it-Kana-CA',
19
+
20
+ // Undefined primary language.
21
+ und: 'en-Latn-US',
22
+ 'und-Thai': 'th-Thai-TH',
23
+ 'und-419': 'es-Latn-419',
24
+ // 'und-150': 'ru-Cyrl-RU', TODO: Fix this or verify https://github.com/tc39/test262/issues/2628
25
+ 'und-AT': 'de-Latn-AT',
26
+ 'und-Cyrl-RO': 'bg-Cyrl-RO',
27
+
28
+ // Undefined primary language not required to change in all cases.
29
+ 'und-AQ': 'und-Latn-AQ',
30
+ }
31
+
32
+ const testDataMinimal: Record<string, string> = {
33
+ // Language subtag is present.
34
+ en: 'en',
35
+
36
+ // Language and script subtags are present.
37
+ 'en-Latn': 'en',
38
+ 'ar-Arab': 'ar',
39
+
40
+ // Language and region subtags are present.
41
+ 'en-US': 'en',
42
+ 'en-GB': 'en-GB',
43
+
44
+ // Reverse cases from |testDataMaximal|.
45
+ 'en-Latn-US': 'en',
46
+ 'en-Shaw-GB': 'en-Shaw',
47
+ 'en-Arab-US': 'en-Arab',
48
+ 'en-Latn-GB': 'en-GB',
49
+ 'en-Latn-FR': 'en-FR',
50
+ 'it-Kana-CA': 'it-Kana-CA',
51
+ 'th-Thai-TH': 'th',
52
+ 'es-Latn-419': 'es-419',
53
+ 'ru-Cyrl-RU': 'ru',
54
+ 'de-Latn-AT': 'de-AT',
55
+ 'bg-Cyrl-RO': 'bg-RO',
56
+ 'und-Latn-AQ': 'und-AQ',
57
+ }
58
+
59
+ // Add variants, extensions, and privateuse subtags and ensure they don't
60
+ // modify the result of the likely subtags algorithms.
61
+ const extras = [
62
+ '',
63
+ '-fonipa',
64
+ '-a-not-assigned',
65
+ '-u-attr',
66
+ '-u-co',
67
+ '-u-co-phonebk',
68
+ '-x-private',
69
+ ]
70
+
71
+ describe('likely-subtags', function () {
72
+ for (const tag in testDataMaximal) {
73
+ const maximal = testDataMaximal[tag]
74
+ it(`"${maximal}" should be maximal`, function () {
75
+ expect(new Locale(maximal).maximize().toString()).toBe(maximal)
76
+ })
77
+
78
+ for (const extra of extras) {
79
+ const input = tag + extra
80
+ const output = maximal + extra
81
+ it(`"${input}".maximize() should be "${output}"`, function () {
82
+ expect(new Locale(input).maximize().toString()).toBe(output)
83
+ })
84
+ }
85
+ }
86
+
87
+ for (const tag in testDataMinimal) {
88
+ const minimal = testDataMinimal[tag]
89
+ it(`"${minimal}" should be minimal`, function () {
90
+ expect(new Locale(minimal).minimize().toString()).toBe(minimal)
91
+ })
92
+
93
+ for (const extra of extras) {
94
+ const input = tag + extra
95
+ const output = minimal + extra
96
+ it(`"${input}".minimize() should be "${output}"`, function () {
97
+ expect(new Locale(input).minimize().toString()).toBe(output)
98
+ })
99
+ }
100
+ }
101
+
102
+ // privateuse only.
103
+ // "x" in "x-private" does not match unicode_language_subtag
104
+ // unicode_language_subtag = alpha{2,3} | alpha{5,8};
105
+ it('x-private', function () {
106
+ expect(() => new Locale('x-private')).toThrowError(RangeError)
107
+ })
108
+ })