@pure-ds/core 0.7.36 → 0.7.38

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.
@@ -71,6 +71,59 @@ function normalizeAliasMap(aliases = {}) {
71
71
  return normalized;
72
72
  }
73
73
 
74
+ function resolveAliasTarget(aliasKey, aliases, localeSet) {
75
+ const values = Array.isArray(aliases?.[aliasKey]) ? aliases[aliasKey] : [];
76
+ for (const value of values) {
77
+ const normalizedValue = normalizeLocaleTag(value);
78
+ if (localeSet.has(normalizedValue)) {
79
+ return normalizedValue;
80
+ }
81
+ }
82
+ return "";
83
+ }
84
+
85
+ function createAliasLookup({ aliases, localeSet }) {
86
+ const lookup = new Map();
87
+
88
+ for (const locale of localeSet) {
89
+ lookup.set(locale, locale);
90
+ }
91
+
92
+ for (const key of Object.keys(aliases || {})) {
93
+ const target = resolveAliasTarget(key, aliases, localeSet);
94
+ if (!target) {
95
+ throw new Error(
96
+ `[i18n] Locale alias "${key}" does not map to any configured locale.`
97
+ );
98
+ }
99
+ lookup.set(key, target);
100
+ }
101
+
102
+ return lookup;
103
+ }
104
+
105
+ function resolveConfiguredLocale(locale, { defaultLocale, aliasLookup }) {
106
+ const normalized = normalizeLocaleTag(locale);
107
+ if (!normalized) {
108
+ return defaultLocale;
109
+ }
110
+
111
+ const direct = aliasLookup.get(normalized);
112
+ if (direct) {
113
+ return direct;
114
+ }
115
+
116
+ const base = toBaseLocale(normalized);
117
+ const baseMapped = base ? aliasLookup.get(base) : "";
118
+ if (baseMapped) {
119
+ return baseMapped;
120
+ }
121
+
122
+ throw new Error(
123
+ `[i18n] Locale alias "${locale}" is not configured. Add an alias entry for "${base || normalized}".`
124
+ );
125
+ }
126
+
74
127
  function normalizeBundleShape(bundle) {
75
128
  if (!bundle || typeof bundle !== "object" || Array.isArray(bundle)) {
76
129
  return {};
@@ -177,6 +230,7 @@ function buildCandidateLocales({ locale, effectiveLocale, defaultLocale, aliases
177
230
  * locales: string[],
178
231
  * provider: {
179
232
  * locales: string[],
233
+ * resolveLocale: (locale: string) => string,
180
234
  * loadLocale: (context: { locale: string }) => Promise<Record<string, string | { content?: string }>>,
181
235
  * },
182
236
  * }}
@@ -186,6 +240,7 @@ export function createJSONLocalization(options = {}) {
186
240
  const locales = toLocaleList(options?.locales, defaultLocale);
187
241
  const localeSet = new Set(locales);
188
242
  const aliases = normalizeAliasMap(options?.aliases || {});
243
+ const aliasLookup = createAliasLookup({ aliases, localeSet });
189
244
  const cache = options?.cache instanceof Map ? options.cache : new Map();
190
245
  const basePath = normalizeBasePath(options?.basePath);
191
246
  const requestInit =
@@ -194,21 +249,10 @@ export function createJSONLocalization(options = {}) {
194
249
  : {};
195
250
 
196
251
  const resolveEffectiveLocale = (locale) => {
197
- const normalized = normalizeLocaleTag(locale);
198
- if (!normalized) {
199
- return defaultLocale;
200
- }
201
-
202
- if (localeSet.has(normalized)) {
203
- return normalized;
204
- }
205
-
206
- const base = toBaseLocale(normalized);
207
- if (localeSet.has(base)) {
208
- return base;
209
- }
210
-
211
- return defaultLocale;
252
+ return resolveConfiguredLocale(locale, {
253
+ defaultLocale,
254
+ aliasLookup,
255
+ });
212
256
  };
213
257
 
214
258
  const loadLocale = async ({ locale }) => {
@@ -268,6 +312,7 @@ export function createJSONLocalization(options = {}) {
268
312
  locales: [...locales],
269
313
  provider: {
270
314
  locales: [...locales],
315
+ resolveLocale: resolveEffectiveLocale,
271
316
  loadLocale,
272
317
  },
273
318
  };
@@ -11,10 +11,22 @@ const __localizationState = {
11
11
  reconcileTimer: null,
12
12
  requestedKeys: new Set(),
13
13
  textNodeKeyMap: new WeakMap(),
14
+ attributeKeyMap: new WeakMap(),
14
15
  valueToKeys: new Map(),
15
16
  missingWarnings: new Set(),
16
17
  };
17
18
 
19
+ const __LOCALIZABLE_ATTRIBUTES = [
20
+ "title",
21
+ "placeholder",
22
+ "aria-label",
23
+ "aria-description",
24
+ "aria-placeholder",
25
+ "aria-roledescription",
26
+ "alt",
27
+ "label",
28
+ ];
29
+
18
30
  const __isStrTagged = (val) =>
19
31
  Boolean(val) && typeof val !== "string" && typeof val === "object" && "strTag" in val;
20
32
 
@@ -33,6 +45,15 @@ function __resolveLocaleCandidate(locale) {
33
45
  if (!normalized) {
34
46
  return __localizationState.defaultLocale;
35
47
  }
48
+
49
+ const resolveLocale = __localizationState.provider?.resolveLocale;
50
+ if (typeof resolveLocale === "function") {
51
+ const resolved = __normalizeLocale(resolveLocale(locale));
52
+ if (resolved) {
53
+ return resolved;
54
+ }
55
+ }
56
+
36
57
  return normalized;
37
58
  }
38
59
 
@@ -98,7 +119,14 @@ function __resolveProvider(config) {
98
119
  ? provider.setLocale
99
120
  : null;
100
121
 
101
- if (!translate && !loadLocale && !setLocale) {
122
+ const resolveLocale =
123
+ typeof config?.resolveLocale === "function"
124
+ ? config.resolveLocale
125
+ : typeof provider?.resolveLocale === "function"
126
+ ? provider.resolveLocale
127
+ : null;
128
+
129
+ if (!translate && !loadLocale && !setLocale && !resolveLocale) {
102
130
  return null;
103
131
  }
104
132
 
@@ -106,6 +134,7 @@ function __resolveProvider(config) {
106
134
  translate,
107
135
  loadLocale,
108
136
  setLocale,
137
+ resolveLocale,
109
138
  };
110
139
  }
111
140
 
@@ -599,10 +628,135 @@ async function __localizeRequestedTextNodes() {
599
628
  }
600
629
  }
601
630
 
631
+ function __getElementAttributeKeyMap(element) {
632
+ let map = __localizationState.attributeKeyMap.get(element);
633
+ if (!map) {
634
+ map = new Map();
635
+ __localizationState.attributeKeyMap.set(element, map);
636
+ }
637
+ return map;
638
+ }
639
+
640
+ async function __localizeAttribute(element, attrName) {
641
+ if (!element || typeof element.getAttribute !== "function") {
642
+ return;
643
+ }
644
+
645
+ const rawValue = element.getAttribute(attrName);
646
+ if (typeof rawValue !== "string" || !rawValue.length) {
647
+ return;
648
+ }
649
+
650
+ const keyMap = __getElementAttributeKeyMap(element);
651
+ let key = keyMap.get(attrName) || null;
652
+
653
+ if (!key || !__localizationState.requestedKeys.has(key)) {
654
+ key = __findRequestedKeyForText(rawValue);
655
+ }
656
+
657
+ if (!key) {
658
+ const segmentMatch = __findRequestedSubsegmentForText(rawValue);
659
+ if (!segmentMatch) {
660
+ return;
661
+ }
662
+
663
+ const scopedLocale = __resolveContextLocale({ element });
664
+ await __loadLocaleInternal(scopedLocale, "attribute");
665
+
666
+ const translated = __resolveTranslation(segmentMatch.key, segmentMatch.values, { element }, null);
667
+ const translatedText = segmentMatch.values.length
668
+ ? __replacePlaceholders(translated, (index) => segmentMatch.values[index])
669
+ : translated;
670
+
671
+ const localizedValue =
672
+ rawValue.slice(0, segmentMatch.start) +
673
+ translatedText +
674
+ rawValue.slice(segmentMatch.end);
675
+
676
+ if (localizedValue !== rawValue) {
677
+ element.setAttribute(attrName, localizedValue);
678
+ }
679
+
680
+ keyMap.set(attrName, segmentMatch.key);
681
+ return;
682
+ }
683
+
684
+ keyMap.set(attrName, key);
685
+
686
+ const scopedLocale = __resolveContextLocale({ element });
687
+ await __loadLocaleInternal(scopedLocale, "attribute");
688
+
689
+ const values = __resolveTemplateValuesForText(key, rawValue);
690
+ const translated = __resolveTranslation(key, values, { element }, null);
691
+ const translatedText = values.length
692
+ ? __replacePlaceholders(translated, (index) => values[index])
693
+ : translated;
694
+
695
+ if (translatedText !== rawValue) {
696
+ element.setAttribute(attrName, translatedText);
697
+ }
698
+ }
699
+
700
+ async function __localizeRequestedAttributes() {
701
+ if (typeof document === "undefined" || __localizationState.requestedKeys.size === 0) {
702
+ return;
703
+ }
704
+
705
+ const root = document.body || document.documentElement;
706
+ if (!root) {
707
+ return;
708
+ }
709
+
710
+ const roots = [];
711
+ const seenRoots = new Set();
712
+
713
+ const addRoot = (candidateRoot) => {
714
+ if (!candidateRoot || seenRoots.has(candidateRoot)) {
715
+ return;
716
+ }
717
+
718
+ seenRoots.add(candidateRoot);
719
+ roots.push(candidateRoot);
720
+ };
721
+
722
+ addRoot(root);
723
+
724
+ for (let index = 0; index < roots.length; index += 1) {
725
+ const currentRoot = roots[index];
726
+ if (!currentRoot || typeof currentRoot.querySelectorAll !== "function") {
727
+ continue;
728
+ }
729
+
730
+ const elements = currentRoot.querySelectorAll("*");
731
+ for (const element of elements) {
732
+ const shadowRoot = element?.shadowRoot;
733
+ if (shadowRoot) {
734
+ addRoot(shadowRoot);
735
+ }
736
+ }
737
+ }
738
+
739
+ for (const scanRoot of roots) {
740
+ if (!scanRoot || typeof scanRoot.querySelectorAll !== "function") {
741
+ continue;
742
+ }
743
+
744
+ const elements = scanRoot.querySelectorAll("*");
745
+ for (const element of elements) {
746
+ for (const attrName of __LOCALIZABLE_ATTRIBUTES) {
747
+ if (element.hasAttribute(attrName)) {
748
+ await __localizeAttribute(element, attrName);
749
+ }
750
+ }
751
+ }
752
+ }
753
+ }
754
+
602
755
  async function __reconcileLocalization() {
603
756
  const detectedLocales = __collectDetectedLocales();
604
757
  await __ensureDetectedLocalesLoaded(detectedLocales);
605
758
  await __localizeRequestedTextNodes();
759
+ await __localizeRequestedAttributes();
606
760
  __pruneUndetectedLocales(detectedLocales);
607
761
  }
608
762
 
@@ -748,6 +902,7 @@ export function configureLocalization(config = null) {
748
902
  __localizationState.loadingByLocale.clear();
749
903
  __localizationState.requestedKeys.clear();
750
904
  __localizationState.textNodeKeyMap = new WeakMap();
905
+ __localizationState.attributeKeyMap = new WeakMap();
751
906
  __localizationState.valueToKeys.clear();
752
907
  __localizationState.missingWarnings.clear();
753
908
 
@@ -767,11 +922,18 @@ export function configureLocalization(config = null) {
767
922
  Object.prototype.hasOwnProperty.call(config, "provider") ||
768
923
  Object.prototype.hasOwnProperty.call(config, "translate") ||
769
924
  Object.prototype.hasOwnProperty.call(config, "loadLocale") ||
770
- Object.prototype.hasOwnProperty.call(config, "setLocale")
925
+ Object.prototype.hasOwnProperty.call(config, "setLocale") ||
926
+ Object.prototype.hasOwnProperty.call(config, "resolveLocale")
771
927
  ) {
772
928
  __localizationState.provider = __resolveProvider(config);
773
929
  }
774
930
 
931
+ if (__localizationState.provider?.resolveLocale) {
932
+ __localizationState.defaultLocale = __resolveLocaleCandidate(
933
+ __localizationState.defaultLocale
934
+ );
935
+ }
936
+
775
937
  __attachLangObserver();
776
938
  __scheduleReconcile();
777
939
 
@@ -3,6 +3,7 @@
3
3
  * Separated to keep the base runtime bundle lean for production/static usage.
4
4
  */
5
5
  import { Generator } from "./pds-generator.js";
6
+ import { msg } from "../common/localization.js";
6
7
  import { applyStyles, adoptLayers, adoptPrimitives } from "./pds-runtime.js";
7
8
  import {
8
9
  presets,
@@ -309,10 +310,10 @@ async function ensureLiveEditToggleButton() {
309
310
  return li;
310
311
  };
311
312
 
312
- menu.appendChild(createItem("toggle", "Toggle live editing", "pencil"));
313
- menu.appendChild(createItem("open-settings", "Open Settings", "gear"));
313
+ menu.appendChild(createItem("toggle", msg("Toggle live editing"), "pencil"));
314
+ menu.appendChild(createItem("open-settings", msg("Open Settings"), "gear"));
314
315
  menu.appendChild(createSeparator());
315
- menu.appendChild(createItem("reset-config", "Reset Config", "arrow-counter-clockwise"));
316
+ menu.appendChild(createItem("reset-config", msg("Reset Config"), "arrow-counter-clockwise"));
316
317
 
317
318
  await ensureSharedQuickModeToggleMenuItem(menu);
318
319
 
package/src/js/pds.d.ts CHANGED
@@ -124,6 +124,7 @@ export interface PDSLocalizationTranslateContext {
124
124
 
125
125
  export interface PDSLocalizationProvider {
126
126
  translate?: (context: PDSLocalizationTranslateContext) => string | undefined | null;
127
+ resolveLocale?: (locale: string) => string;
127
128
  loadLocale?: (context: {
128
129
  locale: string;
129
130
  defaultLocale: string;
@@ -154,6 +155,7 @@ export interface PDSLocalizationConfig {
154
155
  messages?: PDSLocalizationMessages;
155
156
  provider?: PDSLocalizationProvider;
156
157
  translate?: PDSLocalizationProvider["translate"];
158
+ resolveLocale?: PDSLocalizationProvider["resolveLocale"];
157
159
  loadLocale?: PDSLocalizationProvider["loadLocale"];
158
160
  setLocale?: PDSLocalizationProvider["setLocale"];
159
161
  }