rfc-bcp47 0.0.0

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/dist/index.js ADDED
@@ -0,0 +1,1104 @@
1
+ //#region src/utilities.ts
2
+ function titleCase(value) {
3
+ return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
4
+ }
5
+ //#endregion
6
+ //#region src/operators/parse/parse.ts
7
+ const IRREGULAR_GRANDFATHERED = {
8
+ "en-gb-oed": "en-GB-oed",
9
+ "i-ami": "i-ami",
10
+ "i-bnn": "i-bnn",
11
+ "i-default": "i-default",
12
+ "i-enochian": "i-enochian",
13
+ "i-hak": "i-hak",
14
+ "i-klingon": "i-klingon",
15
+ "i-lux": "i-lux",
16
+ "i-mingo": "i-mingo",
17
+ "i-navajo": "i-navajo",
18
+ "i-pwn": "i-pwn",
19
+ "i-tao": "i-tao",
20
+ "i-tay": "i-tay",
21
+ "i-tsu": "i-tsu",
22
+ "sgn-be-fr": "sgn-BE-FR",
23
+ "sgn-be-nl": "sgn-BE-NL",
24
+ "sgn-ch-de": "sgn-CH-DE"
25
+ };
26
+ const REGULAR_GRANDFATHERED = {
27
+ "art-lojban": "art-lojban",
28
+ "cel-gaulish": "cel-gaulish",
29
+ "no-bok": "no-bok",
30
+ "no-nyn": "no-nyn",
31
+ "zh-guoyu": "zh-guoyu",
32
+ "zh-hakka": "zh-hakka",
33
+ "zh-min": "zh-min",
34
+ "zh-min-nan": "zh-min-nan",
35
+ "zh-xiang": "zh-xiang"
36
+ };
37
+ const LANGTAG_RE = /^(?:(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))$|^((?:[a-z]{2,3}(?:(?:-[a-z]{3}){1,3})?)|[a-z]{4}|[a-z]{5,8})(?:-([a-z]{4}))?(?:-([a-z]{2}|\d{3}))?((?:-(?:[\da-z]{5,8}|\d[\da-z]{3}))*)?((?:-[\da-wy-z](?:-[\da-z]{2,8})+)*)?(-x(?:-[\da-z]{1,8})+)?$|^(x(?:-[\da-z]{1,8})+)$/i;
38
+ function splitSubtags(raw) {
39
+ const parts = raw.split("-");
40
+ parts.shift();
41
+ return parts.map((subtag) => subtag.toLowerCase());
42
+ }
43
+ function hasDuplicates(subtags) {
44
+ const seen = /* @__PURE__ */ new Set();
45
+ for (const subtag of subtags) {
46
+ if (seen.has(subtag)) return true;
47
+ seen.add(subtag);
48
+ }
49
+ return false;
50
+ }
51
+ function parseLanguageAndExtlang(raw) {
52
+ const parts = raw.split("-");
53
+ return {
54
+ language: parts.shift().toLowerCase(),
55
+ extlang: parts.map((subtag) => subtag.toLowerCase())
56
+ };
57
+ }
58
+ function parseExtensions(raw) {
59
+ const tokens = raw.split("-");
60
+ tokens.shift();
61
+ const result = [];
62
+ const singletonsSeen = /* @__PURE__ */ new Set();
63
+ let currentSingleton;
64
+ let currentSubtags = [];
65
+ for (const token of tokens) if (token.length === 1) {
66
+ if (currentSingleton) result.push({
67
+ singleton: currentSingleton.toLowerCase(),
68
+ subtags: currentSubtags.map((subtag) => subtag.toLowerCase())
69
+ });
70
+ const lower = token.toLowerCase();
71
+ if (singletonsSeen.has(lower)) return null;
72
+ singletonsSeen.add(lower);
73
+ currentSingleton = token;
74
+ currentSubtags = [];
75
+ } else currentSubtags.push(token);
76
+ result.push({
77
+ singleton: currentSingleton.toLowerCase(),
78
+ subtags: currentSubtags.map((subtag) => subtag.toLowerCase())
79
+ });
80
+ return result;
81
+ }
82
+ /** Parse a BCP 47 language tag string into a structured object. Returns `null` for invalid input. */
83
+ function parse(tag) {
84
+ const match = LANGTAG_RE.exec(tag);
85
+ if (!match) return null;
86
+ match.shift();
87
+ const [irregular, regular, languageGroup, script, region, variantGroup, extensionGroup, langPrivateuseGroup, standalonePrivateuseGroup] = match;
88
+ if (irregular) return {
89
+ type: "grandfathered",
90
+ grandfathered: {
91
+ type: "irregular",
92
+ tag: IRREGULAR_GRANDFATHERED[irregular.toLowerCase()]
93
+ }
94
+ };
95
+ if (regular) return {
96
+ type: "grandfathered",
97
+ grandfathered: {
98
+ type: "regular",
99
+ tag: REGULAR_GRANDFATHERED[regular.toLowerCase()]
100
+ }
101
+ };
102
+ if (standalonePrivateuseGroup) {
103
+ const parts = standalonePrivateuseGroup.split("-");
104
+ parts.shift();
105
+ return {
106
+ type: "privateuse",
107
+ privateuse: parts.map((subtag) => subtag.toLowerCase())
108
+ };
109
+ }
110
+ const { language, extlang } = parseLanguageAndExtlang(languageGroup);
111
+ const variant = variantGroup ? splitSubtags(variantGroup) : [];
112
+ if (hasDuplicates(variant)) return null;
113
+ const extension = extensionGroup ? parseExtensions(extensionGroup) : [];
114
+ if (!extension) return null;
115
+ return {
116
+ type: "langtag",
117
+ langtag: {
118
+ language,
119
+ extlang,
120
+ script: script ? titleCase(script) : null,
121
+ region: region ? region.toUpperCase() : null,
122
+ variant,
123
+ extension,
124
+ privateuse: langPrivateuseGroup ? splitSubtags(langPrivateuseGroup).slice(1) : []
125
+ }
126
+ };
127
+ }
128
+ //#endregion
129
+ //#region src/operators/stringify/stringify.ts
130
+ /** Convert a parsed `BCP47Tag` object back into a well-formed language tag string. */
131
+ function stringify(tag) {
132
+ switch (tag.type) {
133
+ case "grandfathered": return tag.grandfathered.tag;
134
+ case "privateuse": return "x-" + tag.privateuse.map((subtag) => subtag.toLowerCase()).join("-");
135
+ case "langtag": {
136
+ const parts = [];
137
+ parts.push(tag.langtag.language.toLowerCase());
138
+ for (const subtag of tag.langtag.extlang) parts.push(subtag.toLowerCase());
139
+ if (tag.langtag.script) parts.push(titleCase(tag.langtag.script));
140
+ if (tag.langtag.region) parts.push(tag.langtag.region.toUpperCase());
141
+ for (const subtag of tag.langtag.variant) parts.push(subtag.toLowerCase());
142
+ for (const extension of tag.langtag.extension) {
143
+ parts.push(extension.singleton.toLowerCase());
144
+ for (const subtag of extension.subtags) parts.push(subtag.toLowerCase());
145
+ }
146
+ if (tag.langtag.privateuse.length > 0) {
147
+ parts.push("x");
148
+ for (const subtag of tag.langtag.privateuse) parts.push(subtag.toLowerCase());
149
+ }
150
+ return parts.join("-");
151
+ }
152
+ }
153
+ }
154
+ //#endregion
155
+ //#region src/operators/langtag/langtag.ts
156
+ const LANGUAGE_RE = /^[a-z]{2,8}$/i;
157
+ const EXTLANG_RE = /^[a-z]{3}$/i;
158
+ const SCRIPT_RE = /^[a-z]{4}$/i;
159
+ const REGION_RE = /^(?:[a-z]{2}|\d{3})$/i;
160
+ const VARIANT_RE = /^(?:[\da-z]{5,8}|\d[\da-z]{3})$/i;
161
+ const SINGLETON_RE = /^[\da-wy-z]$/i;
162
+ const EXTENSION_SUBTAG_RE = /^[\da-z]{2,8}$/i;
163
+ const PRIVATEUSE_RE = /^[\da-z]{1,8}$/i;
164
+ function validate(condition, message) {
165
+ if (!condition) throw new RangeError(message);
166
+ }
167
+ /** Build a `BCP47Tag` from parts with sensible defaults. Throws `RangeError` on invalid input. */
168
+ function langtag(language, options) {
169
+ validate(LANGUAGE_RE.test(language), `Invalid language: '${language}'`);
170
+ const extlang = options?.extlang ?? [];
171
+ if (extlang.length > 0) validate(language.length <= 3, `Extlang is only valid with 2-3 character language subtags, got '${language}'`);
172
+ for (const subtag of extlang) validate(EXTLANG_RE.test(subtag), `Invalid extlang: '${subtag}'`);
173
+ validate(extlang.length <= 3, `Too many extlang subtags: ${extlang.length} (max 3)`);
174
+ if (options?.script !== void 0) validate(SCRIPT_RE.test(options.script), `Invalid script: '${options.script}'`);
175
+ if (options?.region !== void 0) validate(REGION_RE.test(options.region), `Invalid region: '${options.region}'`);
176
+ const variant = options?.variant ?? [];
177
+ const variantsSeen = /* @__PURE__ */ new Set();
178
+ for (const subtag of variant) {
179
+ validate(VARIANT_RE.test(subtag), `Invalid variant: '${subtag}'`);
180
+ const lower = subtag.toLowerCase();
181
+ validate(!variantsSeen.has(lower), `Duplicate variant: '${subtag}'`);
182
+ variantsSeen.add(lower);
183
+ }
184
+ const extension = options?.extension ?? [];
185
+ const singletons = /* @__PURE__ */ new Set();
186
+ for (const entry of extension) {
187
+ validate(SINGLETON_RE.test(entry.singleton), `Invalid extension singleton: '${entry.singleton}'`);
188
+ const lower = entry.singleton.toLowerCase();
189
+ validate(!singletons.has(lower), `Duplicate extension singleton: '${entry.singleton}'`);
190
+ singletons.add(lower);
191
+ validate(entry.subtags.length > 0, `Extension '${entry.singleton}' must have at least one subtag`);
192
+ for (const subtag of entry.subtags) validate(EXTENSION_SUBTAG_RE.test(subtag), `Invalid extension subtag: '${subtag}'`);
193
+ }
194
+ const privateuse = options?.privateuse ?? [];
195
+ for (const subtag of privateuse) validate(PRIVATEUSE_RE.test(subtag), `Invalid privateuse subtag: '${subtag}'`);
196
+ return {
197
+ type: "langtag",
198
+ langtag: {
199
+ language: language.toLowerCase(),
200
+ extlang: extlang.map((subtag) => subtag.toLowerCase()),
201
+ script: options?.script ? titleCase(options.script) : null,
202
+ region: options?.region ? options.region.toUpperCase() : null,
203
+ variant: variant.map((subtag) => subtag.toLowerCase()),
204
+ extension: extension.map((entry) => ({
205
+ singleton: entry.singleton.toLowerCase(),
206
+ subtags: entry.subtags.map((subtag) => subtag.toLowerCase())
207
+ })),
208
+ privateuse: privateuse.map((subtag) => subtag.toLowerCase())
209
+ }
210
+ };
211
+ }
212
+ //#endregion
213
+ //#region src/language-subtag-registry.ts
214
+ const DEPRECATED_LANGUAGES = {
215
+ aam: "aas",
216
+ adp: "dz",
217
+ ajp: "apc",
218
+ ajt: "aeb",
219
+ asd: "snz",
220
+ aue: "ktz",
221
+ ayx: "nun",
222
+ bgm: "bcg",
223
+ bic: "bir",
224
+ bjd: "drl",
225
+ blg: "iba",
226
+ ccq: "rki",
227
+ cjr: "mom",
228
+ cka: "cmr",
229
+ cmk: "xch",
230
+ coy: "pij",
231
+ cqu: "quh",
232
+ dek: "sqm",
233
+ dit: "dif",
234
+ drh: "khk",
235
+ drr: "kzk",
236
+ drw: "prs",
237
+ gav: "dev",
238
+ gfx: "vaj",
239
+ ggn: "gvr",
240
+ gli: "kzk",
241
+ gti: "nyc",
242
+ guv: "duz",
243
+ hrr: "jal",
244
+ ibi: "opa",
245
+ ilw: "gal",
246
+ in: "id",
247
+ iw: "he",
248
+ jeg: "oyb",
249
+ ji: "yi",
250
+ jw: "jv",
251
+ kgc: "tdf",
252
+ kgh: "kml",
253
+ kgm: "plu",
254
+ koj: "kwv",
255
+ krm: "bmf",
256
+ ktr: "dtp",
257
+ kvs: "gdj",
258
+ kwq: "yam",
259
+ kxe: "tvd",
260
+ kxl: "kru",
261
+ kzj: "dtp",
262
+ kzt: "dtp",
263
+ lak: "ksp",
264
+ lii: "raq",
265
+ llo: "ngt",
266
+ lmm: "rmx",
267
+ meg: "cir",
268
+ mo: "ro",
269
+ mst: "mry",
270
+ mwj: "vaj",
271
+ myd: "aog",
272
+ myt: "mry",
273
+ nad: "xny",
274
+ ncp: "kdz",
275
+ nns: "nbr",
276
+ nnx: "ngv",
277
+ nom: "cbr",
278
+ nte: "eko",
279
+ nts: "pij",
280
+ nxu: "bpp",
281
+ oun: "vaj",
282
+ pat: "kxr",
283
+ pcr: "adx",
284
+ pmc: "huw",
285
+ pmk: "crr",
286
+ pmu: "phr",
287
+ ppa: "bfy",
288
+ ppr: "lcq",
289
+ prp: "gu",
290
+ pry: "prt",
291
+ puz: "pub",
292
+ sca: "hle",
293
+ skk: "oyb",
294
+ smd: "kmb",
295
+ snb: "iba",
296
+ szd: "umi",
297
+ tdu: "dtp",
298
+ thc: "tpo",
299
+ thw: "ola",
300
+ thx: "oyb",
301
+ tie: "ras",
302
+ tkk: "twm",
303
+ tlw: "weo",
304
+ tmk: "tdg",
305
+ tmp: "tyj",
306
+ tne: "kak",
307
+ tnf: "prs",
308
+ tpw: "tpn",
309
+ tsf: "taj",
310
+ uok: "ema",
311
+ xba: "cax",
312
+ xia: "acn",
313
+ xkh: "waw",
314
+ xrq: "dmw",
315
+ xss: "zko",
316
+ ybd: "rki",
317
+ yma: "lrr",
318
+ ymt: "mtm",
319
+ yos: "zom",
320
+ yuu: "yug",
321
+ zir: "scv",
322
+ zkb: "kjh"
323
+ };
324
+ const EXTLANG_PREFIXES = {
325
+ aao: "ar",
326
+ abh: "ar",
327
+ abv: "ar",
328
+ acm: "ar",
329
+ acq: "ar",
330
+ acw: "ar",
331
+ acx: "ar",
332
+ acy: "ar",
333
+ adf: "ar",
334
+ ads: "sgn",
335
+ aeb: "ar",
336
+ aec: "ar",
337
+ aed: "sgn",
338
+ aen: "sgn",
339
+ afb: "ar",
340
+ afg: "sgn",
341
+ ajs: "sgn",
342
+ apc: "ar",
343
+ apd: "ar",
344
+ arb: "ar",
345
+ arq: "ar",
346
+ ars: "ar",
347
+ ary: "ar",
348
+ arz: "ar",
349
+ ase: "sgn",
350
+ asf: "sgn",
351
+ asp: "sgn",
352
+ asq: "sgn",
353
+ asw: "sgn",
354
+ auz: "ar",
355
+ avl: "ar",
356
+ ayh: "ar",
357
+ ayl: "ar",
358
+ ayn: "ar",
359
+ ayp: "ar",
360
+ bfi: "sgn",
361
+ bfk: "sgn",
362
+ bjn: "ms",
363
+ bog: "sgn",
364
+ bqn: "sgn",
365
+ bqy: "sgn",
366
+ btj: "ms",
367
+ bve: "ms",
368
+ bvl: "sgn",
369
+ bvu: "ms",
370
+ bzs: "sgn",
371
+ cdo: "zh",
372
+ cds: "sgn",
373
+ cjy: "zh",
374
+ cmn: "zh",
375
+ cnp: "zh",
376
+ coa: "ms",
377
+ cpx: "zh",
378
+ csc: "sgn",
379
+ csd: "sgn",
380
+ cse: "sgn",
381
+ csf: "sgn",
382
+ csg: "sgn",
383
+ csl: "sgn",
384
+ csn: "sgn",
385
+ csp: "zh",
386
+ csq: "sgn",
387
+ csr: "sgn",
388
+ csx: "sgn",
389
+ czh: "zh",
390
+ czo: "zh",
391
+ doq: "sgn",
392
+ dse: "sgn",
393
+ dsl: "sgn",
394
+ dsz: "sgn",
395
+ dup: "ms",
396
+ ecs: "sgn",
397
+ ehs: "sgn",
398
+ esl: "sgn",
399
+ esn: "sgn",
400
+ eso: "sgn",
401
+ eth: "sgn",
402
+ fcs: "sgn",
403
+ fse: "sgn",
404
+ fsl: "sgn",
405
+ fss: "sgn",
406
+ gan: "zh",
407
+ gds: "sgn",
408
+ gom: "kok",
409
+ gse: "sgn",
410
+ gsg: "sgn",
411
+ gsm: "sgn",
412
+ gss: "sgn",
413
+ gus: "sgn",
414
+ hab: "sgn",
415
+ haf: "sgn",
416
+ hak: "zh",
417
+ hds: "sgn",
418
+ hji: "ms",
419
+ hks: "sgn",
420
+ hnm: "zh",
421
+ hos: "sgn",
422
+ hps: "sgn",
423
+ hsh: "sgn",
424
+ hsl: "sgn",
425
+ hsn: "zh",
426
+ icl: "sgn",
427
+ iks: "sgn",
428
+ ils: "sgn",
429
+ inl: "sgn",
430
+ ins: "sgn",
431
+ ise: "sgn",
432
+ isg: "sgn",
433
+ isr: "sgn",
434
+ jak: "ms",
435
+ jax: "ms",
436
+ jcs: "sgn",
437
+ jhs: "sgn",
438
+ jks: "sgn",
439
+ jls: "sgn",
440
+ jos: "sgn",
441
+ jsl: "sgn",
442
+ jus: "sgn",
443
+ kgi: "sgn",
444
+ knn: "kok",
445
+ kvb: "ms",
446
+ kvk: "sgn",
447
+ kvr: "ms",
448
+ kxd: "ms",
449
+ lbs: "sgn",
450
+ lce: "ms",
451
+ lcf: "ms",
452
+ lgs: "sgn",
453
+ liw: "ms",
454
+ lls: "sgn",
455
+ lsb: "sgn",
456
+ lsc: "sgn",
457
+ lsl: "sgn",
458
+ lsn: "sgn",
459
+ lso: "sgn",
460
+ lsp: "sgn",
461
+ lst: "sgn",
462
+ lsv: "sgn",
463
+ lsw: "sgn",
464
+ lsy: "sgn",
465
+ ltg: "lv",
466
+ luh: "zh",
467
+ lvs: "lv",
468
+ lws: "sgn",
469
+ lzh: "zh",
470
+ max: "ms",
471
+ mdl: "sgn",
472
+ meo: "ms",
473
+ mfa: "ms",
474
+ mfb: "ms",
475
+ mfs: "sgn",
476
+ min: "ms",
477
+ mnp: "zh",
478
+ mqg: "ms",
479
+ mre: "sgn",
480
+ msd: "sgn",
481
+ msi: "ms",
482
+ msr: "sgn",
483
+ mui: "ms",
484
+ mzc: "sgn",
485
+ mzg: "sgn",
486
+ mzy: "sgn",
487
+ nan: "zh",
488
+ nbs: "sgn",
489
+ ncs: "sgn",
490
+ nsi: "sgn",
491
+ nsl: "sgn",
492
+ nsp: "sgn",
493
+ nsr: "sgn",
494
+ nzs: "sgn",
495
+ okl: "sgn",
496
+ orn: "ms",
497
+ ors: "ms",
498
+ pel: "ms",
499
+ pga: "ar",
500
+ pgz: "sgn",
501
+ pks: "sgn",
502
+ prl: "sgn",
503
+ prz: "sgn",
504
+ psc: "sgn",
505
+ psd: "sgn",
506
+ pse: "ms",
507
+ psg: "sgn",
508
+ psl: "sgn",
509
+ pso: "sgn",
510
+ psp: "sgn",
511
+ psr: "sgn",
512
+ pys: "sgn",
513
+ rib: "sgn",
514
+ rms: "sgn",
515
+ rnb: "sgn",
516
+ rsl: "sgn",
517
+ rsm: "sgn",
518
+ rsn: "sgn",
519
+ sdl: "sgn",
520
+ sfb: "sgn",
521
+ sfs: "sgn",
522
+ sgg: "sgn",
523
+ sgx: "sgn",
524
+ shu: "ar",
525
+ sjc: "zh",
526
+ slf: "sgn",
527
+ sls: "sgn",
528
+ sqk: "sgn",
529
+ sqs: "sgn",
530
+ sqx: "sgn",
531
+ ssh: "ar",
532
+ ssp: "sgn",
533
+ ssr: "sgn",
534
+ svk: "sgn",
535
+ swc: "sw",
536
+ swh: "sw",
537
+ swl: "sgn",
538
+ syy: "sgn",
539
+ szs: "sgn",
540
+ tmw: "ms",
541
+ tse: "sgn",
542
+ tsm: "sgn",
543
+ tsq: "sgn",
544
+ tss: "sgn",
545
+ tsy: "sgn",
546
+ tza: "sgn",
547
+ ugn: "sgn",
548
+ ugy: "sgn",
549
+ ukl: "sgn",
550
+ uks: "sgn",
551
+ urk: "ms",
552
+ uzn: "uz",
553
+ uzs: "uz",
554
+ vgt: "sgn",
555
+ vkk: "ms",
556
+ vkt: "ms",
557
+ vsi: "sgn",
558
+ vsl: "sgn",
559
+ vsv: "sgn",
560
+ wbs: "sgn",
561
+ wuu: "zh",
562
+ xki: "sgn",
563
+ xml: "sgn",
564
+ xmm: "ms",
565
+ xms: "sgn",
566
+ ygs: "sgn",
567
+ yhs: "sgn",
568
+ ysl: "sgn",
569
+ ysm: "sgn",
570
+ yue: "zh",
571
+ zib: "sgn",
572
+ zlm: "ms",
573
+ zmi: "ms",
574
+ zsl: "sgn",
575
+ zsm: "ms"
576
+ };
577
+ const SUPPRESS_SCRIPTS = {
578
+ ab: "Cyrl",
579
+ af: "Latn",
580
+ am: "Ethi",
581
+ ar: "Arab",
582
+ as: "Beng",
583
+ ay: "Latn",
584
+ be: "Cyrl",
585
+ bg: "Cyrl",
586
+ bn: "Beng",
587
+ bs: "Latn",
588
+ ca: "Latn",
589
+ ch: "Latn",
590
+ cs: "Latn",
591
+ cy: "Latn",
592
+ da: "Latn",
593
+ de: "Latn",
594
+ dsb: "Latn",
595
+ dv: "Thaa",
596
+ dz: "Tibt",
597
+ el: "Grek",
598
+ en: "Latn",
599
+ eo: "Latn",
600
+ es: "Latn",
601
+ et: "Latn",
602
+ eu: "Latn",
603
+ fa: "Arab",
604
+ fi: "Latn",
605
+ fj: "Latn",
606
+ fo: "Latn",
607
+ fr: "Latn",
608
+ frr: "Latn",
609
+ frs: "Latn",
610
+ fy: "Latn",
611
+ ga: "Latn",
612
+ gl: "Latn",
613
+ gn: "Latn",
614
+ gsw: "Latn",
615
+ gu: "Gujr",
616
+ gv: "Latn",
617
+ he: "Hebr",
618
+ hi: "Deva",
619
+ hr: "Latn",
620
+ hsb: "Latn",
621
+ ht: "Latn",
622
+ hu: "Latn",
623
+ hy: "Armn",
624
+ id: "Latn",
625
+ is: "Latn",
626
+ it: "Latn",
627
+ ja: "Jpan",
628
+ ka: "Geor",
629
+ kk: "Cyrl",
630
+ kl: "Latn",
631
+ km: "Khmr",
632
+ kn: "Knda",
633
+ ko: "Kore",
634
+ kok: "Deva",
635
+ la: "Latn",
636
+ lb: "Latn",
637
+ ln: "Latn",
638
+ lo: "Laoo",
639
+ lt: "Latn",
640
+ lv: "Latn",
641
+ mai: "Deva",
642
+ men: "Latn",
643
+ mg: "Latn",
644
+ mh: "Latn",
645
+ mk: "Cyrl",
646
+ ml: "Mlym",
647
+ mr: "Deva",
648
+ ms: "Latn",
649
+ mt: "Latn",
650
+ my: "Mymr",
651
+ na: "Latn",
652
+ nb: "Latn",
653
+ nd: "Latn",
654
+ nds: "Latn",
655
+ ne: "Deva",
656
+ niu: "Latn",
657
+ nl: "Latn",
658
+ nn: "Latn",
659
+ no: "Latn",
660
+ nqo: "Nkoo",
661
+ nr: "Latn",
662
+ nso: "Latn",
663
+ ny: "Latn",
664
+ om: "Latn",
665
+ or: "Orya",
666
+ pa: "Guru",
667
+ pl: "Latn",
668
+ ps: "Arab",
669
+ pt: "Latn",
670
+ qu: "Latn",
671
+ rm: "Latn",
672
+ rn: "Latn",
673
+ ro: "Latn",
674
+ ru: "Cyrl",
675
+ rw: "Latn",
676
+ sg: "Latn",
677
+ si: "Sinh",
678
+ sk: "Latn",
679
+ sl: "Latn",
680
+ sm: "Latn",
681
+ so: "Latn",
682
+ sq: "Latn",
683
+ ss: "Latn",
684
+ st: "Latn",
685
+ sv: "Latn",
686
+ sw: "Latn",
687
+ ta: "Taml",
688
+ te: "Telu",
689
+ tem: "Latn",
690
+ th: "Thai",
691
+ ti: "Ethi",
692
+ tkl: "Latn",
693
+ tl: "Latn",
694
+ tmh: "Latn",
695
+ tn: "Latn",
696
+ to: "Latn",
697
+ tpi: "Latn",
698
+ tr: "Latn",
699
+ ts: "Latn",
700
+ tvl: "Latn",
701
+ uk: "Cyrl",
702
+ ur: "Arab",
703
+ ve: "Latn",
704
+ vi: "Latn",
705
+ xh: "Latn",
706
+ yi: "Hebr",
707
+ zbl: "Blis",
708
+ zu: "Latn"
709
+ };
710
+ const DEPRECATED_REGIONS = {
711
+ BU: "MM",
712
+ DD: "DE",
713
+ FX: "FR",
714
+ TP: "TL",
715
+ YD: "YE",
716
+ ZR: "CD"
717
+ };
718
+ const DEPRECATED_VARIANTS = { heploc: "alalc97" };
719
+ const REDUNDANT_PREFERRED = {
720
+ "sgn-BR": "bzs",
721
+ "sgn-CO": "csn",
722
+ "sgn-DE": "gsg",
723
+ "sgn-DK": "dsl",
724
+ "sgn-ES": "ssp",
725
+ "sgn-FR": "fsl",
726
+ "sgn-GB": "bfi",
727
+ "sgn-GR": "gss",
728
+ "sgn-IE": "isg",
729
+ "sgn-IT": "ise",
730
+ "sgn-JP": "jsl",
731
+ "sgn-MX": "mfs",
732
+ "sgn-NI": "ncs",
733
+ "sgn-NL": "dse",
734
+ "sgn-NO": "nsl",
735
+ "sgn-PT": "psr",
736
+ "sgn-SE": "swl",
737
+ "sgn-US": "ase",
738
+ "sgn-ZA": "sfs"
739
+ };
740
+ const GRANDFATHERED_PREFERRED = {
741
+ "art-lojban": "jbo",
742
+ "en-gb-oed": "en-GB-oxendict",
743
+ "i-ami": "ami",
744
+ "i-bnn": "bnn",
745
+ "i-hak": "hak",
746
+ "i-klingon": "tlh",
747
+ "i-lux": "lb",
748
+ "i-navajo": "nv",
749
+ "i-pwn": "pwn",
750
+ "i-tao": "tao",
751
+ "i-tay": "tay",
752
+ "i-tsu": "tsu",
753
+ "no-bok": "nb",
754
+ "no-nyn": "nn",
755
+ "sgn-be-fr": "sfb",
756
+ "sgn-be-nl": "vgt",
757
+ "sgn-ch-de": "sgg",
758
+ "zh-guoyu": "cmn",
759
+ "zh-hakka": "hak",
760
+ "zh-min-nan": "nan",
761
+ "zh-xiang": "hsn"
762
+ };
763
+ //#endregion
764
+ //#region src/operators/canonicalize/canonicalize.ts
765
+ function canonicalizeTag(tag) {
766
+ if (tag.type !== "langtag") return tag;
767
+ let language = tag.langtag.language;
768
+ let extlang = tag.langtag.extlang;
769
+ let script = tag.langtag.script;
770
+ let region = tag.langtag.region;
771
+ const preferred = DEPRECATED_LANGUAGES[language];
772
+ if (preferred) language = preferred;
773
+ if (extlang.length > 0) {
774
+ const prefix = EXTLANG_PREFIXES[extlang[0]];
775
+ if (prefix && prefix === language) {
776
+ language = extlang[0];
777
+ extlang = extlang.slice(1);
778
+ }
779
+ }
780
+ if (script) {
781
+ const suppressed = SUPPRESS_SCRIPTS[language];
782
+ if (suppressed && suppressed === script) script = null;
783
+ }
784
+ if (region) {
785
+ const preferredRegion = DEPRECATED_REGIONS[region];
786
+ if (preferredRegion) region = preferredRegion;
787
+ }
788
+ if (region) {
789
+ const redundant = REDUNDANT_PREFERRED[`${language}-${region}`];
790
+ if (redundant) {
791
+ language = redundant;
792
+ extlang = [];
793
+ region = null;
794
+ }
795
+ }
796
+ const variant = tag.langtag.variant.map((subtag) => {
797
+ return DEPRECATED_VARIANTS[subtag] ?? subtag;
798
+ });
799
+ const canonicalizedExtensions = tag.langtag.extension.map((extension) => {
800
+ if (extension.singleton === "u") return canonicalizeUExtension(extension);
801
+ if (extension.singleton === "t") return canonicalizeTExtension(extension);
802
+ return extension;
803
+ });
804
+ const sorted = canonicalizedExtensions.length > 1 ? [...canonicalizedExtensions].sort((left, right) => {
805
+ if (left.singleton < right.singleton) return -1;
806
+ if (left.singleton > right.singleton) return 1;
807
+ return 0;
808
+ }) : canonicalizedExtensions;
809
+ return {
810
+ type: "langtag",
811
+ langtag: {
812
+ ...tag.langtag,
813
+ language,
814
+ extlang,
815
+ script,
816
+ region,
817
+ variant,
818
+ extension: sorted
819
+ }
820
+ };
821
+ }
822
+ const UKEY_RE$1 = /^[a-z\d][a-z]$/;
823
+ const TKEY_RE$1 = /^[a-z]\d$/;
824
+ function canonicalizeUExtension(extension) {
825
+ const attributes = [];
826
+ const keywords = [];
827
+ let currentKey = null;
828
+ let currentValues = [];
829
+ for (const subtag of extension.subtags) if (subtag.length === 2 && UKEY_RE$1.test(subtag)) {
830
+ if (currentKey) keywords.push({
831
+ key: currentKey,
832
+ values: currentValues
833
+ });
834
+ currentKey = subtag;
835
+ currentValues = [];
836
+ } else if (currentKey) currentValues.push(subtag);
837
+ else attributes.push(subtag);
838
+ if (currentKey) keywords.push({
839
+ key: currentKey,
840
+ values: currentValues
841
+ });
842
+ keywords.sort((left, right) => {
843
+ if (left.key < right.key) return -1;
844
+ if (left.key > right.key) return 1;
845
+ return 0;
846
+ });
847
+ attributes.sort();
848
+ const subtags = [...attributes];
849
+ for (const keyword of keywords) subtags.push(keyword.key, ...keyword.values);
850
+ return {
851
+ singleton: extension.singleton,
852
+ subtags
853
+ };
854
+ }
855
+ function canonicalizeTExtension(extension) {
856
+ const sourceParts = [];
857
+ const fields = [];
858
+ let currentKey = null;
859
+ let currentValues = [];
860
+ let inSource = true;
861
+ for (const subtag of extension.subtags) if (TKEY_RE$1.test(subtag)) {
862
+ inSource = false;
863
+ if (currentKey) fields.push({
864
+ key: currentKey,
865
+ values: currentValues
866
+ });
867
+ currentKey = subtag;
868
+ currentValues = [];
869
+ } else if (inSource) sourceParts.push(subtag);
870
+ else currentValues.push(subtag);
871
+ if (currentKey) fields.push({
872
+ key: currentKey,
873
+ values: currentValues
874
+ });
875
+ fields.sort((left, right) => {
876
+ if (left.key < right.key) return -1;
877
+ if (left.key > right.key) return 1;
878
+ return 0;
879
+ });
880
+ const subtags = [...sourceParts];
881
+ for (const field of fields) subtags.push(field.key, ...field.values);
882
+ return {
883
+ singleton: extension.singleton,
884
+ subtags
885
+ };
886
+ }
887
+ /** Canonicalize a BCP 47 tag per RFC 5646 §4.5 (case, deprecated subtags, suppress-script, extlang). Returns `null` for invalid input. */
888
+ function canonicalize(tag) {
889
+ const parsed = parse(tag);
890
+ if (!parsed) return null;
891
+ if (parsed.type === "grandfathered") {
892
+ const preferred = GRANDFATHERED_PREFERRED[parsed.grandfathered.tag.toLowerCase()];
893
+ if (preferred) return canonicalize(preferred);
894
+ return stringify(parsed);
895
+ }
896
+ return stringify(canonicalizeTag(parsed));
897
+ }
898
+ //#endregion
899
+ //#region src/operators/filter/filter.ts
900
+ function matchSubtags(rangeSubtags, tagSubtags) {
901
+ let rangeIndex = 0;
902
+ let tagIndex = 0;
903
+ if (rangeSubtags[0] !== "*" && rangeSubtags[0].toLowerCase() !== tagSubtags[0].toLowerCase()) return false;
904
+ rangeIndex++;
905
+ tagIndex++;
906
+ while (rangeIndex < rangeSubtags.length) {
907
+ const rangeSubtag = rangeSubtags[rangeIndex];
908
+ if (rangeSubtag === "*") {
909
+ rangeIndex++;
910
+ continue;
911
+ }
912
+ if (tagIndex >= tagSubtags.length) return false;
913
+ const tagSubtag = tagSubtags[tagIndex];
914
+ if (rangeSubtag.toLowerCase() === tagSubtag.toLowerCase()) {
915
+ rangeIndex++;
916
+ tagIndex++;
917
+ continue;
918
+ }
919
+ if (tagSubtag.length === 1) return false;
920
+ tagIndex++;
921
+ }
922
+ return true;
923
+ }
924
+ /** Find all matching tags via Extended Filtering per RFC 4647 §3.3.2. Supports `*` wildcards. */
925
+ function filter(tags, patterns) {
926
+ const rangeList = typeof patterns === "string" ? [patterns] : patterns;
927
+ const results = [];
928
+ const seen = /* @__PURE__ */ new Set();
929
+ for (const range of rangeList) {
930
+ if (range === "*") {
931
+ for (const tag of tags) {
932
+ const lower = tag.toLowerCase();
933
+ if (!seen.has(lower)) {
934
+ seen.add(lower);
935
+ results.push(tag);
936
+ }
937
+ }
938
+ continue;
939
+ }
940
+ const rangeSubtags = range.split("-");
941
+ for (const tag of tags) {
942
+ const tagLower = tag.toLowerCase();
943
+ if (seen.has(tagLower)) continue;
944
+ if (matchSubtags(rangeSubtags, tag.split("-"))) {
945
+ seen.add(tagLower);
946
+ results.push(tag);
947
+ }
948
+ }
949
+ }
950
+ return results;
951
+ }
952
+ //#endregion
953
+ //#region src/operators/lookup/lookup.ts
954
+ function truncate(subtags) {
955
+ subtags.pop();
956
+ while (subtags.length > 0 && subtags[subtags.length - 1].length === 1) subtags.pop();
957
+ return subtags;
958
+ }
959
+ /** Find the single best matching tag via Lookup per RFC 4647 §3.4. Returns first match, `defaultValue`, or `null`. */
960
+ function lookup(tags, preferences, defaultValue) {
961
+ const rangeList = typeof preferences === "string" ? [preferences] : preferences;
962
+ const tagMap = /* @__PURE__ */ new Map();
963
+ for (const tag of tags) {
964
+ const lower = tag.toLowerCase();
965
+ if (!tagMap.has(lower)) tagMap.set(lower, tag);
966
+ }
967
+ for (const range of rangeList) {
968
+ if (range === "*") continue;
969
+ let subtags = range.split("-");
970
+ while (subtags.length > 0) {
971
+ const candidate = subtags.join("-").toLowerCase();
972
+ const matched = tagMap.get(candidate);
973
+ if (matched) return matched;
974
+ subtags = truncate(subtags);
975
+ }
976
+ }
977
+ return defaultValue ?? null;
978
+ }
979
+ //#endregion
980
+ //#region src/operators/extension/extension.ts
981
+ const TKEY_RE = /^[a-z]\d$/i;
982
+ const UKEY_RE = /^[a-z\d][a-z]$/i;
983
+ function isUnicodeKey(subtag) {
984
+ return subtag.length === 2 && UKEY_RE.test(subtag);
985
+ }
986
+ /** Extract Unicode locale attributes and keywords from the `u` extension (RFC 6067). Returns `null` if absent. */
987
+ function extensionU(tag) {
988
+ if (tag.type !== "langtag") return null;
989
+ const uExtension = tag.langtag.extension.find((entry) => entry.singleton === "u");
990
+ if (!uExtension) return null;
991
+ const attributes = [];
992
+ const attributesSeen = /* @__PURE__ */ new Set();
993
+ const keywords = {};
994
+ let currentKey = null;
995
+ let currentValues = [];
996
+ for (const subtag of uExtension.subtags) if (isUnicodeKey(subtag)) {
997
+ if (currentKey && !(currentKey in keywords)) keywords[currentKey] = currentValues.join("-");
998
+ currentKey = subtag;
999
+ currentValues = [];
1000
+ } else if (currentKey) currentValues.push(subtag);
1001
+ else if (!attributesSeen.has(subtag)) {
1002
+ attributesSeen.add(subtag);
1003
+ attributes.push(subtag);
1004
+ }
1005
+ if (currentKey && !(currentKey in keywords)) keywords[currentKey] = currentValues.join("-");
1006
+ return {
1007
+ attributes,
1008
+ keywords
1009
+ };
1010
+ }
1011
+ /** Extract transformed content data from the `t` extension (RFC 6497). Returns `null` if absent. */
1012
+ function extensionT(tag) {
1013
+ if (tag.type !== "langtag") return null;
1014
+ const tExtension = tag.langtag.extension.find((entry) => entry.singleton === "t");
1015
+ if (!tExtension) return null;
1016
+ const sourceParts = [];
1017
+ const fields = {};
1018
+ let currentKey = null;
1019
+ let currentValues = [];
1020
+ let inSource = true;
1021
+ for (const subtag of tExtension.subtags) if (TKEY_RE.test(subtag)) {
1022
+ inSource = false;
1023
+ if (currentKey && !(currentKey in fields)) fields[currentKey] = currentValues.join("-");
1024
+ currentKey = subtag;
1025
+ currentValues = [];
1026
+ } else if (inSource) sourceParts.push(subtag);
1027
+ else currentValues.push(subtag);
1028
+ if (currentKey && !(currentKey in fields)) fields[currentKey] = currentValues.join("-");
1029
+ return {
1030
+ source: sourceParts.length > 0 ? sourceParts.join("-") : null,
1031
+ fields
1032
+ };
1033
+ }
1034
+ //#endregion
1035
+ //#region src/operators/extension/cldr-keys.ts
1036
+ const UNICODE_LOCALE_KEYS = {
1037
+ ca: "Calendar",
1038
+ cf: "Currency format",
1039
+ co: "Collation",
1040
+ cu: "Currency",
1041
+ dx: "Dictionary break exclusions",
1042
+ em: "Emoji presentation",
1043
+ fw: "First day of week",
1044
+ hc: "Hour cycle",
1045
+ ka: "Collation alternate handling",
1046
+ kb: "Collation backward sorting",
1047
+ kc: "Collation case level",
1048
+ kf: "Collation case first",
1049
+ kk: "Collation normalization",
1050
+ kn: "Collation numeric ordering",
1051
+ kr: "Collation reorder",
1052
+ ks: "Collation strength",
1053
+ kv: "Collation max variable",
1054
+ lb: "Line break",
1055
+ lw: "Word break",
1056
+ ms: "Measurement system",
1057
+ mu: "Measurement unit",
1058
+ nu: "Numbering system",
1059
+ rg: "Region override",
1060
+ sd: "Subdivision",
1061
+ ss: "Sentence break suppressions",
1062
+ tz: "Timezone",
1063
+ va: "Common variant"
1064
+ };
1065
+ const TRANSFORM_KEYS = {
1066
+ d0: "Transform destination",
1067
+ h0: "Hybrid locale",
1068
+ i0: "Input method",
1069
+ k0: "Keyboard",
1070
+ m0: "Transform mechanism",
1071
+ s0: "Transform source",
1072
+ t0: "Machine translation",
1073
+ x0: "Private use transform"
1074
+ };
1075
+ //#endregion
1076
+ //#region src/operators/accept-language/accept-language.ts
1077
+ const QUALITY_RE = /;\s*q=(0(?:\.\d{0,3})?|1(?:\.0{0,3})?)(?:\s*$|\s*,|\s*;)/i;
1078
+ const HAS_QUALITY_PARAM_RE = /;\s*q=/i;
1079
+ const SEMICOLON_PARAMS_RE = /;.*/;
1080
+ const LANGUAGE_RANGE_RE = /^(?:\*|[a-z]{1,8}(?:-[a-z\d]{1,8})*)$/i;
1081
+ /** Parse an `Accept-Language` header into entries sorted by quality descending (RFC 9110 §12.5.4). */
1082
+ function acceptLanguage(header) {
1083
+ if (!header.trim()) return [];
1084
+ const entries = [];
1085
+ for (const segment of header.split(",")) {
1086
+ const trimmed = segment.trim();
1087
+ if (!trimmed) continue;
1088
+ const tag = trimmed.replace(SEMICOLON_PARAMS_RE, "").trim();
1089
+ if (!tag || !LANGUAGE_RANGE_RE.test(tag)) continue;
1090
+ const qualityMatch = QUALITY_RE.exec(trimmed);
1091
+ if (!qualityMatch && HAS_QUALITY_PARAM_RE.test(trimmed)) continue;
1092
+ const quality = qualityMatch ? parseFloat(qualityMatch[1]) : 1;
1093
+ entries.push({
1094
+ tag,
1095
+ quality
1096
+ });
1097
+ }
1098
+ entries.sort((left, right) => right.quality - left.quality);
1099
+ return entries;
1100
+ }
1101
+ //#endregion
1102
+ export { TRANSFORM_KEYS, UNICODE_LOCALE_KEYS, acceptLanguage, canonicalize, extensionT, extensionU, filter, langtag, lookup, parse, stringify };
1103
+
1104
+ //# sourceMappingURL=index.js.map