@mmstack/translate 19.3.5 → 19.3.7

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { computed, inject, InjectionToken, LOCALE_ID, signal, isDevMode, effect, resource, untracked, Injectable, isSignal, input, Renderer2, ElementRef, afterRenderEffect, Directive, ChangeDetectorRef } from '@angular/core';
2
+ import { computed, inject, InjectionToken, LOCALE_ID, signal, untracked, isDevMode, effect, resource, Injectable, isSignal, input, Renderer2, ElementRef, afterRenderEffect, Directive, ChangeDetectorRef } from '@angular/core';
3
3
  import { createIntlCache, createIntl } from '@formatjs/intl';
4
4
  import { toSignal } from '@angular/core/rxjs-interop';
5
5
  import { Router, ActivatedRoute } from '@angular/router';
@@ -132,9 +132,27 @@ function injectSupportedLocales() {
132
132
  * the actual locale signal used to store the current locale string
133
133
  */
134
134
  const STORE_LOCALE = signal('en-US');
135
+ /**
136
+ * @internal
137
+ * @deprecated will be removed when ng23 drops
138
+ */
139
+ function readLocaleUnsafe() {
140
+ return STORE_LOCALE();
141
+ }
135
142
  function injectLocaleInternal() {
136
143
  return STORE_LOCALE;
137
144
  }
145
+ function proxyToGlobalSignleton(src) {
146
+ const originalSet = src.set;
147
+ src.set = (next) => {
148
+ originalSet(next);
149
+ STORE_LOCALE.set(next);
150
+ };
151
+ src.update = (updater) => {
152
+ src.set(updater(untracked(src)));
153
+ };
154
+ return src;
155
+ }
138
156
  function isDynamicConfig(cfg) {
139
157
  return !!cfg && 'localeStorage' in cfg && !!cfg.localeStorage;
140
158
  }
@@ -232,7 +250,7 @@ class TranslationStore {
232
250
  messages: this.messages(),
233
251
  }, this.cache));
