@formatjs/intl-locale 3.0.1 → 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.
- package/BUILD +132 -0
- package/CHANGELOG.md +436 -0
- package/LICENSE.md +0 -0
- package/README.md +0 -0
- package/get_internal_slots.ts +15 -0
- package/index.ts +529 -0
- package/package.json +4 -4
- package/polyfill-force.ts +8 -0
- package/polyfill.ts +10 -0
- package/should-polyfill.ts +14 -0
- package/tests/index.test.ts +18 -0
- package/tests/likely-subtags.test.ts +108 -0
- package/tests/minimize.test.ts +35 -0
- package/tsconfig.json +5 -0
- package/get_internal_slots.d.ts +0 -3
- package/get_internal_slots.d.ts.map +0 -1
- package/get_internal_slots.js +0 -14
- package/index.d.ts +0 -49
- package/index.d.ts.map +0 -1
- package/index.js +0 -394
- package/lib/get_internal_slots.d.ts +0 -3
- package/lib/get_internal_slots.d.ts.map +0 -1
- package/lib/get_internal_slots.js +0 -11
- package/lib/index.d.ts +0 -49
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js +0 -391
- package/lib/polyfill-force.d.ts +0 -2
- package/lib/polyfill-force.d.ts.map +0 -1
- package/lib/polyfill-force.js +0 -7
- package/lib/polyfill.d.ts +0 -2
- package/lib/polyfill.d.ts.map +0 -1
- package/lib/polyfill.js +0 -10
- package/lib/should-polyfill.d.ts +0 -2
- package/lib/should-polyfill.d.ts.map +0 -1
- package/lib/should-polyfill.js +0 -14
- package/polyfill-force.d.ts +0 -2
- package/polyfill-force.d.ts.map +0 -1
- package/polyfill-force.js +0 -9
- package/polyfill.d.ts +0 -2
- package/polyfill.d.ts.map +0 -1
- package/polyfill.iife.js +0 -6468
- package/polyfill.js +0 -12
- package/should-polyfill.d.ts +0 -2
- package/should-polyfill.d.ts.map +0 -1
- 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
|
+
"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.
|
|
28
|
-
"@formatjs/intl-getcanonicallocales": "2.0.
|
|
27
|
+
"@formatjs/ecma402-abstract": "1.11.9",
|
|
28
|
+
"@formatjs/intl-getcanonicallocales": "2.0.3",
|
|
29
29
|
"tslib": "2.4.0"
|
|
30
30
|
}
|
|
31
|
-
}
|
|
31
|
+
}
|
package/polyfill.ts
ADDED
|
@@ -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
|
+
})
|