@4site/engrid-scripts 0.27.6 → 0.28.1

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.
package/dist/a11y.js CHANGED
@@ -364,7 +364,10 @@ export class A11y {
364
364
  }
365
365
  else {
366
366
  const cleaned = allMessages.map(message => message.replace(/[.!?]+$/, '').trim());
367
- region.textContent = `There are ${allMessages.length} errors: ${cleaned.join('. ')}.`;
367
+ region.textContent = ENGrid.t("a11y.errorSummary", {
368
+ count: allMessages.length,
369
+ messages: cleaned.join('. '),
370
+ });
368
371
  }
369
372
  if (this.shouldFocusFirstInvalidField) {
370
373
  this.shouldFocusFirstInvalidField = false;
@@ -19,6 +19,10 @@ export class DataAttributes {
19
19
  if (ENGrid.checkNested(window, "pageJson", "pageType")) {
20
20
  ENGrid.setBodyData("page-type", window.pageJson.pageType);
21
21
  }
22
+ // Add the locale as a Data Attribute on the Body Tag
23
+ if (ENGrid.checkNested(window, "pageJson", "locale")) {
24
+ ENGrid.setBodyData("locale", window.pageJson.locale.toLowerCase());
25
+ }
22
26
  // Add the currency code as a Data Attribute on the Body Tag
23
27
  ENGrid.setBodyData("currency-code", ENGrid.getCurrencyCode());
24
28
  // Add a body banner data attribute if the banner contains no image or video
package/dist/engrid.d.ts CHANGED
@@ -28,6 +28,10 @@ export declare abstract class ENGrid {
28
28
  static getClientID(): any;
29
29
  static getDataCenter(): "us2" | "us" | "ca";
30
30
  static getPageType(): "DONATION" | "ECARD" | "SURVEY" | "EMAILTOTARGET" | "ADVOCACY" | "SUBSCRIBEFORM" | "EVENT" | "SUPPORTERHUB" | "UNSUBSCRIBE" | "TWEETPAGE" | "UNKNOWN";
31
+ static getPageLanguage(): string;
32
+ private static getI18nDictionaries;
33
+ static hasI18nKey(key: string): boolean;
34
+ static t(key: string, replacements?: Record<string, string | number>): string;
31
35
  static setBodyData(dataName: string, value: string | boolean): void;
32
36
  static getBodyData(dataName: string): string | null;
33
37
  static hasBodyData(dataName: string): boolean;
package/dist/engrid.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { I18nDefaults } from "./interfaces/i18n-options";
1
2
  const errorCallbacks = new Map();
2
3
  export class ENGrid {
3
4
  constructor() {
@@ -261,6 +262,52 @@ export class ENGrid {
261
262
  return "UNKNOWN";
262
263
  }
263
264
  }
265
+ // Return the current page language: the first 2 characters of
266
+ // pageJson.locale, lowercased (e.g. "es_US" -> "es"). Defaults to "en"
267
+ // when pageJson or the locale is not available.
268
+ static getPageLanguage() {
269
+ var _a;
270
+ const locale = (_a = window.pageJson) === null || _a === void 0 ? void 0 : _a.locale;
271
+ if (typeof locale === "string" && locale.length >= 2) {
272
+ return locale.substring(0, 2).toLowerCase();
273
+ }
274
+ return "en";
275
+ }
276
+ // Return the merged i18n dictionaries: window.EngridI18n merged over
277
+ // I18nDefaults, key-by-key per language, without mutating the defaults.
278
+ static getI18nDictionaries() {
279
+ const dictionaries = Object.assign({}, I18nDefaults);
280
+ if ("EngridI18n" in window && window.EngridI18n) {
281
+ for (const lang in window.EngridI18n) {
282
+ dictionaries[lang] = Object.assign(Object.assign({}, (I18nDefaults[lang] || {})), window.EngridI18n[lang]);
283
+ }
284
+ }
285
+ return dictionaries;
286
+ }
287
+ // Check if an i18n key is defined in the merged dictionary for the current
288
+ // page language (the English fallback does not count).
289
+ static hasI18nKey(key) {
290
+ var _a;
291
+ const language = ENGrid.getPageLanguage();
292
+ return key in ((_a = ENGrid.getI18nDictionaries()[language]) !== null && _a !== void 0 ? _a : {});
293
+ }
294
+ // Translate a UI string key using the i18n dictionary: I18nDefaults merged
295
+ // with the window.EngridI18n global (key-by-key, per language, without
296
+ // mutating the defaults). Resolution order: current page language bucket ->
297
+ // English bucket -> the key itself. {placeholders} are interpolated from
298
+ // the replacements argument.
299
+ static t(key, replacements = {}) {
300
+ var _a, _b, _c, _d;
301
+ const dictionaries = ENGrid.getI18nDictionaries();
302
+ const language = ENGrid.getPageLanguage();
303
+ let text = (_d = (_b = (_a = dictionaries[language]) === null || _a === void 0 ? void 0 : _a[key]) !== null && _b !== void 0 ? _b : (_c = dictionaries["en"]) === null || _c === void 0 ? void 0 : _c[key]) !== null && _d !== void 0 ? _d : key;
304
+ for (const name in replacements) {
305
+ // Function replacement: the value is inserted literally, so $-sequences
306
+ // ($&, $$, ...) in user-facing text are never interpreted.
307
+ text = text.replace(new RegExp(`\\{${name}\\}`, "g"), () => String(replacements[name]));
308
+ }
309
+ return text;
310
+ }
264
311
  // Set body engrid data attributes
265
312
  static setBodyData(dataName, value) {
266
313
  const body = document.querySelector("body");
@@ -6,6 +6,8 @@ export declare class DonationAmount {
6
6
  private _dispatch;
7
7
  private static instance;
8
8
  private constructor();
9
+ isOtherAmountSelected(): boolean;
10
+ private syncOtherAmount;
9
11
  static getInstance(radios?: string, other?: string): DonationAmount;
10
12
  get amount(): number;
11
13
  set amount(value: number);
@@ -17,12 +17,7 @@ export class DonationAmount {
17
17
  this.amount = parseFloat(element.value);
18
18
  }
19
19
  else if (element.name == other) {
20
- const cleanedAmount = ENGrid.cleanAmount(element.value);
21
- element.value =
22
- cleanedAmount % 1 != 0
23
- ? cleanedAmount.toFixed(2)
24
- : cleanedAmount.toString();
25
- this.amount = cleanedAmount;
20
+ this.syncOtherAmount(element, true);
26
21
  }
27
22
  }
28
23
  });
@@ -30,12 +25,30 @@ export class DonationAmount {
30
25
  const otherField = document.querySelector(`[name='${this._other}']`);
31
26
  if (otherField) {
32
27
  otherField.addEventListener("keyup", (e) => {
33
- this.amount = ENGrid.cleanAmount(otherField.value);
28
+ this.syncOtherAmount(otherField);
34
29
  });
35
30
  }
36
31
  // Load the current amount
37
32
  this.load();
38
33
  }
34
+ // The "other" radio is the one whose value isn't a numeric amount
35
+ // (EN renders it as value="other"), so it cleans to 0
36
+ isOtherAmountSelected() {
37
+ const selectedAmount = document.querySelector(`input[name="${this._radios}"]:checked`);
38
+ return (selectedAmount !== null &&
39
+ ENGrid.cleanAmount(selectedAmount.value) === 0);
40
+ }
41
+ syncOtherAmount(field, formatValue = false) {
42
+ const otherIsSelected = this.isOtherAmountSelected();
43
+ const amount = ENGrid.cleanAmount(field.value);
44
+ if (!otherIsSelected || amount <= 0) {
45
+ return;
46
+ }
47
+ if (formatValue) {
48
+ field.value = amount % 1 != 0 ? amount.toFixed(2) : amount.toString();
49
+ }
50
+ this.amount = amount;
51
+ }
39
52
  static getInstance(radios = "transaction.donationAmt", other = "transaction.donationAmt.other") {
40
53
  if (!DonationAmount.instance) {
41
54
  DonationAmount.instance = new DonationAmount(radios, other);
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from "./deprecated";
2
2
  export { Options, OptionsDefaults } from "./interfaces/options";
3
3
  export { UpsellOptions, UpsellOptionsDefaults, } from "./interfaces/upsell-options";
4
4
  export { TranslateOptions, TranslateOptionsDefaults, } from "./interfaces/translate-options";
5
+ export { I18nOptions, I18nDefaults } from "./interfaces/i18n-options";
5
6
  export { ExitIntentOptions, ExitIntentOptionsDefaults, } from "./interfaces/exit-intent-options";
6
7
  export { FrequencyUpsellOptions, FrequencyUpsellOptionsDefaults, FrequencyUpsellABTestConfig, FrequencyUpsellConfig, } from "./interfaces/frequency-upsell-options";
7
8
  export { IframeQueueOptions, IframeQueueOptionsDefaults, IframeQueueItem, IframeQueueErrorPayload, } from "./interfaces/iframe-queue-options";
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export * from "./deprecated"; // Runs first so it can change the DOM markup befo
2
2
  export { OptionsDefaults } from "./interfaces/options";
3
3
  export { UpsellOptionsDefaults, } from "./interfaces/upsell-options";
4
4
  export { TranslateOptionsDefaults, } from "./interfaces/translate-options";
5
+ export { I18nDefaults } from "./interfaces/i18n-options";
5
6
  export { ExitIntentOptionsDefaults, } from "./interfaces/exit-intent-options";
6
7
  export { FrequencyUpsellOptionsDefaults, } from "./interfaces/frequency-upsell-options";
7
8
  export { IframeQueueOptionsDefaults, } from "./interfaces/iframe-queue-options";
@@ -1,7 +1,10 @@
1
1
  export declare class InputPlaceholders {
2
2
  private defaultPlaceholders;
3
+ private selectorToI18nKey;
4
+ private customSelectors;
3
5
  constructor();
4
6
  private shouldRun;
5
7
  private run;
8
+ private resolvePlaceholder;
6
9
  private addPlaceholder;
7
10
  }
@@ -12,6 +12,9 @@
12
12
  import { ENGrid } from ".";
13
13
  export class InputPlaceholders {
14
14
  constructor() {
15
+ // NOTE: for selectors listed in selectorToI18nKey below, these English
16
+ // strings are shadowed by the i18n dictionary — edit
17
+ // interfaces/i18n-options.ts ("placeholders.*" keys) instead of here.
15
18
  this.defaultPlaceholders = {
16
19
  "input#en__field_supporter_firstName": "First Name",
17
20
  "input#en__field_supporter_lastName": "Last Name",
@@ -57,10 +60,30 @@ export class InputPlaceholders {
57
60
  "input#en__field_supporter_billingRegion": "Billing Region",
58
61
  "input#en__field_supporter_billingPostcode": "Billing Postal Code",
59
62
  };
63
+ // Maps the default-placeholder selectors to i18n dictionary keys, so the
64
+ // built-in strings follow the page language. Selectors the client overrides
65
+ // via the Placeholders option are never translated.
66
+ this.selectorToI18nKey = {
67
+ "input#en__field_supporter_firstName": "placeholders.firstName",
68
+ "input#en__field_supporter_lastName": "placeholders.lastName",
69
+ "input#en__field_supporter_emailAddress": "placeholders.emailAddress",
70
+ "input#en__field_supporter_phoneNumber": "placeholders.phoneNumberOptional",
71
+ ".en__mandatory input#en__field_supporter_phoneNumber": "placeholders.phoneNumber",
72
+ ".i-required input#en__field_supporter_phoneNumber": "placeholders.phoneNumber",
73
+ "input#en__field_supporter_phoneNumber2": "placeholders.phoneNumber2Optional",
74
+ "input#en__field_supporter_country": "placeholders.country",
75
+ "input#en__field_supporter_address1": "placeholders.address1",
76
+ "input#en__field_supporter_address2": "placeholders.address2",
77
+ "input#en__field_supporter_city": "placeholders.city",
78
+ "input#en__field_supporter_region": "placeholders.region",
79
+ "input#en__field_supporter_postcode": "placeholders.postcode",
80
+ };
81
+ this.customSelectors = new Set();
60
82
  if (this.shouldRun()) {
61
83
  // If there's a Placeholders option, merge it with the default placeholders
62
84
  const placeholders = ENGrid.getOption("Placeholders");
63
85
  if (placeholders) {
86
+ this.customSelectors = new Set(Object.keys(placeholders));
64
87
  this.defaultPlaceholders = Object.assign(Object.assign({}, this.defaultPlaceholders), placeholders);
65
88
  }
66
89
  this.run();
@@ -72,9 +95,18 @@ export class InputPlaceholders {
72
95
  run() {
73
96
  Object.keys(this.defaultPlaceholders).forEach((selector) => {
74
97
  if (selector in this.defaultPlaceholders)
75
- this.addPlaceholder(selector, this.defaultPlaceholders[selector]);
98
+ this.addPlaceholder(selector, this.resolvePlaceholder(selector));
76
99
  });
77
100
  }
101
+ // Built-in placeholder strings follow the page language; client-provided
102
+ // Placeholders options always win.
103
+ resolvePlaceholder(selector) {
104
+ const key = this.selectorToI18nKey[selector];
105
+ if (key && !this.customSelectors.has(selector)) {
106
+ return ENGrid.t(key);
107
+ }
108
+ return this.defaultPlaceholders[selector];
109
+ }
78
110
  addPlaceholder(selector, placeholder) {
79
111
  const fieldEl = document.querySelector(selector);
80
112
  if (fieldEl) {
@@ -0,0 +1,6 @@
1
+ export interface I18nOptions {
2
+ [lang: string]: {
3
+ [key: string]: string;
4
+ };
5
+ }
6
+ export declare const I18nDefaults: I18nOptions;
@@ -0,0 +1,56 @@
1
+ export const I18nDefaults = {
2
+ en: {
3
+ "rememberMe.label": "Remember Me",
4
+ "rememberMe.clearLabel": "(clear autofill)",
5
+ "rememberMe.tooltip": "Check “{label}” to complete forms on this device faster. While your financial information won’t be stored, you should only check this box from a personal device. Click “{clearLabel}” to remove the information from your device at any time.",
6
+ "rememberMe.iframeTitle": "Remember Me iframe",
7
+ "translateFields.state": "State",
8
+ "translateFields.stateGeneric": "Province / State",
9
+ "translateFields.stateRegion": "State/Region",
10
+ "translateFields.provinceTerritory": "Province / Territory",
11
+ "translateFields.selectState": "Select State",
12
+ "translateFields.select": "Select",
13
+ "translateFields.recipientTo": "To:",
14
+ "a11y.errorSummary": "There are {count} errors: {messages}.",
15
+ // InputPlaceholders component defaults
16
+ "placeholders.firstName": "First Name",
17
+ "placeholders.lastName": "Last Name",
18
+ "placeholders.emailAddress": "Email Address",
19
+ "placeholders.phoneNumber": "Phone Number",
20
+ "placeholders.phoneNumberOptional": "Phone Number (Optional)",
21
+ "placeholders.phoneNumber2Optional": "000-000-0000 (Optional)",
22
+ "placeholders.country": "Country",
23
+ "placeholders.address1": "Street Address",
24
+ "placeholders.address2": "Apt., Ste., Bldg.",
25
+ "placeholders.city": "City",
26
+ "placeholders.region": "Region",
27
+ "placeholders.postcode": "ZIP Code",
28
+ },
29
+ es: {
30
+ "rememberMe.label": "Recuérdame",
31
+ "rememberMe.clearLabel": "(borrar autocompletado)",
32
+ "rememberMe.tooltip": "Marque “{label}” para completar los formularios en este dispositivo más rápido. Aunque su información financiera no se almacenará, solo debe marcar esta casilla desde un dispositivo personal. Haga clic en “{clearLabel}” para eliminar la información de su dispositivo en cualquier momento.",
33
+ "rememberMe.iframeTitle": "iframe de Recuérdame",
34
+ "translateFields.state": "Estado",
35
+ "translateFields.stateGeneric": "Provincia/Estado",
36
+ "translateFields.stateRegion": "Estado/Región",
37
+ "translateFields.provinceTerritory": "Provincia/Territorio",
38
+ "translateFields.selectState": "Seleccione Estado",
39
+ "translateFields.select": "Seleccione",
40
+ "translateFields.recipientTo": "Para:",
41
+ "a11y.errorSummary": "Hay {count} errores: {messages}.",
42
+ // InputPlaceholders component defaults
43
+ "placeholders.firstName": "Nombre",
44
+ "placeholders.lastName": "Apellidos",
45
+ "placeholders.emailAddress": "Correo electrónico",
46
+ "placeholders.phoneNumber": "Teléfono",
47
+ "placeholders.phoneNumberOptional": "Teléfono (opcional)",
48
+ "placeholders.phoneNumber2Optional": "000-000-0000 (opcional)",
49
+ "placeholders.country": "País",
50
+ "placeholders.address1": "Calle y número",
51
+ "placeholders.address2": "Depto., Piso, Edif.",
52
+ "placeholders.city": "Ciudad",
53
+ "placeholders.region": "Provincia/Estado",
54
+ "placeholders.postcode": "Código Postal",
55
+ },
56
+ };
@@ -30,6 +30,21 @@ const nlTranslation = [
30
30
  { field: "supporter.region", translation: "Provincie" },
31
31
  { field: "supporter.country", translation: "Country" },
32
32
  ];
33
+ // Page-language layer: keyed by lowercase 2-letter language code
34
+ // (see ENGrid.getPageLanguage()), applied as the base translation layer
35
+ // before any country-specific translations.
36
+ const esTranslation = [
37
+ { field: "supporter.firstName", translation: "Nombre" },
38
+ { field: "supporter.lastName", translation: "Apellidos" },
39
+ { field: "supporter.emailAddress", translation: "Correo electrónico" },
40
+ { field: "supporter.phoneNumber", translation: "Teléfono" },
41
+ { field: "supporter.address1", translation: "Dirección" },
42
+ { field: "supporter.address2", translation: "Departamento/Piso" },
43
+ { field: "supporter.postcode", translation: "Código Postal" },
44
+ { field: "supporter.city", translation: "Ciudad" },
45
+ { field: "supporter.region", translation: "Provincia/Estado" },
46
+ { field: "supporter.country", translation: "País" },
47
+ ];
33
48
  export const TranslateOptionsDefaults = {
34
49
  BR: ptbrTranslation,
35
50
  BRA: ptbrTranslation,
@@ -39,4 +54,5 @@ export const TranslateOptionsDefaults = {
39
54
  FRA: frTranslation,
40
55
  NL: nlTranslation,
41
56
  NLD: nlTranslation,
57
+ es: esTranslation,
42
58
  };
@@ -23,6 +23,11 @@ export class OtherAmount {
23
23
  otherAmountField.setAttribute("autocomplete", "off");
24
24
  otherAmountField.setAttribute("data-lpignore", "true");
25
25
  otherAmountField.addEventListener("change", (e) => {
26
+ // Formatting only matters when entering a custom amount; skip
27
+ // unrelated change events (e.g. browser autofill firing on the
28
+ // field while a preset amount is selected)
29
+ if (!this._amount.isOtherAmountSelected())
30
+ return;
26
31
  const target = e.target;
27
32
  const amount = target.value;
28
33
  const cleanAmount = ENGrid.cleanAmount(amount);
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import * as cookie from "./cookie";
11
11
  import { EnForm, RememberMeEvents, DonationFrequency } from "./events";
12
+ import { ENGrid } from "./engrid";
12
13
  const tippy = require("tippy.js").default;
13
14
  // localStorage key used to cache the per-device AES-GCM encryption key.
14
15
  // A random secret generated once per device and held in localStorage.
@@ -64,10 +65,10 @@ export class RememberMe {
64
65
  : "before";
65
66
  this.fieldClearLabel = options.fieldClearLabel
66
67
  ? options.fieldClearLabel
67
- : "(clear autofill)";
68
+ : ENGrid.t("rememberMe.clearLabel");
68
69
  this.rememberMeLabel = options.rememberMeLabel
69
70
  ? options.rememberMeLabel
70
- : "Remember Me";
71
+ : ENGrid.t("rememberMe.label");
71
72
  this.fieldData = {};
72
73
  if (this.useRemote()) {
73
74
  this.createIframe(() => {
@@ -232,11 +233,10 @@ export class RememberMe {
232
233
  let rememberMeOptInField = document.getElementById("remember-me-opt-in");
233
234
  if (!rememberMeOptInField) {
234
235
  const rememberMeLabel = this.rememberMeLabel;
235
- const rememberMeInfo = `
236
- Check “${rememberMeLabel}” to complete forms on this device faster.
237
- While your financial information won’t be stored, you should only check this box from a personal device.
238
- Click “${this.fieldClearLabel}” to remove the information from your device at any time.
239
- `;
236
+ const rememberMeInfo = ENGrid.t("rememberMe.tooltip", {
237
+ label: rememberMeLabel,
238
+ clearLabel: this.fieldClearLabel,
239
+ });
240
240
  const rememberMeOptInFieldChecked = this.rememberMeOptIn ? "checked" : "";
241
241
  const rememberMeOptInField = document.createElement("div");
242
242
  rememberMeOptInField.classList.add("en__field", "en__field--checkbox", "en__field--question", "rememberme-wrapper");
@@ -298,7 +298,7 @@ export class RememberMe {
298
298
  "position:absolute;width:1px;height:1px;left:-9999px;";
299
299
  iframe.src = this.remoteUrl;
300
300
  iframe.setAttribute("sandbox", "allow-same-origin allow-scripts");
301
- iframe.setAttribute("title", "Remember Me iframe");
301
+ iframe.setAttribute("title", ENGrid.t("rememberMe.iframeTitle"));
302
302
  this.iframe = iframe;
303
303
  document.body.appendChild(this.iframe);
304
304
  this.iframe.addEventListener("load", () => iframeLoaded(), false);
@@ -4,6 +4,7 @@ export declare class TranslateFields {
4
4
  private options;
5
5
  constructor();
6
6
  private translateFields;
7
+ private applyLanguageLayer;
7
8
  private translateField;
8
9
  private resetTranslatedFields;
9
10
  private setStateField;
@@ -12,7 +12,9 @@ export class TranslateFields {
12
12
  };
13
13
  this.countriesSelect = document.querySelectorAll('select[name="supporter.country"], select[name="transaction.shipcountry"], select[name="supporter.billingCountry"], select[name="transaction.infcountry"]');
14
14
  let options = "EngridTranslate" in window ? window.EngridTranslate : {};
15
- this.options = TranslateOptionsDefaults;
15
+ // Shallow clone: the EngridTranslate merge below concatenates arrays per
16
+ // key and must never mutate the shared TranslateOptionsDefaults.
17
+ this.options = Object.assign({}, TranslateOptionsDefaults);
16
18
  // Don't run this for US-only forms.
17
19
  if (document.querySelector(".en__component--formblock.us-only-form .en__field--country")) {
18
20
  return;
@@ -50,11 +52,18 @@ export class TranslateFields {
50
52
  }
51
53
  }
52
54
  }
55
+ else {
56
+ // No country field on the page: still translate to the page language
57
+ this.applyLanguageLayer();
58
+ }
53
59
  }
54
60
  translateFields(countryName = "supporter.country") {
55
61
  this.resetTranslatedFields();
56
62
  const countryValue = ENGrid.getFieldValue(countryName);
57
- // Translate the State Field
63
+ // Apply the page language as the base translation layer
64
+ this.applyLanguageLayer();
65
+ // Translate the State Field (runs last so country-specific state labels
66
+ // like "Provincia" or "Estado" win over the language layer)
58
67
  this.setStateField(countryValue, this.countryToStateFields[countryName]);
59
68
  if (countryName === "supporter.country") {
60
69
  if (countryValue in this.options) {
@@ -66,6 +75,14 @@ export class TranslateFields {
66
75
  // Translate the "To:"
67
76
  const recipient_block = document.querySelectorAll(".recipient-block");
68
77
  if (!!recipient_block.length) {
78
+ // Capture the original page-builder text once per cycle so
79
+ // resetTranslatedFields() can restore it — a country change never
80
+ // leaves a stale translation behind.
81
+ recipient_block.forEach((elem) => {
82
+ const el = elem;
83
+ if (!el.dataset.original)
84
+ el.dataset.original = el.innerHTML;
85
+ });
69
86
  switch (countryValue) {
70
87
  case "FR":
71
88
  case "FRA":
@@ -82,10 +99,29 @@ export class TranslateFields {
82
99
  case "Netherlands":
83
100
  recipient_block.forEach((elem) => (elem.innerHTML = "Aan:"));
84
101
  break;
102
+ default:
103
+ // No country-specific rule: use the page language string when the
104
+ // language dictionary defines one (e.g. "es" -> "Para:"). English
105
+ // pages keep the page-builder text, already restored above.
106
+ if (ENGrid.getPageLanguage() !== "en" &&
107
+ ENGrid.hasI18nKey("translateFields.recipientTo")) {
108
+ recipient_block.forEach((elem) => (elem.innerHTML = ENGrid.t("translateFields.recipientTo")));
109
+ }
110
+ break;
85
111
  }
86
112
  }
87
113
  }
88
114
  }
115
+ // Apply the translation layer for the current page language (e.g. "es").
116
+ // This is the base layer; country-specific translations override it per field.
117
+ applyLanguageLayer() {
118
+ const language = ENGrid.getPageLanguage();
119
+ if (language in this.options) {
120
+ this.options[language].forEach((field) => {
121
+ this.translateField(field.field, field.translation);
122
+ });
123
+ }
124
+ }
89
125
  translateField(name, translation) {
90
126
  const field = document.querySelector(`[name="${name}"]`);
91
127
  if (field) {
@@ -98,7 +134,20 @@ export class TranslateFields {
98
134
  ? simplecountriesSelect.cloneNode(true)
99
135
  : null;
100
136
  if (field instanceof HTMLInputElement && field.placeholder != "") {
101
- if (!fieldLabel || fieldLabel.innerHTML == field.placeholder) {
137
+ // Translate the placeholder when it mirrors the label (the common
138
+ // case). Compare normalized visible text so template whitespace and
139
+ // required-marker markup don't break the match. Order matters:
140
+ // trim before stripping the marker, or labels like "Name *\n"
141
+ // keep the asterisk.
142
+ const labelText = ((fieldLabel === null || fieldLabel === void 0 ? void 0 : fieldLabel.textContent) || "")
143
+ .replace(/\s+/g, " ")
144
+ .trim()
145
+ .replace(/\s*\*$/, "");
146
+ const placeholderText = field.placeholder
147
+ .replace(/\s+/g, " ")
148
+ .trim()
149
+ .replace(/\s*\*$/, "");
150
+ if (!fieldLabel || labelText === placeholderText) {
102
151
  field.dataset.original = field.placeholder;
103
152
  field.placeholder = translation;
104
153
  }
@@ -153,7 +202,7 @@ export class TranslateFields {
153
202
  case "GB":
154
203
  case "GBR":
155
204
  case "United Kingdom":
156
- this.setStateValues(state, "State/Region", null);
205
+ this.setStateValues(state, ENGrid.t("translateFields.stateRegion"), null);
157
206
  break;
158
207
  case "DE":
159
208
  case "DEU":
@@ -167,8 +216,8 @@ export class TranslateFields {
167
216
  break;
168
217
  case "AU":
169
218
  case "AUS":
170
- this.setStateValues(state, "Province / State", [
171
- { label: "Select", value: "" },
219
+ this.setStateValues(state, ENGrid.t("translateFields.stateGeneric"), [
220
+ { label: ENGrid.t("translateFields.select"), value: "" },
172
221
  { label: "New South Wales", value: "NSW" },
173
222
  { label: "Victoria", value: "VIC" },
174
223
  { label: "Queensland", value: "QLD" },
@@ -180,8 +229,8 @@ export class TranslateFields {
180
229
  ]);
181
230
  break;
182
231
  case "Australia":
183
- this.setStateValues(state, "Province / State", [
184
- { label: "Select", value: "" },
232
+ this.setStateValues(state, ENGrid.t("translateFields.stateGeneric"), [
233
+ { label: ENGrid.t("translateFields.select"), value: "" },
185
234
  { label: "New South Wales", value: "New South Wales" },
186
235
  { label: "Victoria", value: "Victoria" },
187
236
  { label: "Queensland", value: "Queensland" },
@@ -197,8 +246,8 @@ export class TranslateFields {
197
246
  break;
198
247
  case "US":
199
248
  case "USA":
200
- this.setStateValues(state, "State", [
201
- { label: "Select State", value: "" },
249
+ this.setStateValues(state, ENGrid.t("translateFields.state"), [
250
+ { label: ENGrid.t("translateFields.selectState"), value: "" },
202
251
  { label: "Alabama", value: "AL" },
203
252
  { label: "Alaska", value: "AK" },
204
253
  { label: "Arizona", value: "AZ" },
@@ -275,8 +324,8 @@ export class TranslateFields {
275
324
  ]);
276
325
  break;
277
326
  case "United States":
278
- this.setStateValues(state, "State", [
279
- { label: "Select State", value: "" },
327
+ this.setStateValues(state, ENGrid.t("translateFields.state"), [
328
+ { label: ENGrid.t("translateFields.selectState"), value: "" },
280
329
  { label: "Alabama", value: "Alabama" },
281
330
  { label: "Alaska", value: "Alaska" },
282
331
  { label: "Arizona", value: "Arizona" },
@@ -363,8 +412,8 @@ export class TranslateFields {
363
412
  break;
364
413
  case "CA":
365
414
  case "CAN":
366
- this.setStateValues(state, "Province / Territory", [
367
- { label: "Select", value: "" },
415
+ this.setStateValues(state, ENGrid.t("translateFields.provinceTerritory"), [
416
+ { label: ENGrid.t("translateFields.select"), value: "" },
368
417
  { label: "Alberta", value: "AB" },
369
418
  { label: "British Columbia", value: "BC" },
370
419
  { label: "Manitoba", value: "MB" },
@@ -381,8 +430,8 @@ export class TranslateFields {
381
430
  ]);
382
431
  break;
383
432
  case "Canada":
384
- this.setStateValues(state, "Province / Territory", [
385
- { label: "Select", value: "" },
433
+ this.setStateValues(state, ENGrid.t("translateFields.provinceTerritory"), [
434
+ { label: ENGrid.t("translateFields.select"), value: "" },
386
435
  { label: "Alberta", value: "Alberta" },
387
436
  { label: "British Columbia", value: "British Columbia" },
388
437
  { label: "Manitoba", value: "Manitoba" },
@@ -475,7 +524,7 @@ export class TranslateFields {
475
524
  ]);
476
525
  break;
477
526
  default:
478
- this.setStateValues(state, "Province / State", null);
527
+ this.setStateValues(state, ENGrid.t("translateFields.stateGeneric"), null);
479
528
  break;
480
529
  }
481
530
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const AppVersion = "0.27.6";
1
+ export declare const AppVersion = "0.28.1";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const AppVersion = "0.27.6";
1
+ export const AppVersion = "0.28.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4site/engrid-scripts",
3
- "version": "0.27.6",
3
+ "version": "0.28.1",
4
4
  "description": "Scripts for the ENGRID Project",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -25,8 +25,9 @@
25
25
  "strongly-typed-events": "^2.0.9"
26
26
  },
27
27
  "homepage": "https://github.com/4site-interactive-studios/engrid#readme",
28
- "gitHead": "84b8bf12eb84acac7332a55098a2786815912f84",
28
+ "gitHead": "32b7076b094f29226c5f6b90d80c5bbe87062abe",
29
29
  "devDependencies": {
30
- "nodemon": "^2.0.20"
30
+ "nodemon": "^2.0.20",
31
+ "typescript": "^4.9.5"
31
32
  }
32
33
  }