@juit/vue-i18n 0.4.0 → 1.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/lib/index.ts ADDED
@@ -0,0 +1,386 @@
1
+ import { inject } from 'vue'
2
+
3
+ import { isISOLanguage } from './iso-639'
4
+ import { makeTranslator } from './translator'
5
+
6
+ import type { App } from 'vue'
7
+ import type { ISOCurrency } from './iso-4217'
8
+ import type { ISOLanguage } from './iso-639'
9
+ import type { Translator } from './translator'
10
+
11
+ /* ===== REFERENCE LANGUAGES, COUNTRIES, AND CURRENCIES ====================== */
12
+
13
+ export { isISOCountry, ISO_COUNTRIES } from './iso-3166'
14
+ export { isISOCurrency, ISO_CURRENCIES } from './iso-4217'
15
+ export { isISOLanguage, ISO_LANGUAGES } from './iso-639'
16
+
17
+ export type * from './iso-3166'
18
+ export type * from './iso-4217'
19
+ export type * from './iso-639'
20
+
21
+ /* ===== TYPES FOR DECLARATION MERGING ====================================== */
22
+
23
+ /**
24
+ * Application configuration types, supplied through declaration merging.
25
+ *
26
+ * This interface (intentionally empty) is used to merge the actual per-app
27
+ * configuration of the translation system, in order to provide the correct
28
+ * types to the rest of the system.
29
+ *
30
+ * The following properties can be defined as unions of string literals:
31
+ *
32
+ * * `languages`: the supported ISO 639-1 language codes. When configured
33
+ * with a subset of codes, each translation must include
34
+ * a message for every base language in that subset.
35
+ * * `translationKeys`: the translation keys known by the application.
36
+ * Those are the arbitrary keys used to identify the
37
+ * messages to be translated with the `t` and `tc`
38
+ * methods of `Translator`.
39
+ * * `dateTimeFormats`: the date and time format _aliases_ used by the
40
+ * application.
41
+ * * `numberFormats`: the number format _aliases_ used by the application.
42
+ *
43
+ * To configure the types, follow the example below:
44
+ *
45
+ * ```ts
46
+ * const translations = {
47
+ * 'hello': { en: 'Hello, world!', de: 'Hallo, Welt!' }
48
+ * } as const satisfies Translations
49
+ *
50
+ * const dateTimeFormats = {
51
+ * // override the default format
52
+ * default: { dateStyle: 'short', timeStyle: 'short' },
53
+ * // add a new custom format
54
+ * custom: {
55
+ * day: '2-digit',
56
+ * month: '2-digit',
57
+ * year: 'numeric',
58
+ * weekday: 'short',
59
+ * timeZone: 'UTC',
60
+ * },
61
+ * } as const satisfies DateTimeFormats
62
+ *
63
+ * const numberFormats = {
64
+ * speed: { style: 'unit', unit: 'kilometer-per-hour' },
65
+ * } as const satisfies NumberFormats
66
+ *
67
+ * declare module '@juit/vue-i18n' {
68
+ * export interface I18nConfiguration {
69
+ * languages: 'de' | 'en',
70
+ * translationKeys: keyof typeof translations,
71
+ * dateTimeFormats: keyof typeof dateTimeFormats,
72
+ * numberFormats: keyof typeof numberFormats,
73
+ * }
74
+ * }
75
+ * ```
76
+ */
77
+ export interface I18nConfiguration {
78
+ // intentionally empty
79
+ }
80
+
81
+ /* ===== FROM CONFIG TO TRANSLATIONS ======================================== */
82
+
83
+ /** Extract `T[K]` when present and assignable to `R`; otherwise fall back to `R`. */
84
+ type ExtractConfig<T, R, K extends string> = T extends { [ X in K ]: infer V } ? V extends R ? V : R : R
85
+
86
+ /** The languages configured in `I18nConfiguration` or all ISO languages */
87
+ export type Language = ExtractConfig<I18nConfiguration, ISOLanguage, 'languages'>
88
+
89
+ /** A message with optional pipe-delimited variants, or a readonly tuple of one to three variants. */
90
+ export type TranslationMessage = string | readonly [string, string?, string?]
91
+
92
+ /** Base languages are required for a configured subset; otherwise all are optional. */
93
+ type BaseTranslation = ISOLanguage extends Language ? {
94
+ readonly [ key in ISOLanguage ]?: TranslationMessage
95
+ } : {
96
+ readonly [ key in Language ]: TranslationMessage
97
+ }
98
+
99
+ /** Optional regional variants of each supported language. */
100
+ type ExtendedTranslation = {
101
+ readonly [ key in `${Language}-${string}` ]?: TranslationMessage
102
+ }
103
+
104
+ /** Expand the properties of the exported `Translation` type for editor hints. */
105
+ type PrettifyTranslation<T> = { [ l in keyof T ]: T[l] }
106
+
107
+ /**
108
+ * A type describing the translations for a given translation key.
109
+ *
110
+ * When `I18nConfiguration.languages` specifies a subset of ISO languages,
111
+ * every base language in that subset is required. Regional variants are
112
+ * optional. When unconfigured, or configured with all ISO languages, every
113
+ * language is optional.
114
+ */
115
+ export type Translation = PrettifyTranslation<BaseTranslation & ExtendedTranslation>
116
+
117
+ /**
118
+ * All known translation keys.
119
+ *
120
+ * `I18nConfiguration.translationKeys` restricts the keys accepted by the
121
+ * `t(...)` and `tc(...)` methods and by `utils.updateTranslations(...)`.
122
+ *
123
+ * When left unconfigured, this type will be `string`.
124
+ */
125
+ export type TranslationKey = ExtractConfig<I18nConfiguration, string, 'translationKeys'>
126
+
127
+ /**
128
+ * Supported date and time format aliases.
129
+ *
130
+ * `I18nConfiguration.dateTimeFormats` defines the custom aliases accepted
131
+ * by `d(...)`. Built-in aliases remain available.
132
+ *
133
+ * When left unconfigured, this type will be `string`.
134
+ */
135
+ export type DateTimeFormatAlias = ExtractConfig<I18nConfiguration, string, 'dateTimeFormats'>
136
+ | 'default' | 'short' | 'medium' | 'long' | 'full'
137
+ | 'date' | 'shortDate' | 'mediumDate' | 'longDate' | 'fullDate'
138
+ | 'time' | 'shortTime' | 'mediumTime' | 'longTime' | 'fullTime'
139
+
140
+ /**
141
+ * Supported number format aliases.
142
+ *
143
+ * `I18nConfiguration.numberFormats` defines the custom aliases accepted
144
+ * by `n(...)`. The `default` alias and currency code types remain available.
145
+ *
146
+ * When left unconfigured, this type will be `string`.
147
+ */
148
+ export type NumberFormatAlias = ExtractConfig<I18nConfiguration, string, 'numberFormats'>
149
+ | 'default' | ISOCurrency
150
+
151
+ /* ===== MODULE INITIALIZATION ============================================== */
152
+
153
+ /* Export the translator types */
154
+ export type * from './translator'
155
+
156
+ /**
157
+ * Options to initialize the translations handled by the translation system.
158
+ *
159
+ * Each key identifies a message, and its value maps languages and regional
160
+ * variants to message strings or plural tuples.
161
+ */
162
+ export interface Translations {
163
+ readonly [ key: string ]: Translation
164
+ }
165
+
166
+ /**
167
+ * Options to initialize the date and time format _aliases_ used by the
168
+ * translation system.
169
+ *
170
+ * The default aliases (each can be overridden) are:
171
+ *
172
+ * ```ts
173
+ * {
174
+ * default: { dateStyle: 'medium', timeStyle: 'medium' },
175
+ * short: { dateStyle: 'short', timeStyle: 'short' },
176
+ * medium: { dateStyle: 'medium', timeStyle: 'medium' },
177
+ * long: { dateStyle: 'long', timeStyle: 'long' },
178
+ * full: { dateStyle: 'full', timeStyle: 'full' },
179
+ *
180
+ * // Formats for dates only
181
+ * date: { dateStyle: 'medium' },
182
+ * shortDate: { dateStyle: 'short' },
183
+ * mediumDate: { dateStyle: 'medium' },
184
+ * longDate: { dateStyle: 'long' },
185
+ * fullDate: { dateStyle: 'full' },
186
+ *
187
+ * // Formats for times only
188
+ * time: { timeStyle: 'medium' },
189
+ * shortTime: { timeStyle: 'short' },
190
+ * mediumTime: { timeStyle: 'medium' },
191
+ * longTime: { timeStyle: 'long' },
192
+ * fullTime: { timeStyle: 'full' },
193
+ * }
194
+ * ```
195
+ */
196
+ export interface DateTimeFormats {
197
+ readonly [ key: string ]: Intl.DateTimeFormatOptions
198
+ }
199
+
200
+ /**
201
+ * Options to initialize the number format _aliases_ used by the translation
202
+ * system.
203
+ *
204
+ * The default aliases (each can be overridden) are:
205
+ *
206
+ * ```ts
207
+ * {
208
+ * default: { }, // use the default number format
209
+ * EUR: { style: 'currency', currency: 'EUR' },
210
+ * USD: { style: 'currency', currency: 'USD' },
211
+ * // ... codes from ISO_CURRENCIES are available as aliases
212
+ * }
213
+ * ```
214
+ */
215
+ export interface NumberFormats {
216
+ readonly [ key: string ]: Intl.NumberFormatOptions
217
+ }
218
+
219
+ /** The initial language or locale; only its language and region are retained. */
220
+ export type DefaultLanguage = ISOLanguage | `${ISOLanguage}-${string}` | Intl.Locale
221
+
222
+ /** Options to initialize the I18n plugin */
223
+ export interface I18nOptions {
224
+ defaultLanguage: DefaultLanguage,
225
+ defaultTimeZone?: string,
226
+ translations?: Translations,
227
+ dateTimeFormats?: DateTimeFormats,
228
+ numberFormats?: NumberFormats,
229
+ }
230
+
231
+ /* ===== PUBLIC METHODS ===================================================== */
232
+
233
+ /** Symbol for Vue injections */
234
+ const injectionSymbol = Symbol.for('@juit/vue-i18n/translator')
235
+
236
+ /** Initialize the translation system plugin */
237
+ export function i18n(app: App, optionsOrLanguage: Language | I18nOptions): App {
238
+ const options = typeof optionsOrLanguage === 'string' ?
239
+ { defaultLanguage: optionsOrLanguage } : optionsOrLanguage
240
+
241
+ const translator = makeTranslator(options)
242
+
243
+ app.config.globalProperties.$t = translator.t
244
+ app.config.globalProperties.$tc = translator.tc
245
+ app.config.globalProperties.$n = translator.n
246
+ app.config.globalProperties.$d = translator.d
247
+
248
+ app.provide(injectionSymbol, translator)
249
+ return app
250
+ }
251
+
252
+ /** Retrieve the translator from the current Vue injection context, or throw if none is provided. */
253
+ export function useTranslator(): Translator {
254
+ const translator = inject(injectionSymbol)
255
+ if (! translator) throw new Error('No translator found in the Vue app')
256
+ return translator as Translator
257
+ }
258
+
259
+ /* ===== VUE EXTENSIONS ===================================================== */
260
+
261
+ // Extension to the Vue component interface
262
+ declare module 'vue' {
263
+ interface ComponentCustomProperties {
264
+ /** Translate a message according to the current language */
265
+ $t: Translator['t']
266
+ /**
267
+ * Return the (possibly parameterized) translation for the specified message
268
+ * in the current language, with pluralization.
269
+ */
270
+ $tc: Translator['tc']
271
+ /** Format a number into a string according to the current locale. */
272
+ $n: Translator['n']
273
+ /**
274
+ * Format a date and time using the current locale and the specified
275
+ * format, or the configurable `default` alias when omitted.
276
+ */
277
+ $d: Translator['d']
278
+ }
279
+ }
280
+
281
+ /* ===== UTILITIES ========================================================== */
282
+
283
+ function normalizeLanguage(language: unknown): ISOLanguage | undefined {
284
+ if (typeof language !== 'string') return undefined
285
+
286
+ const normalized = language.toLowerCase().split(/[-_]/)[0]
287
+ if (! normalized) return undefined // empty after normalization
288
+
289
+ return isISOLanguage(normalized) ? normalized : undefined
290
+ }
291
+
292
+ interface LanguageMatcherConstructor {
293
+ /**
294
+ * Create a new {@link LanguageMatcher} instance matching *only* the single
295
+ * language specified.
296
+ *
297
+ * At runtime, the input is normalized. If it does not resolve to a valid
298
+ * ISO language code, an error is thrown.
299
+ */
300
+ new <L extends ISOLanguage>(availableLanguages: L): LanguageMatcher<[ L ]>
301
+ /**
302
+ * Create a new {@link LanguageMatcher} instance matching the specified set
303
+ * of available languages.
304
+ *
305
+ * Typed inputs must be valid ISO language codes. At runtime, inputs are
306
+ * normalized and invalid entries are filtered out. The resulting list is
307
+ * copied, so later changes to the input array do not affect the matcher.
308
+ *
309
+ * If no valid ISO languages are provided, an error will be thrown.
310
+ */
311
+ new <const A extends readonly [ ISOLanguage, ...ISOLanguage[] ]>(availableLanguages: A): LanguageMatcher<A>
312
+ }
313
+
314
+ /**
315
+ * A language matcher that determines the best matching language from a set
316
+ * of available languages.
317
+ */
318
+ export interface LanguageMatcher<T extends readonly [ ISOLanguage, ...ISOLanguage[] ]> {
319
+ /** The list of available languages, with the first one being the default */
320
+ readonly availableLanguages: Readonly<T>
321
+ /** The default language */
322
+ readonly defaultLanguage: T[0]
323
+
324
+ /**
325
+ * Determine the best matching language from the available languages.
326
+ *
327
+ * The first supported language in the input preference order is returned.
328
+ * Empty input or an input with no matches returns the default language.
329
+ *
330
+ * All languages here will be *normalized* before matching (for example
331
+ * `en-US` will be normalized to `en`, and `JA` will be normalized to `ja`).
332
+ *
333
+ * @param languages The language or list of languages to match against the
334
+ * available languages.
335
+ * @returns The best matching language from the available languages, or the
336
+ * default language if no match is found.
337
+ */
338
+ match(languages: readonly string[] | string | undefined | null): T[number]
339
+ }
340
+
341
+ /** Implementation of the {@link LanguageMatcher} interface */
342
+ class LanguageMatcherImpl implements LanguageMatcher<readonly [ ISOLanguage, ...ISOLanguage[] ]> {
343
+ readonly availableLanguages: readonly [ ISOLanguage, ...ISOLanguage[] ]
344
+ readonly defaultLanguage: ISOLanguage
345
+
346
+ constructor(availableLanguages: ISOLanguage | readonly ISOLanguage[]) {
347
+ const languages = typeof availableLanguages === 'string' ?
348
+ [ availableLanguages ] : availableLanguages
349
+
350
+ const [ defaultLanguage, ...extraLanguages ] = languages
351
+ .map(normalizeLanguage) // Normalize each language, returning undefined for invalid entries.
352
+ .filter((language) => !! language) // Remove invalid entries.
353
+
354
+ if (!defaultLanguage) {
355
+ throw new Error(`At least one valid ISO language must be provided (${languages.join(', ')})`)
356
+ }
357
+
358
+ this.defaultLanguage = defaultLanguage
359
+ this.availableLanguages = [ defaultLanguage, ...extraLanguages ]
360
+ }
361
+
362
+ match(languages: readonly string[] | string | undefined | null): ISOLanguage {
363
+ // Use the default when no preferences are supplied.
364
+ if (!languages) return this.defaultLanguage
365
+
366
+ // Normalize the input to an array of strings.
367
+ if (typeof languages === 'string') languages = [ languages ]
368
+
369
+ // Iterate over the provided languages in order of preference.
370
+ for (const language of languages) {
371
+ const normalized = normalizeLanguage(language)
372
+ if (! normalized) continue // empty after normalization
373
+
374
+ // Check if the normalized language is available. If so, we match!
375
+ if (this.availableLanguages.includes(normalized)) {
376
+ return normalized
377
+ }
378
+ }
379
+
380
+ // None of the provided languages matched, so we return the default.
381
+ return this.defaultLanguage
382
+ }
383
+ }
384
+
385
+ /** The {@link LanguageMatcher} constructor */
386
+ export const LanguageMatcher = LanguageMatcherImpl as LanguageMatcherConstructor
@@ -0,0 +1,272 @@
1
+ /** Frozen, sorted ISO 3166-1 country codes, plus the CLDR code XK for Kosovo. */
2
+ export const ISO_COUNTRIES = Object.freeze([
3
+ ...('ADAEAFAGAIALAMAOAQARASATAUAWAXAZBABBBDBEBFBGBHBIBJBLBMBNBOBQBRBSBTBVBW' +
4
+ 'BYBZCACCCDCFCGCHCICKCLCMCNCOCRCUCVCWCXCYCZDEDJDKDMDODZECEEEGEHERESETFIFJFK' +
5
+ 'FMFOFRGAGBGDGEGFGGGHGIGLGMGNGPGQGRGSGTGUGWGYHKHMHNHRHTHUIDIEILIMINIOIQIRIS' +
6
+ 'ITJEJMJOJPKEKGKHKIKMKNKPKRKWKYKZLALBLCLILKLRLSLTLULVLYMAMCMDMEMFMGMHMKMLMM' +
7
+ 'MNMOMPMQMRMSMTMUMVMWMXMYMZNANCNENFNGNINLNONPNRNUNZOMPAPEPFPGPHPKPLPMPNPRPS' +
8
+ 'PTPWPYQARERORSRURWSASBSCSDSESGSHSISJSKSLSMSNSOSRSSSTSVSXSYSZTCTDTFTGTHTJTK' +
9
+ 'TLTMTNTOTRTTTVTWTZUAUGUMUSUYUZVAVCVEVGVIVNVUWFWSXKYEYTZAZMZW').match(/../g)!,
10
+ ].sort() as ISOCountry[])
11
+
12
+ /** Reference country codes and names, including the CLDR code XK for Kosovo. */
13
+ export type ISOCountries = {
14
+ AD: 'Andorra',
15
+ AE: 'United Arab Emirates',
16
+ AF: 'Afghanistan',
17
+ AG: 'Antigua and Barbuda',
18
+ AI: 'Anguilla',
19
+ AL: 'Albania',
20
+ AM: 'Armenia',
21
+ AO: 'Angola',
22
+ AQ: 'Antarctica',
23
+ AR: 'Argentina',
24
+ AS: 'American Samoa',
25
+ AT: 'Austria',
26
+ AU: 'Australia',
27
+ AW: 'Aruba',
28
+ AX: 'Åland Islands',
29
+ AZ: 'Azerbaijan',
30
+ BA: 'Bosnia and Herzegovina',
31
+ BB: 'Barbados',
32
+ BD: 'Bangladesh',
33
+ BE: 'Belgium',
34
+ BF: 'Burkina Faso',
35
+ BG: 'Bulgaria',
36
+ BH: 'Bahrain',
37
+ BI: 'Burundi',
38
+ BJ: 'Benin',
39
+ BL: 'Saint Barthélemy',
40
+ BM: 'Bermuda',
41
+ BN: 'Brunei Darussalam',
42
+ BO: 'Bolivia (Plurinational State of)',
43
+ BQ: 'Bonaire, Sint Eustatius and Saba',
44
+ BR: 'Brazil',
45
+ BS: 'Bahamas',
46
+ BT: 'Bhutan',
47
+ BV: 'Bouvet Island',
48
+ BW: 'Botswana',
49
+ BY: 'Belarus',
50
+ BZ: 'Belize',
51
+ CA: 'Canada',
52
+ CC: 'Cocos (Keeling) Islands',
53
+ CD: 'Congo (Democratic Republic of the)',
54
+ CF: 'Central African Republic',
55
+ CG: 'Congo',
56
+ CH: 'Switzerland',
57
+ CI: 'Côte d\'Ivoire',
58
+ CK: 'Cook Islands',
59
+ CL: 'Chile',
60
+ CM: 'Cameroon',
61
+ CN: 'China',
62
+ CO: 'Colombia',
63
+ CR: 'Costa Rica',
64
+ CU: 'Cuba',
65
+ CV: 'Cabo Verde',
66
+ CW: 'Curaçao',
67
+ CX: 'Christmas Island',
68
+ CY: 'Cyprus',
69
+ CZ: 'Czechia',
70
+ DE: 'Germany',
71
+ DJ: 'Djibouti',
72
+ DK: 'Denmark',
73
+ DM: 'Dominica',
74
+ DO: 'Dominican Republic',
75
+ DZ: 'Algeria',
76
+ EC: 'Ecuador',
77
+ EE: 'Estonia',
78
+ EG: 'Egypt',
79
+ EH: 'Western Sahara',
80
+ ER: 'Eritrea',
81
+ ES: 'Spain',
82
+ ET: 'Ethiopia',
83
+ FI: 'Finland',
84
+ FJ: 'Fiji',
85
+ FK: 'Falkland Islands (Malvinas)',
86
+ FM: 'Micronesia (Federated States of)',
87
+ FO: 'Faroe Islands',
88
+ FR: 'France',
89
+ GA: 'Gabon',
90
+ GB: 'United Kingdom of Great Britain and Northern Ireland',
91
+ GD: 'Grenada',
92
+ GE: 'Georgia',
93
+ GF: 'French Guiana',
94
+ GG: 'Guernsey',
95
+ GH: 'Ghana',
96
+ GI: 'Gibraltar',
97
+ GL: 'Greenland',
98
+ GM: 'Gambia',
99
+ GN: 'Guinea',
100
+ GP: 'Guadeloupe',
101
+ GQ: 'Equatorial Guinea',
102
+ GR: 'Greece',
103
+ GS: 'South Georgia and the South Sandwich Islands',
104
+ GT: 'Guatemala',
105
+ GU: 'Guam',
106
+ GW: 'Guinea-Bissau',
107
+ GY: 'Guyana',
108
+ HK: 'Hong Kong',
109
+ HM: 'Heard Island and McDonald Islands',
110
+ HN: 'Honduras',
111
+ HR: 'Croatia',
112
+ HT: 'Haiti',
113
+ HU: 'Hungary',
114
+ ID: 'Indonesia',
115
+ IE: 'Ireland',
116
+ IL: 'Israel',
117
+ IM: 'Isle of Man',
118
+ IN: 'India',
119
+ IO: 'British Indian Ocean Territory',
120
+ IQ: 'Iraq',
121
+ IR: 'Iran (Islamic Republic of)',
122
+ IS: 'Iceland',
123
+ IT: 'Italy',
124
+ JE: 'Jersey',
125
+ JM: 'Jamaica',
126
+ JO: 'Jordan',
127
+ JP: 'Japan',
128
+ KE: 'Kenya',
129
+ KG: 'Kyrgyzstan',
130
+ KH: 'Cambodia',
131
+ KI: 'Kiribati',
132
+ KM: 'Comoros',
133
+ KN: 'Saint Kitts and Nevis',
134
+ KP: 'Korea (Democratic People\'s Republic of)',
135
+ KR: 'Korea (Republic of)',
136
+ KW: 'Kuwait',
137
+ KY: 'Cayman Islands',
138
+ KZ: 'Kazakhstan',
139
+ LA: 'Lao People\'s Democratic Republic',
140
+ LB: 'Lebanon',
141
+ LC: 'Saint Lucia',
142
+ LI: 'Liechtenstein',
143
+ LK: 'Sri Lanka',
144
+ LR: 'Liberia',
145
+ LS: 'Lesotho',
146
+ LT: 'Lithuania',
147
+ LU: 'Luxembourg',
148
+ LV: 'Latvia',
149
+ LY: 'Libya',
150
+ MA: 'Morocco',
151
+ MC: 'Monaco',
152
+ MD: 'Moldova (Republic of)',
153
+ ME: 'Montenegro',
154
+ MF: 'Saint Martin (French part)',
155
+ MG: 'Madagascar',
156
+ MH: 'Marshall Islands',
157
+ MK: 'Macedonia (the former Yugoslav Republic of)',
158
+ ML: 'Mali',
159
+ MM: 'Myanmar',
160
+ MN: 'Mongolia',
161
+ MO: 'Macao',
162
+ MP: 'Northern Mariana Islands',
163
+ MQ: 'Martinique',
164
+ MR: 'Mauritania',
165
+ MS: 'Montserrat',
166
+ MT: 'Malta',
167
+ MU: 'Mauritius',
168
+ MV: 'Maldives',
169
+ MW: 'Malawi',
170
+ MX: 'Mexico',
171
+ MY: 'Malaysia',
172
+ MZ: 'Mozambique',
173
+ NA: 'Namibia',
174
+ NC: 'New Caledonia',
175
+ NE: 'Niger',
176
+ NF: 'Norfolk Island',
177
+ NG: 'Nigeria',
178
+ NI: 'Nicaragua',
179
+ NL: 'Netherlands',
180
+ NO: 'Norway',
181
+ NP: 'Nepal',
182
+ NR: 'Nauru',
183
+ NU: 'Niue',
184
+ NZ: 'New Zealand',
185
+ OM: 'Oman',
186
+ PA: 'Panama',
187
+ PE: 'Peru',
188
+ PF: 'French Polynesia',
189
+ PG: 'Papua New Guinea',
190
+ PH: 'Philippines',
191
+ PK: 'Pakistan',
192
+ PL: 'Poland',
193
+ PM: 'Saint Pierre and Miquelon',
194
+ PN: 'Pitcairn',
195
+ PR: 'Puerto Rico',
196
+ PS: 'Palestine, State of',
197
+ PT: 'Portugal',
198
+ PW: 'Palau',
199
+ PY: 'Paraguay',
200
+ QA: 'Qatar',
201
+ RE: 'Réunion',
202
+ RO: 'Romania',
203
+ RS: 'Serbia',
204
+ RU: 'Russian Federation',
205
+ RW: 'Rwanda',
206
+ SA: 'Saudi Arabia',
207
+ SB: 'Solomon Islands',
208
+ SC: 'Seychelles',
209
+ SD: 'Sudan',
210
+ SE: 'Sweden',
211
+ SG: 'Singapore',
212
+ SH: 'Saint Helena, Ascension and Tristan da Cunha',
213
+ SI: 'Slovenia',
214
+ SJ: 'Svalbard and Jan Mayen',
215
+ SK: 'Slovakia',
216
+ SL: 'Sierra Leone',
217
+ SM: 'San Marino',
218
+ SN: 'Senegal',
219
+ SO: 'Somalia',
220
+ SR: 'Suriname',
221
+ SS: 'South Sudan',
222
+ ST: 'Sao Tome and Principe',
223
+ SV: 'El Salvador',
224
+ SX: 'Sint Maarten (Dutch part)',
225
+ SY: 'Syrian Arab Republic',
226
+ SZ: 'Swaziland',
227
+ TC: 'Turks and Caicos Islands',
228
+ TD: 'Chad',
229
+ TF: 'French Southern Territories',
230
+ TG: 'Togo',
231
+ TH: 'Thailand',
232
+ TJ: 'Tajikistan',
233
+ TK: 'Tokelau',
234
+ TL: 'Timor-Leste',
235
+ TM: 'Turkmenistan',
236
+ TN: 'Tunisia',
237
+ TO: 'Tonga',
238
+ TR: 'Turkey',
239
+ TT: 'Trinidad and Tobago',
240
+ TV: 'Tuvalu',
241
+ TW: 'Taiwan, Province of China[a]',
242
+ TZ: 'Tanzania, United Republic of',
243
+ UA: 'Ukraine',
244
+ UG: 'Uganda',
245
+ UM: 'United States Minor Outlying Islands',
246
+ US: 'United States of America',
247
+ UY: 'Uruguay',
248
+ UZ: 'Uzbekistan',
249
+ VA: 'Holy See',
250
+ VC: 'Saint Vincent and the Grenadines',
251
+ VE: 'Venezuela (Bolivarian Republic of)',
252
+ VG: 'Virgin Islands (British)',
253
+ VI: 'Virgin Islands (U.S.)',
254
+ VN: 'Viet Nam',
255
+ VU: 'Vanuatu',
256
+ WF: 'Wallis and Futuna',
257
+ WS: 'Samoa',
258
+ XK: 'Kosovo', // CLDR code, not an officially assigned ISO 3166-1 code.
259
+ YE: 'Yemen',
260
+ YT: 'Mayotte',
261
+ ZA: 'South Africa',
262
+ ZM: 'Zambia',
263
+ ZW: 'Zimbabwe',
264
+ }
265
+
266
+ /** Country code union derived from the reference table, including XK. */
267
+ export type ISOCountry = keyof ISOCountries
268
+
269
+ /** Check whether a value is a country code in ISO_COUNTRIES, including XK. */
270
+ export function isISOCountry(value: unknown): value is ISOCountry {
271
+ return ISO_COUNTRIES.includes(value as ISOCountry)
272
+ }