234
252
  constructor() {
235
- this.locale = initLocale(STORE_LOCALE);
253
+ this.locale = proxyToGlobalSignleton(initLocale(signal('en-US')));
236
254
  const paramName = this.config?.localeParamName;
237
255
  if (paramName) {
238
256
  const param = pathParam(paramName);
@@ -382,6 +400,96 @@ function injectDynamicLocale() {
382
400
  source.isLoading = store.dynamicLocaleLoader.isLoading;
383
401
  return source;
384
402
  }
403
+ /**
404
+ * Power-user escape hatch for adding translations imperatively (e.g. content
405
+ * loaded from a remote API after bootstrap). Returns a function that registers
406
+ * a flat per-locale map of keys under a given namespace
407
+ *
408
+ * Pair with {@link injectUnsafeT} to read the added keys without compile-time
409
+ * constraints.
410
+ *
411
+ * @example
412
+ * ```ts
413
+ * const addTranslations = injectAddTranslations();
414
+ * addTranslations('remote', {
415
+ * 'en-US': { greeting: 'Hi {name}' },
416
+ * 'sl-SI': { greeting: 'Zdravo {name}' },
417
+ * });
418
+ * ```
419
+ */
420
+ function injectAddTranslations() {
421
+ const store = inject(TranslationStore);
422
+ const supportedLocales = injectIntlConfig()?.supportedLocales;
423
+ const supportedLocalesSet = supportedLocales
424
+ ? new Set(supportedLocales)
425
+ : null;
426
+ const validate = supportedLocalesSet
427
+ ? (translations) => {
428
+ const clean = {};
429
+ const invalidLocales = [];
430
+ for (const [locale, translation] of Object.entries(translations)) {
431
+ if (!supportedLocalesSet.has(locale)) {
432
+ invalidLocales.push(locale);
433
+ continue;
434
+ }
435
+ clean[locale] = translation;
436
+ }
437
+ if (isDevMode() && invalidLocales.length > 0)
438
+ console.warn(`[Translate] Attempted to add translations for unsupported locales: ${invalidLocales.join(', ')}. These translations were ignored. Supported locales are: ${(supportedLocales ?? []).join(', ')}.`);
439
+ return clean;
440
+ }
441
+ : (translations) => translations;
442
+ return (ns, translations) => {
443
+ store.register(ns, validate(translations));
444
+ };
445
+ }
446
+
447
+ function equalLocale(a, b) {
448
+ return a.locale === b.locale;
449
+ }
450
+ function createFormatterProvider(formatterName, libraryDefaults, nonLocaleEqual) {
451
+ const token = new InjectionToken(`@mmstack/translate:format-${formatterName}-config`, {
452
+ factory: () => {
453
+ const loc = injectDynamicLocale();
454
+ return computed(() => ({
455
+ ...libraryDefaults,
456
+ locale: loc(),
457
+ }), { equal: equalLocale });
458
+ },
459
+ });
460
+ const provider = (valueOrFn) => {
461
+ const fnProvider = typeof valueOrFn === 'function'
462
+ ? valueOrFn
463
+ : () => valueOrFn;
464
+ return {
465
+ provide: token,
466
+ useFactory: () => {
467
+ const loc = injectDynamicLocale();
468
+ const providedDefaultsOrSignal = fnProvider();
469
+ if (isSignal(providedDefaultsOrSignal))
470
+ return computed(() => ({
471
+ ...libraryDefaults,
472
+ ...providedDefaultsOrSignal(),
473
+ locale: loc(),
474
+ }), {
475
+ equal: (a, b) => equalLocale(a, b) && nonLocaleEqual(a, b),
476
+ });
477
+ const defaults = {
478
+ ...libraryDefaults,
479
+ ...providedDefaultsOrSignal,
480
+ };
481
+ return computed(() => ({
482
+ ...defaults,
483
+ locale: loc(),
484
+ }), {
485
+ equal: equalLocale,
486
+ });
487
+ },
488
+ };
489
+ };
490
+ const injectFn = () => inject(token);
491
+ return [provider, injectFn];
492
+ }
385
493
 
386
494
  function unwrap(value) {
387
495
  return isSignal(value) ? value() : value;
@@ -409,32 +517,68 @@ function validDateOrNull(date) {
409
517
  }
410
518
  const cache$4 = new Map();
411
519
  function getFormatter$4(locale, format, timeZone) {
412
- const cacheKey = `${locale}|${format}|${timeZone ?? ''}`;
520
+ const cacheKey = `${locale}|${typeof format === 'string' ? format : JSON.stringify(format)}|${timeZone ?? ''}`;
413
521
  let formatter = cache$4.get(cacheKey);
414
522
  if (!formatter) {
415
523
  formatter = new Intl.DateTimeFormat(locale, {
416
- ...FORMAT_PRESETS[format],
524
+ ...(typeof format === 'string' ? FORMAT_PRESETS[format] : format),
417
525
  timeZone,
418
526
  });
419
527
  cache$4.set(cacheKey, formatter);
420
528
  }
421
529
  return formatter;
422
530
  }
423
- /**
424
- * Format a date using the current or provided locale & timezone
425
- * By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
426
- *
427
- * @param date - Date to format
428
- * @param opt - Options for formatting
429
- * @returns Formatted date string
430
- */
431
- function formatDate(date, opt) {
531
+ function formatDate(date, optOrLocale) {
432
532
  const validDate = validDateOrNull(unwrap(date));
433
533
  if (validDate === null)
434
534
  return '';
435
- const unwrappedOpt = unwrap(opt);
436
- const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
437
- return getFormatter$4(loc, unwrappedOpt?.format ?? 'medium', unwrappedOpt?.tz).format(validDate);
535
+ const unwrappedArgs = unwrap(optOrLocale);
536
+ let locale;
537
+ let format = 'medium';
538
+ let tz;
539
+ if (typeof unwrappedArgs === 'string') {
540
+ locale = unwrappedArgs;
541
+ }
542
+ else if (unwrappedArgs && typeof unwrappedArgs === 'object') {
543
+ locale = unwrappedArgs.locale ?? readLocaleUnsafe();
544
+ format = unwrappedArgs.format ?? 'medium';
545
+ tz = unwrappedArgs.tz;
546
+ }
547
+ else {
548
+ locale = readLocaleUnsafe();
549
+ }
550
+ return getFormatter$4(locale, format, tz).format(validDate);
551
+ }
552
+ const [provideFormatDateDefaults, injectFormatDateOptions] = createFormatterProvider('date', {
553
+ format: 'medium',
554
+ }, (a, b) => {
555
+ if (a.tz !== b.tz)
556
+ return false;
557
+ if (a.format === b.format)
558
+ return true;
559
+ return JSON.stringify(a.format) === JSON.stringify(b.format);
560
+ });
561
+ /**
562
+ * Inject a context-safe date formatting function tied to the current injector.
563
+ * Uses the libraries locale signal & provided default configuration to react to locale/config changes
564
+ * @example
565
+ * const formatDate = injectFormatDate();
566
+ * readonly displayDate = computed(() => formatDate(this.date()));
567
+ */
568
+ function injectFormatDate() {
569
+ const defaults = injectFormatDateOptions();
570
+ return (date, optOrLocale) => {
571
+ if (!optOrLocale)
572
+ return formatDate(date, defaults());
573
+ const unwrapped = unwrap(optOrLocale);
574
+ const opt = typeof unwrapped === 'object'
575
+ ? { ...defaults(), ...unwrapped }
576
+ : {
577
+ ...defaults(),
578
+ locale: unwrapped,
579
+ };
580
+ return formatDate(date, opt);
581
+ };
438
582
  }
439
583
 
440
584
  const cache$3 = new Map();
@@ -452,21 +596,48 @@ function getFormatter$3(locale, type, style) {
452
596
  }
453
597
  /**
454
598
  * Format a display name using the current or provided locale
455
- * By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
456
599
  *
457
600
  * @param value - The code to format
458
601
  * @param type - The type of display name to format
459
602
  * @param opt - Options for formatting
460
603
  * @returns Formatted display name string
461
604
  */
462
- function formatDisplayName(value, type, opt) {
463
- const unwrapped = unwrap(value);
464
- if (!unwrapped?.trim())
605
+ function formatDisplayName(value, type, localeOrOpt) {
606
+ const unwrappedValue = unwrap(value);
607
+ if (!unwrappedValue?.trim())
465
608
  return '';
466
609
  const unwrappedType = unwrap(type);
467
- const unwrappedOpt = unwrap(opt);
468
- const locale = unwrappedOpt?.locale ?? injectLocaleInternal()();
469
- return (getFormatter$3(locale, unwrappedType, unwrappedOpt?.style ?? 'long').of(unwrapped) ?? '');
610
+ const unwrapped = unwrap(localeOrOpt);
611
+ const locale = typeof unwrapped === 'string'
612
+ ? unwrapped
613
+ : (unwrapped?.locale ?? readLocaleUnsafe());
614
+ const opt = typeof unwrapped === 'object' ? unwrapped : undefined;
615
+ return (getFormatter$3(locale, unwrappedType, opt?.style ?? 'long').of(unwrappedValue) ?? '');
616
+ }
617
+ const [provideFormatDisplayNameDefaults, injectFormatDisplayNameDefaults] = createFormatterProvider('displayName', {
618
+ style: 'long',
619
+ }, (a, b) => a.style === b.style);
620
+ /**
621
+ * Inject a context-safe date formatting function tied to the current injector.
622
+ * Uses the libraries locale signal & provided default configuration to react to locale/config changes
623
+ * @example
624
+ * const formatDisplayName = injectFormatDisplayName();
625
+ * readonly region = computed(() => formatDisplayName('US', 'region'));
626
+ */
627
+ function injectFormatDisplayName() {
628
+ const defaults = injectFormatDisplayNameDefaults();
629
+ return (value, type, localeOrOpt) => {
630
+ if (!localeOrOpt)
631
+ return formatDisplayName(value, type, defaults());
632
+ const unwrapped = unwrap(localeOrOpt);
633
+ const opt = typeof unwrapped === 'object'
634
+ ? { ...defaults, ...unwrapped }
635
+ : {
636
+ ...defaults,
637
+ locale: unwrapped,
638
+ };
639
+ return formatDisplayName(value, type, opt);
640
+ };
470
641
  }
471
642
 
472
643
  const cache$2 = new Map();
@@ -484,21 +655,52 @@ function getFormatter$2(locale, type, style) {
484
655
  }
485
656
  return formatter;
486
657
  }
658
+ function formatList(value, optOrLocale) {
659
+ const unwrappedValue = unwrapList(value);
660
+ if (unwrappedValue.length === 0)
661
+ return '';
662
+ const unwrappedArgs = unwrap(optOrLocale);
663
+ let locale;
664
+ let type = 'conjunction';
665
+ let style = 'long';
666
+ if (typeof unwrappedArgs === 'string') {
667
+ locale = unwrappedArgs;
668
+ }
669
+ else if (unwrappedArgs && typeof unwrappedArgs === 'object') {
670
+ locale = unwrappedArgs.locale ?? readLocaleUnsafe();
671
+ type = unwrappedArgs.type ?? 'conjunction';
672
+ style = unwrappedArgs.style ?? 'long';
673
+ }
674
+ else {
675
+ locale = readLocaleUnsafe();
676
+ }
677
+ return getFormatter$2(locale, type, style).format(unwrappedValue);
678
+ }
679
+ const [provideFormatListDefaults, injectFormatListOptions] = createFormatterProvider('list', {
680
+ type: 'conjunction',
681
+ style: 'long',
682
+ }, (a, b) => a.type === b.type && a.style === b.style);
487
683
  /**
488
- * Format a list using the current or provided locale
489
- * By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
490
- *
491
- * @param value - The list to format
492
- * @param opt - Options for formatting
493
- * @returns Formatted list string
684
+ * Inject a context-safe list formatting function tied to the current injector.
685
+ * Uses the libraries locale signal & provided default configuration to react to locale/config changes
686
+ * @example
687
+ * const formatList = injectFormatList();
688
+ * readonly displayList = computed(() => formatList(this.items()));
494
689
  */
495
- function formatList(value, opt) {
496
- const unwrapped = unwrapList(value);
497
- if (unwrapped.length === 0)
498
- return '';
499
- const unwrappedOpt = unwrap(opt);
500
- const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
501
- return getFormatter$2(loc, unwrappedOpt?.type ?? 'conjunction', unwrappedOpt?.style ?? 'long').format(unwrapped);
690
+ function injectFormatList() {
691
+ const defaults = injectFormatListOptions();
692
+ return (value, optOrLocale) => {
693
+ if (!optOrLocale)
694
+ return formatList(value, defaults());
695
+ const unwrapped = unwrap(optOrLocale);
696
+ const opt = typeof unwrapped === 'object'
697
+ ? { ...defaults(), ...unwrapped }
698
+ : {
699
+ ...defaults(),
700
+ locale: unwrapped,
701
+ };
702
+ return formatList(value, opt);
703
+ };
502
704
  }
503
705
 
504
706
  const cache$1 = new Map();
@@ -525,45 +727,151 @@ function getFormatter$1(locale, minFractionDigits, maxFractionDigits, useGroupin
525
727
  }
526
728
  return formatter;
527
729
  }
528
- /**
529
- * Format a number using the current or provided locale
530
- * By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
531
- *
532
- * @param number - Number to format
533
- * @param opt - Options for formatting
534
- * @returns Formatted number string
535
- */
536
- function formatNumber(value, opt) {
537
- const unwrappedOpt = unwrap(opt);
538
- const unwrappedNumber = unwrapValue(value, unwrappedOpt?.fallbackToZero);
730
+ function formatNumber(value, optOrLocale) {
731
+ const unwrappedArgs = unwrap(optOrLocale);
732
+ const isOpt = unwrappedArgs != null && typeof unwrappedArgs === 'object';
733
+ const fallbackToZero = isOpt ? unwrappedArgs.fallbackToZero : undefined;
734
+ const unwrappedNumber = unwrapValue(value, fallbackToZero);
539
735
  if (unwrappedNumber === null)
540
736
  return '';
541
- const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
542
- return getFormatter$1(loc, unwrappedOpt?.minFractionDigits, unwrappedOpt?.maxFractionDigits, unwrappedOpt?.useGrouping ?? true, unwrappedOpt?.notation ?? 'standard').format(unwrappedNumber);
737
+ let locale;
738
+ let notation;
739
+ let minFractionDigits;
740
+ let maxFractionDigits;
741
+ let useGrouping = true;
742
+ if (typeof unwrappedArgs === 'string') {
743
+ locale = unwrappedArgs;
744
+ }
745
+ else if (isOpt) {
746
+ locale = unwrappedArgs.locale ?? readLocaleUnsafe();
747
+ notation = unwrappedArgs.notation ?? 'standard';
748
+ minFractionDigits = unwrappedArgs.minFractionDigits;
749
+ maxFractionDigits = unwrappedArgs.maxFractionDigits;
750
+ useGrouping = unwrappedArgs.useGrouping ?? true;
751
+ }
752
+ else {
753
+ locale = readLocaleUnsafe();
754
+ notation = 'standard';
755
+ }
756
+ return getFormatter$1(locale, minFractionDigits, maxFractionDigits, useGrouping, notation).format(unwrappedNumber);
543
757
  }
758
+ const [provideFormatNumberDefaults, injectFormatNumberOptions] = createFormatterProvider('number', {
759
+ notation: 'standard',
760
+ useGrouping: true,
761
+ }, (a, b) => a.notation === b.notation &&
762
+ a.minFractionDigits === b.minFractionDigits &&
763
+ a.maxFractionDigits === b.maxFractionDigits &&
764
+ a.useGrouping === b.useGrouping &&
765
+ a.fallbackToZero === b.fallbackToZero);
544
766
  /**
545
- * Format a percentage using the current or provided locale
546
- * By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
547
- *
548
- * @param number - Number to format
549
- * @param opt - Options for formatting
550
- * @returns Formatted percentage string
767
+ * Inject a context-safe number formatting function tied to the current injector.
768
+ * Uses the libraries locale signal & provided default configuration to react to locale/config changes
769
+ * @example
770
+ * const formatNumber = injectFormatNumber();
771
+ * readonly display = computed(() => formatNumber(this.value()));
551
772
  */
552
- function formatPercent(value, opt) {
553
- const unwrappedOpt = unwrap(opt);
554
- const unwrappedNumber = unwrapValue(value, unwrappedOpt?.fallbackToZero);
773
+ function injectFormatNumber() {
774
+ const defaults = injectFormatNumberOptions();
775
+ return (value, optOrLocale) => {
776
+ if (!optOrLocale)
777
+ return formatNumber(value, defaults());
778
+ const unwrapped = unwrap(optOrLocale);
779
+ const opt = typeof unwrapped === 'object'
780
+ ? { ...defaults(), ...unwrapped }
781
+ : {
782
+ ...defaults(),
783
+ locale: unwrapped,
784
+ };
785
+ return formatNumber(value, opt);
786
+ };
787
+ }
788
+ function formatPercent(value, optOrLocale) {
789
+ const unwrappedArgs = unwrap(optOrLocale);
790
+ const isOpt = unwrappedArgs != null && typeof unwrappedArgs === 'object';
791
+ const fallbackToZero = isOpt ? unwrappedArgs.fallbackToZero : undefined;
792
+ const unwrappedNumber = unwrapValue(value, fallbackToZero);
555
793
  if (unwrappedNumber === null)
556
794
  return '';
557
- const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
558
- return getFormatter$1(loc, unwrappedOpt?.minFractionDigits, unwrappedOpt?.maxFractionDigits, undefined, undefined, undefined, undefined, 'percent').format(unwrappedNumber);
795
+ let locale;
796
+ let minFractionDigits;
797
+ let maxFractionDigits;
798
+ if (typeof unwrappedArgs === 'string') {
799
+ locale = unwrappedArgs;
800
+ }
801
+ else if (isOpt) {
802
+ locale = unwrappedArgs.locale ?? readLocaleUnsafe();
803
+ minFractionDigits = unwrappedArgs.minFractionDigits;
804
+ maxFractionDigits = unwrappedArgs.maxFractionDigits;
805
+ }
806
+ else {
807
+ locale = readLocaleUnsafe();
808
+ }
809
+ return getFormatter$1(locale, minFractionDigits, maxFractionDigits, undefined, undefined, undefined, undefined, 'percent').format(unwrappedNumber);
810
+ }
811
+ const [provideFormatPercentDefaults, injectFormatPercentOptions] = createFormatterProvider('percent', {}, (a, b) => a.minFractionDigits === b.minFractionDigits &&
812
+ a.maxFractionDigits === b.maxFractionDigits &&
813
+ a.fallbackToZero === b.fallbackToZero);
814
+ /**
815
+ * Inject a context-safe percent formatting function tied to the current injector.
816
+ * Uses the libraries locale signal & provided default configuration to react to locale/config changes
817
+ */
818
+ function injectFormatPercent() {
819
+ const defaults = injectFormatPercentOptions();
820
+ return (value, optOrLocale) => {
821
+ if (!optOrLocale)
822
+ return formatPercent(value, defaults());
823
+ const unwrapped = unwrap(optOrLocale);
824
+ const opt = typeof unwrapped === 'object'
825
+ ? { ...defaults(), ...unwrapped }
826
+ : {
827
+ ...defaults(),
828
+ locale: unwrapped,
829
+ };
830
+ return formatPercent(value, opt);
831
+ };
559
832
  }
560
- function formatCurrency(value, currency, opt) {
561
- const unwrappedOpt = unwrap(opt);
562
- const unwrappedValue = unwrapValue(value, unwrappedOpt?.fallbackToZero);
833
+ function formatCurrency(value, currency, optOrLocale) {
834
+ const unwrappedArgs = unwrap(optOrLocale);
835
+ const isOpt = unwrappedArgs != null && typeof unwrappedArgs === 'object';
836
+ const fallbackToZero = isOpt ? unwrappedArgs.fallbackToZero : undefined;
837
+ const unwrappedValue = unwrapValue(value, fallbackToZero);
563
838
  if (unwrappedValue === null)
564
839
  return '';
565
- const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
566
- return getFormatter$1(loc, undefined, undefined, undefined, undefined, unwrap(currency), unwrappedOpt?.display ?? 'symbol', 'currency').format(unwrappedValue);
840
+ let locale;
841
+ let display = 'symbol';
842
+ if (typeof unwrappedArgs === 'string') {
843
+ locale = unwrappedArgs;
844
+ }
845
+ else if (isOpt) {
846
+ locale = unwrappedArgs.locale ?? readLocaleUnsafe();
847
+ display = unwrappedArgs.display ?? 'symbol';
848
+ }
849
+ else {
850
+ locale = readLocaleUnsafe();
851
+ }
852
+ return getFormatter$1(locale, undefined, undefined, undefined, undefined, unwrap(currency), display, 'currency').format(unwrappedValue);
853
+ }
854
+ const [provideFormatCurrencyDefaults, injectFormatCurrencyOptions] = createFormatterProvider('currency', {
855
+ display: 'symbol',
856
+ }, (a, b) => a.display === b.display && a.fallbackToZero === b.fallbackToZero);
857
+ /**
858
+ * Inject a context-safe currency formatting function tied to the current injector.
859
+ * Uses the libraries locale signal & provided default configuration to react to locale/config changes
860
+ */
861
+ function injectFormatCurrency() {
862
+ const defaults = injectFormatCurrencyOptions();
863
+ return (value, currency, optOrLocale) => {
864
+ if (!optOrLocale)
865
+ return formatCurrency(value, currency, defaults());
866
+ const unwrapped = unwrap(optOrLocale);
867
+ const opt = typeof unwrapped === 'object'
868
+ ? { ...defaults(), ...unwrapped }
869
+ : {
870
+ ...defaults(),
871
+ locale: unwrapped,
872
+ };
873
+ return formatCurrency(value, currency, opt);
874
+ };
567
875
  }
568
876
 
569
877
  const cache = new Map();
@@ -576,16 +884,7 @@ function getFormatter(locale, style, numeric) {
576
884
  }
577
885
  return formatter;
578
886
  }
579
- /**
580
- * Format a relative time using the current or provided locale
581
- * By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
582
- *
583
- * @param value - The numeric value to use in the relative time internationalization message
584
- * @param unit - The unit to use in the relative time internationalization message
585
- * @param opt - Options for formatting
586
- * @returns Formatted relative time string
587
- */
588
- function formatRelativeTime(value, unit, opt) {
887
+ function formatRelativeTime(value, unit, optOrLocale) {
589
888
  const unwrappedValue = unwrap(value);
590
889
  if (unwrappedValue === null ||
591
890
  unwrappedValue === undefined ||
@@ -594,9 +893,78 @@ function formatRelativeTime(value, unit, opt) {
594
893
  const unwrappedUnit = unwrap(unit);
595
894
  if (!unwrappedUnit)
596
895
  return '';
597
- const unwrappedOpt = unwrap(opt);
598
- const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
599
- return getFormatter(loc, unwrappedOpt?.style ?? 'long', unwrappedOpt?.numeric ?? 'always').format(unwrappedValue, unwrappedUnit);
896
+ const unwrappedArgs = unwrap(optOrLocale);
897
+ let locale;
898
+ let style = 'long';
899
+ let numeric = 'always';
900
+ if (typeof unwrappedArgs === 'string') {
901
+ locale = unwrappedArgs;
902
+ }
903
+ else if (unwrappedArgs && typeof unwrappedArgs === 'object') {
904
+ locale = unwrappedArgs.locale ?? readLocaleUnsafe();
905
+ style = unwrappedArgs.style ?? 'long';
906
+ numeric = unwrappedArgs.numeric ?? 'always';
907
+ }
908
+ else {
909
+ locale = readLocaleUnsafe();
910
+ }
911
+ return getFormatter(locale, style, numeric).format(unwrappedValue, unwrappedUnit);
912
+ }
913
+ const [provideFormatRelativeTimeDefaults, injectFormatRelativeTimeOptions] = createFormatterProvider('relativeTime', {
914
+ style: 'long',
915
+ numeric: 'always',
916
+ }, (a, b) => a.style === b.style && a.numeric === b.numeric);
917
+ /**
918
+ * Inject a context-safe relative time formatting function tied to the current injector.
919
+ * Uses the libraries locale signal & provided default configuration to react to locale/config changes
920
+ * @example
921
+ * const formatRelativeTime = injectFormatRelativeTime();
922
+ * readonly relativeAge = computed(() => formatRelativeTime(this.delta(), 'day'));
923
+ */
924
+ function injectFormatRelativeTime() {
925
+ const defaults = injectFormatRelativeTimeOptions();
926
+ return (value, unit, optOrLocale) => {
927
+ if (!optOrLocale)
928
+ return formatRelativeTime(value, unit, defaults());
929
+ const unwrapped = unwrap(optOrLocale);
930
+ const opt = typeof unwrapped === 'object'
931
+ ? { ...defaults(), ...unwrapped }
932
+ : {
933
+ ...defaults(),
934
+ locale: unwrapped,
935
+ };
936
+ return formatRelativeTime(value, unit, opt);
937
+ };
938
+ }
939
+
940
+ function provideFormatDefaults(cfg) {
941
+ const providers = [];
942
+ if (cfg.date)
943
+ providers.push(provideFormatDateDefaults(cfg.date));
944
+ if (cfg.displayName)
945
+ providers.push(provideFormatDisplayNameDefaults(cfg.displayName));
946
+ if (cfg.list)
947
+ providers.push(provideFormatListDefaults(cfg.list));
948
+ if (cfg.relativeTime)
949
+ providers.push(provideFormatRelativeTimeDefaults(cfg.relativeTime));
950
+ if (cfg.number)
951
+ providers.push(provideFormatNumberDefaults(cfg.number));
952
+ if (cfg.percent)
953
+ providers.push(provideFormatPercentDefaults(cfg.percent));
954
+ if (cfg.currency)
955
+ providers.push(provideFormatCurrencyDefaults(cfg.currency));
956
+ return providers;
957
+ }
958
+ function injectFormatters() {
959
+ return {
960
+ date: injectFormatDate(),
961
+ displayName: injectFormatDisplayName(),
962
+ list: injectFormatList(),
963
+ relativeTime: injectFormatRelativeTime(),
964
+ number: injectFormatNumber(),
965
+ percent: injectFormatPercent(),
966
+ currency: injectFormatCurrency(),
967
+ };
600
968
  }
601
969
 
602
970
  function injectResolveParamLocale(snapshot) {
@@ -809,6 +1177,58 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
809
1177
  resolveNamespaceTranslation: resolver,
810
1178
  };
811
1179
  }
1180
+ class UnsafeTKeyMap {
1181
+ map = new Map();
1182
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: UnsafeTKeyMap, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1183
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: UnsafeTKeyMap, providedIn: 'root' });
1184
+ }
1185
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: UnsafeTKeyMap, decorators: [{
1186
+ type: Injectable,
1187
+ args: [{
1188
+ providedIn: 'root',
1189
+ }]
1190
+ }] });
1191
+ /**
1192
+ * Power-user escape hatch that returns a fully untyped translation function.
1193
+ * Intended for use alongside {@link injectAddTranslations} when translations
1194
+ * are added imperatively (e.g. from a remote API), or for cross-namespace
1195
+ * lookups where the typed API would be impractical.
1196
+ *
1197
+ * @example
1198
+ * ```ts
1199
+ * const t = injectUnsafeT();
1200
+ * t('any.namespace.key', { name: 'Alice', count: 3 });
1201
+ * const sig = t.asSignal('any.namespace.key', () => ({ name: name() }));
1202
+ * ```
1203
+ */
1204
+ function injectUnsafeT() {
1205
+ const store = inject(TranslationStore);
1206
+ const map = inject(UnsafeTKeyMap).map;
1207
+ const fn = (key, params) => {
1208
+ let k = map.get(key);
1209
+ if (k === undefined) {
1210
+ k = replaceWithDelim(key);
1211
+ map.set(key, k);
1212
+ }
1213
+ return store.formatMessage(k, params);
1214
+ };
1215
+ fn.asSignal = (key, params) => {
1216
+ let k = map.get(key);
1217
+ if (k === undefined) {
1218
+ k = replaceWithDelim(key);
1219
+ map.set(key, k);
1220
+ }
1221
+ if (!params)
1222
+ return store.buildSimpleKeySignal(k);
1223
+ const paramsSignal = isSignal(params)
1224
+ ? params
1225
+ : computed(() => params(), {
1226
+ equal: createEqualsRecord(Object.keys(params())),
1227
+ });
1228
+ return computed(() => store.formatMessage(k, paramsSignal()));
1229
+ };
1230
+ return fn;
1231
+ }
812
1232
 
813
1233
  /**
814
1234
  * Guard that validates the locale parameter against supported locales.
@@ -975,9 +1395,36 @@ class Translator {
975
1395
  transform = createTransformFn();
976
1396
  }
977
1397
 
1398
+ /**
1399
+ * Power-user escape hatch for ICU messages whose parameters can't be inferred
1400
+ * from the message string — typically variables nested inside `plural` /
1401
+ * `select` / `selectordinal` arms, which the type-level extractor skips.
1402
+ *
1403
+ * Declared params are merged with auto-extracted ones; on key conflict, the
1404
+ * declared params win. Non-default locales for a key wrapped with `withParams`
1405
+ * may be plain strings — they don't need to repeat the wrapper.
1406
+ *
1407
+ * @example
1408
+ * ```ts
1409
+ * const ns = createNamespace('quote', {
1410
+ * // auto-extracts `count`; `name` is declared explicitly because it
1411
+ * // lives inside the plural arms and can't be inferred
1412
+ * stats: withParams<{ name: string }>(
1413
+ * '{count, plural, one {1 quote from {name}} other {# quotes from {name}}}',
1414
+ * ),
1415
+ * });
1416
+ *
1417
+ * // t inferred as: (key, { count: number; name: string }) => string
1418
+ * t('quote.stats', { count: 3, name: 'Alice' });
1419
+ * ```
1420
+ */
1421
+ function withParams(message) {
1422
+ return message;
1423
+ }
1424
+
978
1425
  /**
979
1426
  * Generated bundle index. Do not edit.
980
1427
  */
981
1428
 
982
- export { Translate, Translator, canMatchLocale, compileTranslation, createNamespace, formatCurrency, formatDate, formatDisplayName, formatList, formatNumber, formatPercent, formatRelativeTime, injectDynamicLocale, injectIntl, injectResolveParamLocale, injectSupportedLocales, provideIntlConfig, provideMockTranslations, registerNamespace, registerRemoteNamespace };
1429
+ export { Translate, Translator, canMatchLocale, compileTranslation, createNamespace, formatCurrency, formatDate, formatDisplayName, formatList, formatNumber, formatPercent, formatRelativeTime, injectAddTranslations, injectDynamicLocale, injectFormatCurrency, injectFormatDate, injectFormatDisplayName, injectFormatList, injectFormatNumber, injectFormatPercent, injectFormatRelativeTime, injectFormatters, injectIntl, injectResolveParamLocale, injectSupportedLocales, injectUnsafeT, provideFormatCurrencyDefaults, provideFormatDateDefaults, provideFormatDefaults, provideFormatDisplayNameDefaults, provideFormatListDefaults, provideFormatNumberDefaults, provideFormatPercentDefaults, provideFormatRelativeTimeDefaults, provideIntlConfig, provideMockTranslations, registerNamespace, registerRemoteNamespace, withParams };
983
1430
  //# sourceMappingURL=mmstack-translate.mjs.map