@mmstack/translate 20.5.12 → 20.5.13

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);
@@ -250,7 +254,7 @@ class TranslationStore {
250
254
  messages: this.messages(),
251
255
  }, this.cache), ...(ngDevMode ? [{ debugName: "intl" }] : []));
252
256
  constructor() {
253
- this.locale = proxyToGlobalSignleton(initLocale(signal('en-US')));
257
+ this.locale = proxyToGlobalSingleton(initLocale(signal('en-US')));
254
258
  const paramName = this.config?.localeParamName;
255
259
  if (paramName) {
256
260
  const param = pathParam(paramName);
@@ -631,9 +635,9 @@ function injectFormatDisplayName() {
631
635
  return formatDisplayName(value, type, defaults());
632
636
  const unwrapped = unwrap(localeOrOpt);
633
637
  const opt = typeof unwrapped === 'object'
634
- ? { ...defaults, ...unwrapped }
638
+ ? { ...defaults(), ...unwrapped }
635
639
  : {
636
- ...defaults,
640
+ ...defaults(),
637
641
  locale: unwrapped,
638
642
  };
639
643
  return formatDisplayName(value, type, opt);
@@ -967,6 +971,9 @@ function injectFormatters() {
967
971
  };
968
972
  }
969
973
 
974
+ /**
975
+ * @internal
976
+ */
970
977
  function injectResolveParamLocale(snapshot) {
971
978
  let locale = null;
972
979
  const paramName = injectIntlConfig()?.localeParamName;
@@ -979,7 +986,7 @@ function injectResolveParamLocale(snapshot) {
979
986
  if (!locale && !alwaysInheritParams) {
980
987
  let currentRoute = snapshot;
981
988
  while (currentRoute && !locale) {
982
- locale = currentRoute.paramMap.get('locale');
989
+ locale = currentRoute.paramMap.get(paramName);
983
990
  currentRoute = currentRoute.parent;
984
991
  }
985
992
  }
@@ -1046,44 +1053,85 @@ function createT(store, keyMap = new Map()) {
1046
1053
  };
1047
1054
  return fn;
1048
1055
  }
1056
+ function isCompiledTranslation(value) {
1057
+ return (!!value &&
1058
+ typeof value === 'object' &&
1059
+ 'flat' in value &&
1060
+ 'namespace' in value);
1061
+ }
1062
+ /**
1063
+ * @internal exported for unit testing
1064
+ *
1065
+ * Unwraps a loader result to a `CompiledTranslation`. Detection order:
1066
+ * 1. value is already a `CompiledTranslation` (has `flat` + `namespace`)
1067
+ * 2. value has a `default` export holding a `CompiledTranslation` (ESM default)
1068
+ * 3. value has a `translation` export holding a `CompiledTranslation` (named export)
1069
+ */
1070
+ function resolveTranslationModule(loaded) {
1071
+ if (isCompiledTranslation(loaded))
1072
+ return loaded;
1073
+ if (loaded && typeof loaded === 'object') {
1074
+ const def = loaded.default;
1075
+ if (isCompiledTranslation(def))
1076
+ return def;
1077
+ const tr = loaded.translation;
1078
+ if (isCompiledTranslation(tr))
1079
+ return tr;
1080
+ }
1081
+ 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`.');
1082
+ }
1049
1083
  function registerNamespace(defaultTranslation, other) {
1050
1084
  const keyMap = new Map();
1085
+ // Pre-wrap loaders once so the rest of the pipeline — including the
1086
+ // dynamic-locale loader in `TranslationStore`, which reads `.namespace`
1087
+ // and `.flat` directly off the result — always sees a `CompiledTranslation`
1088
+ // regardless of which loader shape the caller used.
1089
+ const unwrappedDefault = () => defaultTranslation().then(resolveTranslationModule);
1090
+ const unwrappedOther = Object.fromEntries(Object.entries(other).map(([loc, loader]) => [
1091
+ loc,
1092
+ () => loader().then(resolveTranslationModule),
1093
+ ]));
1051
1094
  const injectT = () => {
1052
1095
  const store = inject(TranslationStore);
1053
1096
  return addSignalFn(createT(store, keyMap), store, keyMap);
1054
1097
  };
1098
+ // Tracks whether the default locale's translation has been loaded by any prior
1099
+ // resolver call. Captured in closure across navigations on purpose — concurrent
1100
+ // resolves may each see `false` and both queue a default preload; that's harmless
1101
+ // because `store.register` is idempotent (same payload overwrites with the same
1102
+ // value). Do not add locking here.
1055
1103
  let defaultTranslationLoaded = false;
1056
1104
  const resolver = async (snapshot) => {
1057
1105
  const store = inject(TranslationStore);
1058
1106
  const locale = injectResolveParamLocale(snapshot);
1059
1107
  const defaultLocale = injectDefaultLocale();
1060
1108
  const shouldPreloadDefault = injectIntlConfig()?.preloadDefaultLocale ?? false;
1061
- const tPromise = other[locale];
1062
- const promise = tPromise ?? defaultTranslation;
1109
+ const tPromise = unwrappedOther[locale];
1110
+ const promise = tPromise ?? unwrappedDefault;
1063
1111
  if (!promise && isDevMode()) {
1064
1112
  return console.warn(`No translation found for locale: ${locale}`);
1065
1113
  }
1066
- if (promise === defaultTranslation && defaultTranslationLoaded)
1114
+ if (promise === unwrappedDefault && defaultTranslationLoaded)
1067
1115
  return;
1068
1116
  try {
1069
1117
  const promises = [promise()];
1070
1118
  if (shouldPreloadDefault &&
1071
1119
  !defaultTranslationLoaded &&
1072
- promise !== defaultTranslation)
1073
- promises.push(defaultTranslation());
1120
+ promise !== unwrappedDefault)
1121
+ promises.push(unwrappedDefault());
1074
1122
  const translations = await Promise.allSettled(promises);
1075
- const fullfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
1076
- if (fullfilled.at(0) === null && fullfilled.at(1) === null)
1123
+ const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
1124
+ if (fulfilled.at(0) === null && fulfilled.at(1) === null)
1077
1125
  throw new Error('Failed to load translations');
1078
- const [t, defaultT] = fullfilled;
1126
+ const [t, defaultT] = fulfilled;
1079
1127
  const ns = t?.namespace ?? defaultT?.namespace;
1080
1128
  if (!ns)
1081
1129
  throw new Error('No namespace found in translation');
1082
1130
  if (isDevMode() && t && t.locale !== locale && t.locale)
1083
1131
  console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
1084
1132
  store.registerOnDemandLoaders(ns, {
1085
- ...other,
1086
- [defaultLocale]: defaultTranslation,
1133
+ ...unwrappedOther,
1134
+ [defaultLocale]: unwrappedDefault,
1087
1135
  });
1088
1136
  const toRegister = {};
1089
1137
  if (t)
@@ -1091,7 +1139,7 @@ function registerNamespace(defaultTranslation, other) {
1091
1139
  if (defaultT)
1092
1140
  toRegister[defaultLocale] = defaultT.flat;
1093
1141
  store.register(ns, toRegister);
1094
- if (promise === defaultTranslation || defaultT)
1142
+ if (promise === unwrappedDefault || defaultT)
1095
1143
  defaultTranslationLoaded = true;
1096
1144
  }
1097
1145
  catch {
@@ -1115,10 +1163,21 @@ function registerNamespace(defaultTranslation, other) {
1115
1163
  */
1116
1164
  function registerRemoteNamespace(ns, defaultTranslation, other) {
1117
1165
  const keyMap = new Map();
1166
+ // The dynamic-locale loader in TranslationStore reads `.namespace` and `.flat`
1167
+ // off the loader result, so on-demand loaders must return CompiledTranslation.
1168
+ // Wrap the raw remote fetchers once here at registration time rather than every
1169
+ // resolver call.
1170
+ const compileLoader = (loader, locale) => () => loader().then((raw) => compileTranslation(raw, ns, locale));
1171
+ const compiledOther = Object.fromEntries(Object.entries(other).map(([loc, loader]) => [
1172
+ loc,
1173
+ compileLoader(loader, loc),
1174
+ ]));
1118
1175
  const injectT = () => {
1119
1176
  const store = inject(TranslationStore);
1120
1177
  return addSignalFn(createT(store, keyMap), store, keyMap);
1121
1178
  };
1179
+ // See `defaultTranslationLoaded` in `registerNamespace` for rationale —
1180
+ // intentionally racy, safe via idempotent `store.register`.
1122
1181
  let defaultTranslationLoaded = false;
1123
1182
  const resolver = async (snapshot) => {
1124
1183
  const store = inject(TranslationStore);
@@ -1139,10 +1198,10 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
1139
1198
  promise !== defaultTranslation)
1140
1199
  promises.push(defaultTranslation());
1141
1200
  const translations = await Promise.allSettled(promises);
1142
- const fullfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
1143
- if (fullfilled.at(0) === null && fullfilled.at(1) === null)
1201
+ const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
1202
+ if (fulfilled.at(0) === null && fulfilled.at(1) === null)
1144
1203
  throw new Error('Failed to load translations');
1145
- const [baseT, baseDefaultT] = fullfilled;
1204
+ const [baseT, baseDefaultT] = fulfilled;
1146
1205
  const t = baseT ? compileTranslation(baseT, ns, locale) : null;
1147
1206
  const defaultT = baseDefaultT
1148
1207
  ? compileTranslation(baseDefaultT, ns, defaultLocale)
@@ -1150,8 +1209,8 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
1150
1209
  if (isDevMode() && t && t.locale !== locale && t.locale)
1151
1210
  console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
1152
1211
  store.registerOnDemandLoaders(ns, {
1153
- ...other,
1154
- [defaultLocale]: defaultTranslation,
1212
+ ...compiledOther,
1213
+ [defaultLocale]: compileLoader(defaultTranslation, defaultLocale),
1155
1214
  });
1156
1215
  const toRegister = {};
1157
1216
  if (t)
@@ -1384,6 +1443,10 @@ function createTransformFn() {
1384
1443
  const store = inject(TranslationStore);
1385
1444
  const t = createT(store);
1386
1445
  const fn = (key, variablesOrLocale,
1446
+ // The locale argument is unused at runtime — the active locale comes from
1447
+ // `TranslationStore`. It exists on the signature so consumers can pass
1448
+ // `locale()` as a pipe argument (`'key' | translate : vars : locale()`)
1449
+ // to bust Angular's pure-pipe memoization on locale changes.
1387
1450
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1388
1451
  _) => {
1389
1452
  const vars = typeof variablesOrLocale === 'string' ? undefined : variablesOrLocale;
@@ -1434,5 +1497,5 @@ function withParams(message) {
1434
1497
  * Generated bundle index. Do not edit.
1435
1498
  */
1436
1499
 
1437
- 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 };
1500
+ 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 };
1438
1501
  //# sourceMappingURL=mmstack-translate.mjs.map