@eouia/intl-msg 0.1.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.
@@ -0,0 +1,591 @@
1
+ var IntlMsg = (function () {
2
+ 'use strict';
3
+
4
+ var _intl = null;
5
+ if (typeof Intl !== 'undefined') {
6
+ _intl = Intl;
7
+ }
8
+
9
+
10
+ function sanitizeLocaleInput(locale) {
11
+ if (typeof locale !== 'string') return null
12
+ return locale.replace(/_/g, '-')
13
+ }
14
+
15
+ function createLocaleInfo(locale) {
16
+ const sanitizedLocale = sanitizeLocaleInput(locale);
17
+ if (!sanitizedLocale) return null
18
+
19
+ try {
20
+ const canonicalLocales = _intl.getCanonicalLocales(sanitizedLocale);
21
+ const canonicalLocale = Array.isArray(canonicalLocales) ? canonicalLocales[0] : canonicalLocales;
22
+ const localeInfo = {
23
+ original: locale,
24
+ sanitized: sanitizedLocale,
25
+ canonical: canonicalLocale,
26
+ baseName: canonicalLocale,
27
+ };
28
+
29
+ if (typeof _intl.Locale === 'function') {
30
+ const intlLocale = new _intl.Locale(canonicalLocale);
31
+ localeInfo.baseName = intlLocale.baseName || canonicalLocale;
32
+ localeInfo.language = intlLocale.language;
33
+ localeInfo.script = intlLocale.script;
34
+ localeInfo.region = intlLocale.region;
35
+ }
36
+
37
+ return localeInfo
38
+ } catch (e) {
39
+ return null
40
+ }
41
+ }
42
+
43
+ function normalizeToBcp47 (locale, func = ()=>{}) {
44
+ const localeInfo = createLocaleInfo(locale);
45
+ if (!localeInfo) return null
46
+ const canonicalLocales = [localeInfo.canonical];
47
+ if (typeof func === 'function') func(canonicalLocales);
48
+ return localeInfo.canonical
49
+ }
50
+
51
+ function isPlainObject(value) {
52
+ if (Object.prototype.toString.call(value) !== '[object Object]') {
53
+ return false;
54
+ }
55
+
56
+ const prototype = Object.getPrototypeOf(value);
57
+ return prototype === null || prototype === Object.prototype;
58
+ }
59
+
60
+ function toArray (item) {
61
+ if(!item) return []
62
+ return [...((Array.isArray(item)) ? [...item] : [item])]
63
+ }
64
+
65
+ function toShortStr (something, len = 20) {
66
+ var c = (something?.toString() || String(something));
67
+ return (c.length > len) ? c.slice(0, len) + '...' : c
68
+ }
69
+
70
+ function toDate (dateLike) {
71
+ if (dateLike instanceof Date) return new Date(dateLike.getTime())
72
+ var date = new Date(dateLike);
73
+ if (date.toString() === 'Invalid Date') return dateLike
74
+ return date
75
+ }
76
+
77
+ function isRangeObject(value) {
78
+ return isPlainObject(value) && Object.hasOwn(value, 'start') && Object.hasOwn(value, 'end')
79
+ }
80
+
81
+ function getSupportedIntlValues(key) {
82
+ if (typeof _intl.supportedValuesOf !== 'function') return null
83
+ try {
84
+ return _intl.supportedValuesOf(key)
85
+ } catch (e) {
86
+ return null
87
+ }
88
+ }
89
+
90
+ function normalizeIntlOptionValue(key, value) {
91
+ if (typeof value !== 'string') return value
92
+ if (key === 'currency') return value.toUpperCase()
93
+ if (key === 'calendar' || key === 'numberingSystem' || key === 'unit') return value.toLowerCase()
94
+ return value
95
+ }
96
+
97
+ function normalizeRelativeTimeUnit(unit) {
98
+ const normalizedUnit = normalizeIntlOptionValue('unit', unit);
99
+ const supportedUnits = getSupportedIntlValues('unit');
100
+ if (!supportedUnits) return normalizedUnit
101
+ if (supportedUnits.includes(normalizedUnit)) return normalizedUnit
102
+ if (normalizedUnit.endsWith('s')) {
103
+ const singularUnit = normalizedUnit.slice(0, -1);
104
+ if (supportedUnits.includes(singularUnit)) return singularUnit
105
+ }
106
+ return normalizedUnit
107
+ }
108
+
109
+ function validateIntlOptions(options = {}, log = DEFAULT_LOGGER) {
110
+ if (!isPlainObject(options)) return { valid: false, options }
111
+
112
+ const supportedOptionKeys = ['currency', 'unit', 'calendar', 'numberingSystem'];
113
+ for (const key of supportedOptionKeys) {
114
+ if (!Object.hasOwn(options, key)) continue
115
+
116
+ const normalizedValue = normalizeIntlOptionValue(key, options[key]);
117
+ const supportedValues = getSupportedIntlValues(key);
118
+ if (!supportedValues) {
119
+ options[key] = normalizedValue;
120
+ continue
121
+ }
122
+
123
+ if (!supportedValues.includes(normalizedValue)) {
124
+ log.warn(`Invalid Intl option '${key}':`, normalizedValue);
125
+ return { valid: false, options }
126
+ }
127
+
128
+ options[key] = normalizedValue;
129
+ }
130
+
131
+ return { valid: true, options }
132
+ }
133
+
134
+ function setPostFormatContext(config, context) {
135
+ if (!isPlainObject(config)) return
136
+ config.__postFormatContext = context;
137
+ }
138
+
139
+ function applyPostFormat(postFormat, context, fallbackValue, log, formatName, formatters = {}) {
140
+ if (typeof postFormat !== 'string') return fallbackValue
141
+ const transform = formatters?.[postFormat];
142
+ if (typeof transform !== 'function') return fallbackValue
143
+ try {
144
+ return transform(context) ?? fallbackValue
145
+ } catch (e) {
146
+ log.error(`Formatter '${formatName}' postFormat error.`);
147
+ log.error({
148
+ formatterConfig: context,
149
+ postFormat,
150
+ });
151
+ return fallbackValue
152
+ }
153
+ }
154
+
155
+ function toPossibleLocales(locales = []) {
156
+ if (!(Array.isArray(locales) && locales.length > 0)) return []
157
+ return locales.reduce((result, locale) => {
158
+ const localeInfo = createLocaleInfo(locale);
159
+ if (!localeInfo) return result
160
+
161
+ if (!result.includes(localeInfo.canonical)) result.push(localeInfo.canonical);
162
+
163
+ var lcParts = localeInfo.baseName.split('-');
164
+ while(lcParts.length > 0) {
165
+ var search = lcParts.join('-');
166
+ if (!result.includes(search)) result.push(search);
167
+ lcParts.pop();
168
+ }
169
+ return result
170
+ }, [])
171
+ }
172
+
173
+ function applyIntl(obj) {
174
+ const required = [
175
+ 'getCanonicalLocales', 'PluralRules', 'DateTimeFormat', 'RelativeTimeFormat',
176
+ 'ListFormat', 'NumberFormat', 'Locale'
177
+ ];
178
+ if (
179
+ obj !== null && typeof obj === 'object'
180
+ && required.every((p) => {
181
+ return obj.hasOwnProperty(p)
182
+ })
183
+ ) _intl = obj;
184
+ if (!_intl || !_intl.hasOwnProperty(required[0])) throw new Error(
185
+ "This module requires native 'Intl' feature or representative polyfill injection."
186
+ )
187
+ }
188
+
189
+ const DEFAULT_LOGGER = {
190
+ log: () => {},
191
+ info: () => {},
192
+ warn: () => {},
193
+ error: () => {}
194
+ };
195
+
196
+ class Dictionary {
197
+ #terms = new Map()
198
+ #formatters = new Map()
199
+ #name = ''
200
+ constructor (locale) {
201
+ normalizeToBcp47(locale, (lc) => {
202
+ this.#name = (Array.isArray(lc)) ? lc[0] || locale : locale;
203
+ });
204
+ if (!this.#name) throw new Error(`Invalid locale name '${locale}' as dictionary`)
205
+ this.setTerm('TEST', 'This is a test phrase by default.');
206
+ }
207
+ setTerm (key, message) {
208
+ return this.#terms.set(key, message)
209
+ }
210
+ getTerm (key) {
211
+ return this.#terms.get(key)
212
+ }
213
+ getName () {
214
+ return this.#name
215
+ }
216
+ setFormatter (key, formatter) {
217
+ return this.#formatters.set(key, formatter)
218
+ }
219
+ getFormatter (key) {
220
+ return this.#formatters.get(key)
221
+ }
222
+ }
223
+
224
+ class IntlMsg {
225
+ #dictionaries = new Map()
226
+ #locales = []
227
+ #formatters = {}
228
+ #log = DEFAULT_LOGGER
229
+
230
+ constructor ({ log = null, intlPolyfill = null, verbose = false } = {}) {
231
+ applyIntl(intlPolyfill);
232
+ if (!_intl.hasOwnProperty('getCanonicalLocales')) throw new Error("This module required native 'Intl' module or ")
233
+ this.#initFormatters();
234
+ this.setLogger(log, verbose);
235
+ }
236
+
237
+ static factory ({ log = null, intlPolyfill = null, verbose = false, locales = null, dictionaries = null } = {}) {
238
+ const instance = new IntlMsg({ log, intlPolyfill, verbose });
239
+ if (locales) instance.addLocale(locales);
240
+ if (dictionaries) instance.addDictionary(dictionaries);
241
+ return instance
242
+ }
243
+
244
+ setLogger (log = null, verbose = false) {
245
+ if (!log) return
246
+ const required = ['log', 'info', 'warn', 'error'];
247
+ if (required.every((m) => { return log.hasOwnProperty(m)}) && verbose) this.#log = log;
248
+ }
249
+
250
+ getLocale () {
251
+ return [...this.#locales]
252
+ }
253
+
254
+
255
+ setLocale (locales = []) {
256
+ this.#locales = [];
257
+ this.addLocale(locales);
258
+ return this
259
+ }
260
+
261
+
262
+ addLocale (locales = []) {
263
+ var newLocales = toArray(locales);
264
+ if (newLocales.length < 1) return this
265
+ newLocales.forEach((lc) => {
266
+ normalizeToBcp47(lc, (filtered) => {
267
+ if (!(Array.isArray(filtered) && filtered.length >= 1)) return
268
+ filtered.forEach((f) => {
269
+ if (this.#locales.includes(f)) return
270
+ this.#locales.push(f);
271
+ });
272
+ });
273
+ });
274
+ return this
275
+ }
276
+
277
+ addDictionary(json = {}) {
278
+ if (!isPlainObject(json)) {
279
+ this.#log.warn('Invalid dictionary data:', toShortStr(json));
280
+ return this
281
+ }
282
+ Object.keys(json).forEach((locale) => {
283
+ var dict = json[locale];
284
+ this.#mergeDictionary(locale, dict);
285
+ });
286
+ return this
287
+ }
288
+
289
+ #mergeDictionary(locale, dictData = {}) {
290
+ if (!isPlainObject(dictData)) {
291
+ this.#log.warn(`Invalid dictionary entry for locale '${locale}':`, toShortStr(dictData));
292
+ return this
293
+ }
294
+
295
+ var {translations = {}, formatters = {}} = dictData;
296
+ var lc = normalizeToBcp47(locale);
297
+ if (!lc) return this
298
+ var dictionary = this.getDictionary(lc);
299
+ if (!(dictionary instanceof Dictionary)) {
300
+ dictionary = new Dictionary(lc);
301
+ this.#dictionaries.set(lc, dictionary);
302
+ }
303
+ if (translations && typeof translations === 'object') {
304
+ for(let [key, value] of Object.entries(translations)) {
305
+ if (typeof key === 'string')
306
+ dictionary.setTerm(key, value);
307
+ }
308
+ }
309
+ if (formatters && typeof formatters === 'object') {
310
+ for(let [key, value] of Object.entries(formatters)) {
311
+ if (typeof key === 'string')
312
+ dictionary.setFormatter(key, value);
313
+ }
314
+ }
315
+ return this
316
+ }
317
+
318
+ getDictionary(locale) {
319
+ var lc = normalizeToBcp47(locale);
320
+ if (this.#dictionaries.has(lc)) return this.#dictionaries.get(lc)
321
+ return null
322
+ }
323
+
324
+ addTermToDictionary(locale, key, value) {
325
+ var dict = this.getDictionary(locale);
326
+ if (dict instanceof Dictionary) dict.setTerm(key, value);
327
+ return this
328
+ }
329
+
330
+ getTermFromDictionary(locale, key) {
331
+ var dict = this.getDictionary(locale);
332
+ if (!(dict instanceof Dictionary)) return undefined
333
+ return dict.getTerm(key)
334
+ }
335
+
336
+ getDictionaryNames () {
337
+ return [...this.#dictionaries.keys()]
338
+ }
339
+
340
+ #findTerms (key, locales = null) {
341
+ var rootLocales = locales ? toArray(locales) : [...this.#locales];
342
+
343
+ const dictionaryList = [...this.#dictionaries.keys()];
344
+ var found = null;
345
+ var originalLocale = null;
346
+ for (let rootLc of rootLocales) {
347
+ if (found) break
348
+ for (let lc of toPossibleLocales([rootLc])) {
349
+ if (dictionaryList.includes(lc) && this.#dictionaries.get(lc).getTerm(key) !== undefined) {
350
+ found = lc;
351
+ originalLocale = rootLc;
352
+ break
353
+ }
354
+ }
355
+ }
356
+ return {
357
+ message: found ? this.#dictionaries.get(found).getTerm(key) : key,
358
+ dictionaryName: found,
359
+ originalLocale: originalLocale,
360
+ }
361
+ }
362
+
363
+ getRawMessage(key, locales = null) {
364
+ var { message } = this.#findTerms(key, locales);
365
+ return message
366
+ }
367
+
368
+ message (key, options = {}) {
369
+ var { message, dictionaryName, originalLocale } = this.#findTerms(key);
370
+
371
+ for (const prop of Object.keys(options)) {
372
+ var pattern = `{{((?<t>${prop})((?:\\:)(?<f>\\w+))?)}}`;
373
+ var rx = new RegExp(pattern, 'gm');
374
+ [...message.matchAll(rx)].map((i) => {
375
+ var val = options[prop];
376
+ var placeholder = i[0];
377
+ var groups = i?.groups;
378
+ if (groups.f) {
379
+ var formatterDefinition = this.#dictionaries.get(dictionaryName)?.getFormatter(groups.f);
380
+ var formatterConfig = isPlainObject(formatterDefinition) ? { ...formatterDefinition } : formatterDefinition;
381
+ var format = formatterConfig?.format;
382
+ if (typeof this.#formatters[format] === 'function') {
383
+ try {
384
+ var rawValue = val;
385
+ formatterConfig.value = val;
386
+ if (formatterConfig.locales == null) formatterConfig.locales = originalLocale;
387
+ val = this.#formatters[format](formatterConfig) ?? {};
388
+ val = applyPostFormat(
389
+ formatterConfig?.postFormat,
390
+ formatterConfig?.__postFormatContext ?? {
391
+ value: val,
392
+ parts: undefined,
393
+ rawValue,
394
+ locales: formatterConfig.locales,
395
+ options: formatterConfig.options,
396
+ format,
397
+ },
398
+ val,
399
+ this.#log,
400
+ format,
401
+ this.#formatters
402
+ );
403
+ } catch (e) {
404
+ this.#log.error (`Formatter '${format}' call error.`);
405
+ this.#log.error({
406
+ key: key,
407
+ formatterConfig: formatterConfig,
408
+ });
409
+ // val은 원본 값으로 유지 → 아래 message.replace(ph, val)에서 원본 값으로 치환
410
+ }
411
+ }
412
+ }
413
+ message = message.replace(placeholder, val);
414
+ return i?.groups
415
+ });
416
+ }
417
+ return message
418
+ }
419
+
420
+
421
+
422
+ #initFormatters () {
423
+ this.registerFormatter('pluralRules', function({locales, value, options, rules}) {
424
+ if (isNaN(value)) return ''
425
+ var plural = new _intl.PluralRules(locales, options).select(value);
426
+ return rules?.[plural] ?? rules?.other ?? ''
427
+ });
428
+ this.registerFormatter('pluralRange', ({locales, value, options, rules}) => {
429
+ if (!isRangeObject(value)) return rules?.other ?? `${value?.toString() || value}`
430
+ if (isNaN(value.start) || isNaN(value.end)) return rules?.other ?? `${value.start} - ${value.end}`
431
+
432
+ var pluralRules = new _intl.PluralRules(locales, options);
433
+ if (typeof pluralRules.selectRange !== 'function') {
434
+ this.#log.warn("Formatter 'pluralRange' requires Intl.PluralRules.prototype.selectRange support.");
435
+ return rules?.other ?? `${value.start}-${value.end}`
436
+ }
437
+
438
+ var plural = pluralRules.selectRange(value.start, value.end);
439
+ return rules?.[plural] ?? rules?.other ?? ''
440
+ });
441
+ this.registerFormatter('list', (formatterConfig = {}) => {
442
+ var {locales, value, options = {}} = formatterConfig;
443
+ if (!Array.isArray(value)) return value?.toString() || value
444
+ var formatter = new _intl.ListFormat(locales, options);
445
+ var ret = formatter.format(value);
446
+ var parts = typeof formatter.formatToParts === 'function' ? formatter.formatToParts(value) : undefined;
447
+ setPostFormatContext(formatterConfig, { value: ret, parts, rawValue: value, locales, options, format: 'list' });
448
+ return ret
449
+ });
450
+ this.registerFormatter('number', (formatterConfig = {}) => {
451
+ var {locales, value, options = {}} = formatterConfig;
452
+ if (isNaN(value)) return value?.toString() || value
453
+ var { valid, options: validatedOptions } = validateIntlOptions(options, this.#log);
454
+ if (!valid) return value?.toString() || value
455
+ var formatter = new _intl.NumberFormat(locales, validatedOptions);
456
+ var ret = formatter.format(value);
457
+ var parts = typeof formatter.formatToParts === 'function' ? formatter.formatToParts(value) : undefined;
458
+ setPostFormatContext(formatterConfig, { value: ret, parts, rawValue: value, locales, options: validatedOptions, format: 'number' });
459
+ return ret
460
+ });
461
+ this.registerFormatter('numberRange', (formatterConfig = {}) => {
462
+ var {locales, value, options = {}} = formatterConfig;
463
+ if (!isRangeObject(value)) return value?.toString() || value
464
+ if (isNaN(value.start) || isNaN(value.end)) return `${value.start} - ${value.end}`
465
+
466
+ var { valid, options: validatedOptions } = validateIntlOptions(options, this.#log);
467
+ if (!valid) return `${value.start} - ${value.end}`
468
+
469
+ var formatter = new _intl.NumberFormat(locales, validatedOptions);
470
+ if (typeof formatter.formatRange !== 'function') {
471
+ this.#log.warn("Formatter 'numberRange' requires Intl.NumberFormat.prototype.formatRange support.");
472
+ return `${formatter.format(value.start)} - ${formatter.format(value.end)}`
473
+ }
474
+ var ret = formatter.formatRange(value.start, value.end);
475
+ var parts = typeof formatter.formatRangeToParts === 'function' ? formatter.formatRangeToParts(value.start, value.end) : undefined;
476
+ setPostFormatContext(formatterConfig, { value: ret, parts, rawValue: value, locales, options: validatedOptions, format: 'numberRange' });
477
+ return ret
478
+ });
479
+ this.registerFormatter('select', function({locales, value, options}) {
480
+ if (options?.[value]) return options?.[value]
481
+ return options?.["other"] ?? value?.toString() ?? value
482
+ });
483
+ this.registerFormatter('dateTime', (formatterConfig = {}) => {
484
+ var {locales, value, options = {}} = formatterConfig;
485
+ var date = toDate(value);
486
+ if (!(date instanceof Date)) return value
487
+ var { valid, options: validatedOptions } = validateIntlOptions(options, this.#log);
488
+ if (!valid) return value
489
+ var formatter = new _intl.DateTimeFormat(locales, validatedOptions);
490
+ var ret = formatter.format(date);
491
+ var parts = typeof formatter.formatToParts === 'function' ? formatter.formatToParts(date) : undefined;
492
+ setPostFormatContext(formatterConfig, { value: ret, parts, rawValue: value, locales, options: validatedOptions, format: 'dateTime' });
493
+ return ret
494
+ });
495
+ this.registerFormatter('dateTimeRange', (formatterConfig = {}) => {
496
+ var {locales, value, options = {}} = formatterConfig;
497
+ if (!isRangeObject(value)) return value?.toString() || value
498
+
499
+ var start = toDate(value.start);
500
+ var end = toDate(value.end);
501
+ var { valid, options: validatedOptions } = validateIntlOptions(options, this.#log);
502
+
503
+ if (!valid) return `${value.start} - ${value.end}`
504
+
505
+ var formatter = new _intl.DateTimeFormat(locales, validatedOptions);
506
+
507
+ if (!(start instanceof Date) || !(end instanceof Date)) return `${value.start} - ${value.end}`
508
+ if (typeof formatter.formatRange !== 'function') {
509
+ this.#log.warn("Formatter 'dateTimeRange' requires Intl.DateTimeFormat.prototype.formatRange support.");
510
+ return `${formatter.format(start)} - ${formatter.format(end)}`
511
+ }
512
+ var ret = formatter.formatRange(start, end);
513
+ var parts = typeof formatter.formatRangeToParts === 'function' ? formatter.formatRangeToParts(start, end) : undefined;
514
+ setPostFormatContext(formatterConfig, { value: ret, parts, rawValue: value, locales, options: validatedOptions, format: 'dateTimeRange' });
515
+ return ret
516
+ });
517
+ this.registerFormatter('relativeTime', (formatterConfig = {}) => {
518
+ var {locales, value, options = {}, unit='seconds'} = formatterConfig;
519
+ if (isNaN(value)) return value?.toString() || value
520
+ var normalizedUnit = normalizeRelativeTimeUnit(unit);
521
+ var { valid, options: validatedOptions } = validateIntlOptions(options, this.#log);
522
+ var supportedUnits = getSupportedIntlValues('unit');
523
+
524
+ if (!valid) return value?.toString() || value
525
+ if (supportedUnits && !supportedUnits.includes(normalizedUnit)) {
526
+ this.#log.warn("Invalid Intl option 'unit':", normalizedUnit);
527
+ return value?.toString() || value
528
+ }
529
+
530
+ var formatter = new _intl.RelativeTimeFormat(locales, validatedOptions);
531
+ var ret = formatter.format(value, normalizedUnit);
532
+ var parts = typeof formatter.formatToParts === 'function' ? formatter.formatToParts(value, normalizedUnit) : undefined;
533
+ setPostFormatContext(formatterConfig, { value: ret, parts, rawValue: value, locales, options: validatedOptions, format: 'relativeTime', unit: normalizedUnit });
534
+ return ret
535
+ });
536
+ this.registerFormatter('duration', ({locales, value, options = {}}) => {
537
+ if (typeof _intl.DurationFormat !== 'function') {
538
+ this.#log.warn("Formatter 'duration' requires Intl.DurationFormat support.");
539
+ return isPlainObject(value) ? JSON.stringify(value) : value?.toString() || value
540
+ }
541
+ if (!isPlainObject(value)) return value?.toString() || value
542
+ var { valid, options: validatedOptions } = validateIntlOptions(options, this.#log);
543
+ if (!valid) return isPlainObject(value) ? JSON.stringify(value) : value?.toString() || value
544
+ return new _intl.DurationFormat(locales, validatedOptions).format(value)
545
+ });
546
+ this.registerFormatter('humanizedRelativeTime', ({locales, value, options = {}}) => {
547
+ var date = toDate(value);
548
+ if (!(date instanceof Date)) return value
549
+ var unit = 'seconds';
550
+ var now = Date.now();
551
+ var diff = Math.round((date - now) / 1000);
552
+ var aDiff = Math.abs(diff);
553
+ var gap = diff;
554
+ const rules = [
555
+ ['minutes', 60] ,
556
+ ['hours', 60 * 60],
557
+ ['days', 60 * 60 * 24],
558
+ ['weeks', 60 * 60 * 24 * 7],
559
+ ['months', 60 * 60 * 24 * 30],
560
+ ['quarters', 60 * 60 * 24 * 90],
561
+ ['years', 60 * 60 * 24 * 365]
562
+ ];
563
+ for (let [u, f] of rules) {
564
+ if (Math.floor(aDiff / f) < 1) continue
565
+ unit = u;
566
+ gap = Math.floor(diff / f);
567
+ }
568
+ var normalizedUnit = normalizeRelativeTimeUnit(unit);
569
+ var { valid, options: validatedOptions } = validateIntlOptions(options, this.#log);
570
+ var supportedUnits = getSupportedIntlValues('unit');
571
+
572
+ if (!valid) return value
573
+ if (supportedUnits && !supportedUnits.includes(normalizedUnit)) {
574
+ this.#log.warn("Invalid Intl option 'unit':", normalizedUnit);
575
+ return value
576
+ }
577
+
578
+ return new _intl.RelativeTimeFormat(locales, validatedOptions).format(gap, normalizedUnit)
579
+ });
580
+ return this
581
+ }
582
+
583
+ registerFormatter (format, func) {
584
+ if (typeof func === 'function') { this.#formatters[format] = func; }
585
+ return this
586
+ }
587
+ }
588
+
589
+ return IntlMsg;
590
+
591
+ })();
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ function isPlainObject(value) {
6
+ if (Object.prototype.toString.call(value) !== '[object Object]') {
7
+ return false
8
+ }
9
+
10
+ const prototype = Object.getPrototypeOf(value);
11
+ return prototype === null || prototype === Object.prototype
12
+ }
13
+
14
+ function mergeDictionaryEntry(target = {}, source = {}) {
15
+ const mergedTranslations = {
16
+ ...(isPlainObject(target.translations) ? target.translations : {}),
17
+ ...(isPlainObject(source.translations) ? source.translations : {}),
18
+ };
19
+ const mergedFormatters = {
20
+ ...(isPlainObject(target.formatters) ? target.formatters : {}),
21
+ ...(isPlainObject(source.formatters) ? source.formatters : {}),
22
+ };
23
+
24
+ const result = {};
25
+ if (Object.keys(mergedTranslations).length > 0) result.translations = mergedTranslations;
26
+ if (Object.keys(mergedFormatters).length > 0) result.formatters = mergedFormatters;
27
+ return result
28
+ }
29
+
30
+ function mergeDictionaries(target = {}, source = {}) {
31
+ if (!isPlainObject(source)) return target
32
+
33
+ const result = { ...target };
34
+ for (const locale of Object.keys(source)) {
35
+ const existingEntry = isPlainObject(result[locale]) ? result[locale] : {};
36
+ const nextEntry = isPlainObject(source[locale]) ? source[locale] : null;
37
+ if (!nextEntry) continue
38
+ result[locale] = mergeDictionaryEntry(existingEntry, nextEntry);
39
+ }
40
+ return result
41
+ }
42
+
43
+ async function composeDictionaries(plan = [], loader, options = {}) {
44
+ if (!Array.isArray(plan)) throw new TypeError('plan must be an array')
45
+ if (typeof loader !== 'function') throw new TypeError('loader must be a function')
46
+
47
+ const onError = options.onError ?? 'throw';
48
+ let merged = {};
49
+
50
+ for (const entry of plan) {
51
+ try {
52
+ const loaded = await loader(entry);
53
+ if (!loaded) continue
54
+ merged = mergeDictionaries(merged, loaded);
55
+ } catch (error) {
56
+ if (onError === 'skip') continue
57
+ throw error
58
+ }
59
+ }
60
+
61
+ return merged
62
+ }
63
+
64
+ exports.composeDictionaries = composeDictionaries;
65
+ exports.default = composeDictionaries;