@mmstack/translate 21.1.14 → 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.
@@ -55,6 +55,10 @@ function createNamespace(ns, translation) {
55
55
  return namespace;
56
56
  }
57
57
 
58
+ /**
59
+ * Dynamic path parameter signal, that allows resolution even when paramsInheritenceStrategy isnt always
60
+ * This helper depends on an internal
61
+ */
58
62
  function pathParam(key, route = inject(ActivatedRoute)) {
59
63
  const keySignal = typeof key === 'string' ? computed(() => key) : computed(key);
60
64
  const routerOptions = inject(Router)['options'];
@@ -142,7 +146,7 @@ function readLocaleUnsafe() {
142
146
  function injectLocaleInternal() {
143
147
  return STORE_LOCALE;
144
148
  }
145
- function proxyToGlobalSignleton(src) {
149
+ function proxyToGlobalSingleton(src) {
146
150
  const originalSet = src.set;
147
151
  src.set = (next) => {
148
152
  originalSet(next);
@@ -248,7 +252,7 @@ class TranslationStore {
248
252
  messages: this.messages(),
249
253
  }, this.cache), ...(ngDevMode ? [{ debugName: "intl" }] : /* istanbul ignore next */ []));
250
254
  constructor() {
251
- this.locale = proxyToGlobalSignleton(initLocale(signal('en-US')));
255
+ this.locale = proxyToGlobalSingleton(initLocale(signal('en-US')));
252
256
  const paramName = this.config?.localeParamName;
253
257
  if (paramName) {
254
258
  const param = pathParam(paramName);
@@ -629,9 +633,9 @@ function injectFormatDisplayName() {
629
633
  return formatDisplayName(value, type, defaults());
630
634
  const unwrapped = unwrap(localeOrOpt);
631
635
  const opt = typeof unwrapped === 'object'
632
- ? { ...defaults, ...unwrapped }
636
+ ? { ...defaults(), ...unwrapped }
633
637
  : {
634
- ...defaults,
638
+ ...defaults(),
635
639
  locale: unwrapped,
636
640
  };
637
641
  return formatDisplayName(value, type, opt);
@@ -965,6 +969,9 @@ function injectFormatters() {
965
969
  };
966
970
  }
967
971
 
972
+ /**
973
+ * @internal
974
+ */
968
975
  function injectResolveParamLocale(snapshot) {
969
976
  let locale = null;
970
977
  const paramName = injectIntlConfig()?.localeParamName;
@@ -977,7 +984,7 @@ function injectResolveParamLocale(snapshot) {
977
984
  if (!locale && !alwaysInheritParams) {
978
985
  let currentRoute = snapshot;
979
986
  while (currentRoute && !locale) {
980
- locale = currentRoute.paramMap.get('locale');
987
+ locale = currentRoute.paramMap.get(paramName);
981
988
  currentRoute = currentRoute.parent;
982
989
  }
983
990
  }
@@ -1044,44 +1051,85 @@ function createT(store, keyMap = new Map()) {
1044
1051
  };
1045
1052
  return fn;
1046
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
+ }
1047
1081
  function registerNamespace(defaultTranslation, other) {
1048
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
+ ]));
1049
1092
  const injectT = () => {
1050
1093
  const store = inject(TranslationStore);
1051
1094
  return addSignalFn(createT(store, keyMap), store, keyMap);
1052
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.
1053
1101
  let defaultTranslationLoaded = false;
1054
1102
  const resolver = async (snapshot) => {
1055
1103
  const store = inject(TranslationStore);
1056
1104
  const locale = injectResolveParamLocale(snapshot);
1057
1105
  const defaultLocale = injectDefaultLocale();
1058
1106
  const shouldPreloadDefault = injectIntlConfig()?.preloadDefaultLocale ?? false;
1059
- const tPromise = other[locale];
1060
- const promise = tPromise ?? defaultTranslation;
1107
+ const tPromise = unwrappedOther[locale];
1108
+ const promise = tPromise ?? unwrappedDefault;
1061
1109
  if (!promise && isDevMode()) {
1062
1110
  return console.warn(`No translation found for locale: ${locale}`);
1063
1111
  }
1064
- if (promise === defaultTranslation && defaultTranslationLoaded)
1112
+ if (promise === unwrappedDefault && defaultTranslationLoaded)
1065
1113
  return;
1066
1114
  try {
1067
1115
  const promises = [promise()];
1068
1116
  if (shouldPreloadDefault &&
1069
1117
  !defaultTranslationLoaded &&
1070
- promise !== defaultTranslation)
1071
- promises.push(defaultTranslation());
1118
+ promise !== unwrappedDefault)
1119
+ promises.push(unwrappedDefault());
1072
1120
  const translations = await Promise.allSettled(promises);
1073
- const fullfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
1074
- 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)
1075
1123
  throw new Error('Failed to load translations');
1076
- const [t, defaultT] = fullfilled;
1124
+ const [t, defaultT] = fulfilled;
1077
1125
  const ns = t?.namespace ?? defaultT?.namespace;
1078
1126
  if (!ns)
1079
1127
  throw new Error('No namespace found in translation');
1080
1128
  if (isDevMode() && t && t.locale !== locale && t.locale)
1081
1129
  console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
1082
1130
  store.registerOnDemandLoaders(ns, {
1083
- ...other,
1084
- [defaultLocale]: defaultTranslation,
1131
+ ...unwrappedOther,
1132
+ [defaultLocale]: unwrappedDefault,
1085
1133
  });
1086
1134
  const toRegister = {};
1087
1135
  if (t)
@@ -1089,7 +1137,7 @@ function registerNamespace(defaultTranslation, other) {
1089
1137
  if (defaultT)
1090
1138
  toRegister[defaultLocale] = defaultT.flat;
1091
1139
  store.register(ns, toRegister);
1092
- if (promise === defaultTranslation || defaultT)
1140
+ if (promise === unwrappedDefault || defaultT)
1093
1141
  defaultTranslationLoaded = true;
1094
1142
  }
1095
1143
  catch {
@@ -1113,10 +1161,21 @@ function registerNamespace(defaultTranslation, other) {
1113
1161
  */
1114
1162
  function registerRemoteNamespace(ns, defaultTranslation, other) {
1115
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
+ ]));
1116
1173
  const injectT = () => {
1117
1174
  const store = inject(TranslationStore);
1118
1175
  return addSignalFn(createT(store, keyMap), store, keyMap);
1119
1176
  };
1177
+ // See `defaultTranslationLoaded` in `registerNamespace` for rationale —
1178
+ // intentionally racy, safe via idempotent `store.register`.
1120
1179
  let defaultTranslationLoaded = false;
1121
1180
  const resolver = async (snapshot) => {
1122
1181
  const store = inject(TranslationStore);
@@ -1137,10 +1196,10 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
1137
1196
  promise !== defaultTranslation)
1138
1197
  promises.push(defaultTranslation());
1139
1198
  const translations = await Promise.allSettled(promises);
1140
- const fullfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
1141
- 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)
1142
1201
  throw new Error('Failed to load translations');
1143
- const [baseT, baseDefaultT] = fullfilled;
1202
+ const [baseT, baseDefaultT] = fulfilled;
1144
1203
  const t = baseT ? compileTranslation(baseT, ns, locale) : null;
1145
1204
  const defaultT = baseDefaultT
1146
1205
  ? compileTranslation(baseDefaultT, ns, defaultLocale)
@@ -1148,8 +1207,8 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
1148
1207
  if (isDevMode() && t && t.locale !== locale && t.locale)
1149
1208
  console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
1150
1209
  store.registerOnDemandLoaders(ns, {
1151
- ...other,
1152
- [defaultLocale]: defaultTranslation,
1210
+ ...compiledOther,
1211
+ [defaultLocale]: compileLoader(defaultTranslation, defaultLocale),
1153
1212
  });
1154
1213
  const toRegister = {};
1155
1214
  if (t)
@@ -1372,6 +1431,10 @@ function createTransformFn() {
1372
1431
  const store = inject(TranslationStore);
1373
1432
  const t = createT(store);
1374
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.
1375
1438
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1376
1439
  _) => {
1377
1440
  const vars = typeof variablesOrLocale === 'string' ? undefined : variablesOrLocale;
@@ -1422,5 +1485,5 @@ function withParams(message) {
1422
1485
  * Generated bundle index. Do not edit.
1423
1486
  */
1424
1487
 
1425
- 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 };
1426
1489
  //# sourceMappingURL=mmstack-translate.mjs.map