@mmstack/translate 21.1.13 → 21.1.15

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.
@@ -3,7 +3,6 @@ import { computed, inject, InjectionToken, LOCALE_ID, signal, untracked, isDevMo
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';
6
- import { inject as inject$1 } from '@angular/core/primitives/di';
7
6
 
8
7
  const KEY_DELIM = '::MMT_DELIM::';
9
8
  function prependDelim(prefix, key) {
@@ -56,6 +55,10 @@ function createNamespace(ns, translation) {
56
55
  return namespace;
57
56
  }
58
57
 
58
+ /**
59
+ * Dynamic path parameter signal, that allows resolution even when paramsInheritenceStrategy isnt always
60
+ * This helper depends on an internal
61
+ */
59
62
  function pathParam(key, route = inject(ActivatedRoute)) {
60
63
  const keySignal = typeof key === 'string' ? computed(() => key) : computed(key);
61
64
  const routerOptions = inject(Router)['options'];
@@ -143,7 +146,7 @@ function readLocaleUnsafe() {
143
146
  function injectLocaleInternal() {
144
147
  return STORE_LOCALE;
145
148
  }
146
- function proxyToGlobalSignleton(src) {
149
+ function proxyToGlobalSingleton(src) {
147
150
  const originalSet = src.set;
148
151
  src.set = (next) => {
149
152
  originalSet(next);
@@ -249,7 +252,7 @@ class TranslationStore {
249
252
  messages: this.messages(),
250
253
  }, this.cache), ...(ngDevMode ? [{ debugName: "intl" }] : /* istanbul ignore next */ []));
251
254
  constructor() {
252
- this.locale = proxyToGlobalSignleton(initLocale(signal('en-US')));
255
+ this.locale = proxyToGlobalSingleton(initLocale(signal('en-US')));
253
256
  const paramName = this.config?.localeParamName;
254
257
  if (paramName) {
255
258
  const param = pathParam(paramName);
@@ -486,7 +489,7 @@ function createFormatterProvider(formatterName, libraryDefaults, nonLocaleEqual)
486
489
  },
487
490
  };
488
491
  };
489
- const injectFn = () => inject$1(token);
492
+ const injectFn = () => inject(token);
490
493
  return [provider, injectFn];
491
494
  }
492
495
 
@@ -630,9 +633,9 @@ function injectFormatDisplayName() {
630
633
  return formatDisplayName(value, type, defaults());
631
634
  const unwrapped = unwrap(localeOrOpt);
632
635
  const opt = typeof unwrapped === 'object'
633
- ? { ...defaults, ...unwrapped }
636
+ ? { ...defaults(), ...unwrapped }
634
637
  : {
635
- ...defaults,
638
+ ...defaults(),
636
639
  locale: unwrapped,
637
640
  };
638
641
  return formatDisplayName(value, type, opt);
@@ -966,6 +969,9 @@ function injectFormatters() {
966
969
  };
967
970
  }
968
971
 
972
+ /**
973
+ * @internal
974
+ */
969
975
  function injectResolveParamLocale(snapshot) {
970
976
  let locale = null;
971
977
  const paramName = injectIntlConfig()?.localeParamName;
@@ -978,7 +984,7 @@ function injectResolveParamLocale(snapshot) {
978
984
  if (!locale && !alwaysInheritParams) {
979
985
  let currentRoute = snapshot;
980
986
  while (currentRoute && !locale) {
981
- locale = currentRoute.paramMap.get('locale');
987
+ locale = currentRoute.paramMap.get(paramName);
982
988
  currentRoute = currentRoute.parent;
983
989
  }
984
990
  }
@@ -1045,44 +1051,85 @@ function createT(store, keyMap = new Map()) {
1045
1051
  };
1046
1052
  return fn;
1047
1053
  }
1054
+ function isCompiledTranslation(value) {
1055
+ return (!!value &&
1056
+ typeof value === 'object' &&
1057
+ 'flat' in value &&
1058
+ 'namespace' in value);
1059
+ }
1060
+ /**
1061
+ * @internal exported for unit testing
1062
+ *
1063
+ * Unwraps a loader result to a `CompiledTranslation`. Detection order:
1064
+ * 1. value is already a `CompiledTranslation` (has `flat` + `namespace`)
1065
+ * 2. value has a `default` export holding a `CompiledTranslation` (ESM default)
1066
+ * 3. value has a `translation` export holding a `CompiledTranslation` (named export)
1067
+ */
1068
+ function resolveTranslationModule(loaded) {
1069
+ if (isCompiledTranslation(loaded))
1070
+ return loaded;
1071
+ if (loaded && typeof loaded === 'object') {
1072
+ const def = loaded.default;
1073
+ if (isCompiledTranslation(def))
1074
+ return def;
1075
+ const tr = loaded.translation;
1076
+ if (isCompiledTranslation(tr))
1077
+ return tr;
1078
+ }
1079
+ throw new Error('[@mmstack/translate] Loader did not return a CompiledTranslation. Expected the value from `createNamespace` / `createTranslation`, or a module exporting one as `default` or `translation`.');
1080
+ }
1048
1081
  function registerNamespace(defaultTranslation, other) {
1049
1082
  const keyMap = new Map();
1083
+ // Pre-wrap loaders once so the rest of the pipeline — including the
1084
+ // dynamic-locale loader in `TranslationStore`, which reads `.namespace`
1085
+ // and `.flat` directly off the result — always sees a `CompiledTranslation`
1086
+ // regardless of which loader shape the caller used.
1087
+ const unwrappedDefault = () => defaultTranslation().then(resolveTranslationModule);
1088
+ const unwrappedOther = Object.fromEntries(Object.entries(other).map(([loc, loader]) => [
1089
+ loc,
1090
+ () => loader().then(resolveTranslationModule),
1091
+ ]));
1050
1092
  const injectT = () => {
1051
1093
  const store = inject(TranslationStore);
1052
1094
  return addSignalFn(createT(store, keyMap), store, keyMap);
1053
1095
  };
1096
+ // Tracks whether the default locale's translation has been loaded by any prior
1097
+ // resolver call. Captured in closure across navigations on purpose — concurrent
1098
+ // resolves may each see `false` and both queue a default preload; that's harmless
1099
+ // because `store.register` is idempotent (same payload overwrites with the same
1100
+ // value). Do not add locking here.
1054
1101
  let defaultTranslationLoaded = false;
1055
1102
  const resolver = async (snapshot) => {
1056
1103
  const store = inject(TranslationStore);
1057
1104
  const locale = injectResolveParamLocale(snapshot);
1058
1105
  const defaultLocale = injectDefaultLocale();
1059
1106
  const shouldPreloadDefault = injectIntlConfig()?.preloadDefaultLocale ?? false;
1060
- const tPromise = other[locale];
1061
- const promise = tPromise ?? defaultTranslation;
1107
+ const tPromise = unwrappedOther[locale];
1108
+ const promise = tPromise ?? unwrappedDefault;
1062
1109
  if (!promise && isDevMode()) {
1063
1110
  return console.warn(`No translation found for locale: ${locale}`);
1064
1111
  }
1065
- if (promise === defaultTranslation && defaultTranslationLoaded)
1112
+ if (promise === unwrappedDefault && defaultTranslationLoaded)
1066
1113
  return;
1067
1114
  try {
1068
1115
  const promises = [promise()];
1069
1116
  if (shouldPreloadDefault &&
1070
1117
  !defaultTranslationLoaded &&
1071
- promise !== defaultTranslation)
1072
- promises.push(defaultTranslation());
1118
+ promise !== unwrappedDefault)
1119
+ promises.push(unwrappedDefault());
1073
1120
  const translations = await Promise.allSettled(promises);
1074
- const fullfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
1075
- if (fullfilled.at(0) === null && fullfilled.at(1) === null)
1121
+ const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
1122
+ if (fulfilled.at(0) === null && fulfilled.at(1) === null)
1076
1123
  throw new Error('Failed to load translations');
1077
- const [t, defaultT] = fullfilled;
1124
+ const [t, defaultT] = fulfilled;
1078
1125
  const ns = t?.namespace ?? defaultT?.namespace;
1079
1126
  if (!ns)
1080
1127
  throw new Error('No namespace found in translation');
1081
1128
  if (isDevMode() && t && t.locale !== locale && t.locale)
1082
1129
  console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
1083
1130
  store.registerOnDemandLoaders(ns, {
1084
- ...other,
1085
- [defaultLocale]: defaultTranslation,
1131
+ ...unwrappedOther,
1132
+ [defaultLocale]: unwrappedDefault,
1086
1133
  });
1087
1134
  const toRegister = {};
1088
1135
  if (t)
@@ -1090,7 +1137,7 @@ function registerNamespace(defaultTranslation, other) {
1090
1137
  if (defaultT)
1091
1138
  toRegister[defaultLocale] = defaultT.flat;
1092
1139
  store.register(ns, toRegister);
1093
- if (promise === defaultTranslation || defaultT)
1140
+ if (promise === unwrappedDefault || defaultT)
1094
1141
  defaultTranslationLoaded = true;
1095
1142
  }
1096
1143
  catch {
@@ -1114,10 +1161,21 @@ function registerNamespace(defaultTranslation, other) {
1114
1161
  */
1115
1162
  function registerRemoteNamespace(ns, defaultTranslation, other) {
1116
1163
  const keyMap = new Map();
1164
+ // The dynamic-locale loader in TranslationStore reads `.namespace` and `.flat`
1165
+ // off the loader result, so on-demand loaders must return CompiledTranslation.
1166
+ // Wrap the raw remote fetchers once here at registration time rather than every
1167
+ // resolver call.
1168
+ const compileLoader = (loader, locale) => () => loader().then((raw) => compileTranslation(raw, ns, locale));
1169
+ const compiledOther = Object.fromEntries(Object.entries(other).map(([loc, loader]) => [
1170
+ loc,
1171
+ compileLoader(loader, loc),
1172
+ ]));
1117
1173
  const injectT = () => {
1118
1174
  const store = inject(TranslationStore);
1119
1175
  return addSignalFn(createT(store, keyMap), store, keyMap);
1120
1176
  };
1177
+ // See `defaultTranslationLoaded` in `registerNamespace` for rationale —
1178
+ // intentionally racy, safe via idempotent `store.register`.
1121
1179
  let defaultTranslationLoaded = false;
1122
1180
  const resolver = async (snapshot) => {
1123
1181
  const store = inject(TranslationStore);
@@ -1138,10 +1196,10 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
1138
1196
  promise !== defaultTranslation)
1139
1197
  promises.push(defaultTranslation());
1140
1198
  const translations = await Promise.allSettled(promises);
1141
- const fullfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
1142
- if (fullfilled.at(0) === null && fullfilled.at(1) === null)
1199
+ const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
1200
+ if (fulfilled.at(0) === null && fulfilled.at(1) === null)
1143
1201
  throw new Error('Failed to load translations');
1144
- const [baseT, baseDefaultT] = fullfilled;
1202
+ const [baseT, baseDefaultT] = fulfilled;
1145
1203
  const t = baseT ? compileTranslation(baseT, ns, locale) : null;
1146
1204
  const defaultT = baseDefaultT
1147
1205
  ? compileTranslation(baseDefaultT, ns, defaultLocale)
@@ -1149,8 +1207,8 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
1149
1207
  if (isDevMode() && t && t.locale !== locale && t.locale)
1150
1208
  console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
1151
1209
  store.registerOnDemandLoaders(ns, {
1152
- ...other,
1153
- [defaultLocale]: defaultTranslation,
1210
+ ...compiledOther,
1211
+ [defaultLocale]: compileLoader(defaultTranslation, defaultLocale),
1154
1212
  });
1155
1213
  const toRegister = {};
1156
1214
  if (t)
@@ -1373,6 +1431,10 @@ function createTransformFn() {
1373
1431
  const store = inject(TranslationStore);
1374
1432
  const t = createT(store);
1375
1433
  const fn = (key, variablesOrLocale,
1434
+ // The locale argument is unused at runtime — the active locale comes from
1435
+ // `TranslationStore`. It exists on the signature so consumers can pass
1436
+ // `locale()` as a pipe argument (`'key' | translate : vars : locale()`)
1437
+ // to bust Angular's pure-pipe memoization on locale changes.
1376
1438
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1377
1439
  _) => {
1378
1440
  const vars = typeof variablesOrLocale === 'string' ? undefined : variablesOrLocale;
@@ -1423,5 +1485,5 @@ function withParams(message) {
1423
1485
  * Generated bundle index. Do not edit.
1424
1486
  */
1425
1487
 
1426
- 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 };
1488
+ export { Translate, Translator, canMatchLocale, compileTranslation, createNamespace, formatCurrency, formatDate, formatDisplayName, formatList, formatNumber, formatPercent, formatRelativeTime, injectAddTranslations, injectDefaultLocale, 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 };
1427
1489
  //# sourceMappingURL=mmstack-translate.mjs.map