langsys-js-typescript 0.6.1 → 0.6.3
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/CHANGELOG.md +41 -0
- package/dist/index.d.mts +45 -5
- package/dist/index.d.ts +45 -5
- package/dist/index.js +39 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +38 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
## 0.6.3 - 2026-08-16
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- **`<select>` option text was harvested twice, diverging `custom_id` from every other Langsys SDK.** A `<select>` special case pushed each `<option>`'s text, and the ordinary text-node walk then pushed it again — `<option>` cannot contain elements, so the walker already covered it. Any content block containing a `<select>` therefore produced `["S","S"]` where `langsys-php` produced `["S"]`, and had a different `custom_id` in the two SDKs since before either was audited. The fixture suite could not see it: those fixtures are synthetic token lists that never touch a DOM.
|
|
6
|
+
|
|
7
|
+
**Migration is automatic and lookup-only**, extending the mechanism 0.6.0 introduced. Three historical id shapes are now tried before registering:
|
|
8
|
+
|
|
9
|
+
| tokens | hash | registered by |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| corrected | corrected | 0.6.3+ (current) |
|
|
12
|
+
| legacy (duplicated) | corrected | 0.6.0 – 0.6.2 |
|
|
13
|
+
| legacy (duplicated) | legacy | before 0.6.0 |
|
|
14
|
+
|
|
15
|
+
Registration always uses the corrected id, so the legacy-keyed population can only shrink. Content without a `<select>` is unaffected — its token list is identical either way, so nothing needlessly rebases. Without this fallback the upgrade would have been silent in the worst direction: a dropdown quietly reverting to source text, with nothing erroring to explain it.
|
|
16
|
+
|
|
17
|
+
- **`legacyTokenizeElement(element)`** — the pre-0.6.3 token list, exported for migration tooling. Lookup-only; never register under an id derived from it. Deprecated on arrival.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Four standard attributes are now harvested: `label`, `aria-description`, `aria-valuetext`, `aria-roledescription`.** `langsys-php` harvests a superset of our list; diffing the two showed nothing JS-only and 16 PHP-only, of which these four carry real user-visible text. `label` is what a user reads in a `<select>` picker (`<option>`, `<optgroup>`, `<track>`) and is invisible to sighted testing that only inspects option text; the three ARIA attributes are read aloud by screen readers, so leaving them untranslated degrades accessibility specifically for the users least able to work around it.
|
|
22
|
+
|
|
23
|
+
The remaining twelve PHP-only attributes are framework conventions (Bootstrap `data-bs-*`, Rails-style `data-confirm`, loading/notification text) and are deliberately **not** mirrored — they're written by server-rendered templates, and a JS app renders those strings through its own components.
|
|
24
|
+
|
|
25
|
+
**Note:** a content block containing one of these attributes now yields a different token list, and therefore a different `custom_id`. Such blocks re-register and their existing translations orphan. The 0.6.0 legacy-id fallback cannot help here — it re-hashes the *same* tokens, and this changes the tokens themselves. Affected population is only blocks that use these four attributes.
|
|
26
|
+
|
|
27
|
+
## 0.6.2 - 2026-08-16
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`data-notrans` is now honored as an alias for `translate="no"`.** `langsys-php` accepts it as an author-facing opt-out, for hosts whose templating strips unknown bare attributes or where `translate` collides with another tool — and it survives into `translatePage()` output. On SSR handoff there is one DOM, so content an author marked "do not translate" was being extracted and registered by whichever SDK walked it. Presence is intent; an explicit `"false"`/`"0"` opts out of the opt-out, compared after trimming. `translate="no"` is now matched case-insensitively too, also mirroring PHP.
|
|
32
|
+
- **`isTranslationExcluded(element)`** — exported alongside `isPhraseMarked`, for wrappers that tokenize through their own templating rather than delegating to the DOM class.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **Phrase-marker values are trimmed before comparison**, so `data-langsys-phrase=" false "` opts out like `="false"` does. This matched `langsys-php` at the time it shipped; auditing the difference showed the gap was drift — their exclusion check gained a trim their phrase check never did — and they normalized on their side, so both SDKs now trim in both places. `isPhraseMarked` is also the method name in both SDKs now, so the correspondence is checkable at a glance.
|
|
37
|
+
|
|
38
|
+
### Notes
|
|
39
|
+
|
|
40
|
+
- Asking `langsys-php` whether to mirror `data-notrans` rather than implementing against their code surfaced a bug on their side: their truthiness test was inverted at both ends — bare `data-notrans` did nothing, while `data-notrans="false"` excluded. Had this been implemented from their source as it stood, it would have faithfully reproduced that. Their fix landed first; this mirrors the corrected semantics.
|
|
41
|
+
|
|
1
42
|
## 0.6.1 - 2026-08-15
|
|
2
43
|
|
|
3
44
|
### Fixed
|
package/dist/index.d.mts
CHANGED
|
@@ -628,14 +628,37 @@ interface iContentBlock {
|
|
|
628
628
|
* additive — `data-langsys-phrase` never appears in DOM our components emit.
|
|
629
629
|
*/
|
|
630
630
|
declare const PHRASE_MARKER_ATTRS: readonly ["data-ls-phrase", "data-langsys-phrase"];
|
|
631
|
+
/**
|
|
632
|
+
* True when an element opts out of translation entirely — it and its subtree
|
|
633
|
+
* are skipped by the tokenizer.
|
|
634
|
+
*
|
|
635
|
+
* Mirrors `langsys-php`'s `HtmlParser::isTranslationExcluded()` exactly:
|
|
636
|
+
* - `translate="no"` — the HTML standard attribute, matched case-insensitively.
|
|
637
|
+
* - `data-notrans` — PHP's author-facing alias, for hosts whose templating
|
|
638
|
+
* strips unknown bare attributes or where `translate` collides with another
|
|
639
|
+
* tool. Presence is intent; an explicit `"false"`/`"0"` opts out of the
|
|
640
|
+
* opt-out, compared after trimming.
|
|
641
|
+
*
|
|
642
|
+
* Honoring PHP's alias matters for the same reason as the phrase marker: on SSR
|
|
643
|
+
* handoff there is ONE DOM, and content an author marked "do not translate"
|
|
644
|
+
* must not be extracted and registered by whichever SDK happens to walk it.
|
|
645
|
+
*
|
|
646
|
+
* Both this and `isPhraseMarked` trim before comparing. They briefly differed
|
|
647
|
+
* — PHP's phrase check predated the trim its exclusion check gained — and the
|
|
648
|
+
* gap was resolved by asking rather than tidying: mirror exactly, flag the
|
|
649
|
+
* wart, let the owner decide. Divergence-by-tidying is the same failure class
|
|
650
|
+
* as divergence-by-oversight, just better intentioned.
|
|
651
|
+
*/
|
|
652
|
+
declare function isTranslationExcluded(element: Element): boolean;
|
|
631
653
|
/**
|
|
632
654
|
* True when an element is marked as a self-managed phrase by either SDK.
|
|
633
655
|
*
|
|
634
656
|
* Presence alone means intent, like any boolean HTML attribute — but an
|
|
635
|
-
* explicit `="false"` or `="0"` opts OUT,
|
|
636
|
-
* `
|
|
637
|
-
* the
|
|
638
|
-
*
|
|
657
|
+
* explicit `="false"` or `="0"` opts OUT, compared after trimming. Mirrors
|
|
658
|
+
* `langsys-php`'s `isPhraseMarked()` exactly (same method name in both SDKs,
|
|
659
|
+
* so the correspondence is checkable at a glance). Without the opt-out we
|
|
660
|
+
* would skip a subtree the author had deliberately un-marked, and it would
|
|
661
|
+
* then be translated by neither SDK.
|
|
639
662
|
*/
|
|
640
663
|
declare function isPhraseMarked(element: Element): boolean;
|
|
641
664
|
/**
|
|
@@ -715,6 +738,23 @@ declare function tokenizeElement(element: HTMLElement): {
|
|
|
715
738
|
tokens: string[];
|
|
716
739
|
content: string;
|
|
717
740
|
};
|
|
741
|
+
/**
|
|
742
|
+
* The token list this element would have produced before 0.6.3, when every
|
|
743
|
+
* `<option>`'s text was harvested twice — once by a `<select>` special case and
|
|
744
|
+
* again by the ordinary text-node walk.
|
|
745
|
+
*
|
|
746
|
+
* **Lookup only.** Content blocks registered by an older SDK are keyed by an id
|
|
747
|
+
* derived from this list, so `Translate` falls back to it when the corrected id
|
|
748
|
+
* misses; without that, every block containing a `<select>` would silently
|
|
749
|
+
* re-key and lose its translations on upgrade. Registration always uses the
|
|
750
|
+
* corrected list, so the legacy-keyed population can only shrink.
|
|
751
|
+
*
|
|
752
|
+
* Skips the computed-style capture that `tokenizeElement` performs — this is a
|
|
753
|
+
* cache-key computation, not a snapshot for translators.
|
|
754
|
+
*
|
|
755
|
+
* @deprecated Migration aid; will be removed once catalogs have been rebased.
|
|
756
|
+
*/
|
|
757
|
+
declare function legacyTokenizeElement(element: HTMLElement): string[];
|
|
718
758
|
|
|
719
759
|
/**
|
|
720
760
|
* Create a Signal whose value is persisted to localStorage (when available).
|
|
@@ -853,4 +893,4 @@ interface iProject {
|
|
|
853
893
|
declare const t: TFunction;
|
|
854
894
|
declare const tSignal: Signal<TFunction>;
|
|
855
895
|
|
|
856
|
-
export { type EncodedRichText, type ExtractParamKeys, LangsysApp, LangsysAppAPI, type LocaleSource, Logger, PHRASE_MARKER_ATTR, PHRASE_MARKER_ATTRS, type ParamPrimitive, type ParamsFor, Phrase, type PhraseOptions, type RichSlot, type Signal, type Subscriber, type TArgs, type TFunction, Translate, type TranslateOptions, type TranslationParams, Translations, type Unsubscriber, type Updater, canonicalizeLocale, createSignal, currentlyLoadedLocale, encodeRichText, findUnusedParamKeys, generateCustomId, generateLegacyCustomId, getValue, type iCategories, type iContentBlock, type iCountry, type iCountryDialCode, type iCountryList, type iCurrency, type iCurrencyList, type iLangsysConfig, type iLangsysInitConfig, type ResponseObject as iLangsysResponse, type iLanguageName, type iLocaleData, type iLocaleDefault, type iLocaleFlat, type iProject, type iTranslations, interpolate, isContentBlockKnown, isEmpty, isICU, isPhraseMarked, logger, markupTokenValues, md5, md5Legacy, normalizeMarkupPlaceholders, persist, reconstitute, registerContentBlock, sTranslations, stripSentinels, t, tSignal, tokenizeElement };
|
|
896
|
+
export { type EncodedRichText, type ExtractParamKeys, LangsysApp, LangsysAppAPI, type LocaleSource, Logger, PHRASE_MARKER_ATTR, PHRASE_MARKER_ATTRS, type ParamPrimitive, type ParamsFor, Phrase, type PhraseOptions, type RichSlot, type Signal, type Subscriber, type TArgs, type TFunction, Translate, type TranslateOptions, type TranslationParams, Translations, type Unsubscriber, type Updater, canonicalizeLocale, createSignal, currentlyLoadedLocale, encodeRichText, findUnusedParamKeys, generateCustomId, generateLegacyCustomId, getValue, type iCategories, type iContentBlock, type iCountry, type iCountryDialCode, type iCountryList, type iCurrency, type iCurrencyList, type iLangsysConfig, type iLangsysInitConfig, type ResponseObject as iLangsysResponse, type iLanguageName, type iLocaleData, type iLocaleDefault, type iLocaleFlat, type iProject, type iTranslations, interpolate, isContentBlockKnown, isEmpty, isICU, isPhraseMarked, isTranslationExcluded, legacyTokenizeElement, logger, markupTokenValues, md5, md5Legacy, normalizeMarkupPlaceholders, persist, reconstitute, registerContentBlock, sTranslations, stripSentinels, t, tSignal, tokenizeElement };
|
package/dist/index.d.ts
CHANGED
|
@@ -628,14 +628,37 @@ interface iContentBlock {
|
|
|
628
628
|
* additive — `data-langsys-phrase` never appears in DOM our components emit.
|
|
629
629
|
*/
|
|
630
630
|
declare const PHRASE_MARKER_ATTRS: readonly ["data-ls-phrase", "data-langsys-phrase"];
|
|
631
|
+
/**
|
|
632
|
+
* True when an element opts out of translation entirely — it and its subtree
|
|
633
|
+
* are skipped by the tokenizer.
|
|
634
|
+
*
|
|
635
|
+
* Mirrors `langsys-php`'s `HtmlParser::isTranslationExcluded()` exactly:
|
|
636
|
+
* - `translate="no"` — the HTML standard attribute, matched case-insensitively.
|
|
637
|
+
* - `data-notrans` — PHP's author-facing alias, for hosts whose templating
|
|
638
|
+
* strips unknown bare attributes or where `translate` collides with another
|
|
639
|
+
* tool. Presence is intent; an explicit `"false"`/`"0"` opts out of the
|
|
640
|
+
* opt-out, compared after trimming.
|
|
641
|
+
*
|
|
642
|
+
* Honoring PHP's alias matters for the same reason as the phrase marker: on SSR
|
|
643
|
+
* handoff there is ONE DOM, and content an author marked "do not translate"
|
|
644
|
+
* must not be extracted and registered by whichever SDK happens to walk it.
|
|
645
|
+
*
|
|
646
|
+
* Both this and `isPhraseMarked` trim before comparing. They briefly differed
|
|
647
|
+
* — PHP's phrase check predated the trim its exclusion check gained — and the
|
|
648
|
+
* gap was resolved by asking rather than tidying: mirror exactly, flag the
|
|
649
|
+
* wart, let the owner decide. Divergence-by-tidying is the same failure class
|
|
650
|
+
* as divergence-by-oversight, just better intentioned.
|
|
651
|
+
*/
|
|
652
|
+
declare function isTranslationExcluded(element: Element): boolean;
|
|
631
653
|
/**
|
|
632
654
|
* True when an element is marked as a self-managed phrase by either SDK.
|
|
633
655
|
*
|
|
634
656
|
* Presence alone means intent, like any boolean HTML attribute — but an
|
|
635
|
-
* explicit `="false"` or `="0"` opts OUT,
|
|
636
|
-
* `
|
|
637
|
-
* the
|
|
638
|
-
*
|
|
657
|
+
* explicit `="false"` or `="0"` opts OUT, compared after trimming. Mirrors
|
|
658
|
+
* `langsys-php`'s `isPhraseMarked()` exactly (same method name in both SDKs,
|
|
659
|
+
* so the correspondence is checkable at a glance). Without the opt-out we
|
|
660
|
+
* would skip a subtree the author had deliberately un-marked, and it would
|
|
661
|
+
* then be translated by neither SDK.
|
|
639
662
|
*/
|
|
640
663
|
declare function isPhraseMarked(element: Element): boolean;
|
|
641
664
|
/**
|
|
@@ -715,6 +738,23 @@ declare function tokenizeElement(element: HTMLElement): {
|
|
|
715
738
|
tokens: string[];
|
|
716
739
|
content: string;
|
|
717
740
|
};
|
|
741
|
+
/**
|
|
742
|
+
* The token list this element would have produced before 0.6.3, when every
|
|
743
|
+
* `<option>`'s text was harvested twice — once by a `<select>` special case and
|
|
744
|
+
* again by the ordinary text-node walk.
|
|
745
|
+
*
|
|
746
|
+
* **Lookup only.** Content blocks registered by an older SDK are keyed by an id
|
|
747
|
+
* derived from this list, so `Translate` falls back to it when the corrected id
|
|
748
|
+
* misses; without that, every block containing a `<select>` would silently
|
|
749
|
+
* re-key and lose its translations on upgrade. Registration always uses the
|
|
750
|
+
* corrected list, so the legacy-keyed population can only shrink.
|
|
751
|
+
*
|
|
752
|
+
* Skips the computed-style capture that `tokenizeElement` performs — this is a
|
|
753
|
+
* cache-key computation, not a snapshot for translators.
|
|
754
|
+
*
|
|
755
|
+
* @deprecated Migration aid; will be removed once catalogs have been rebased.
|
|
756
|
+
*/
|
|
757
|
+
declare function legacyTokenizeElement(element: HTMLElement): string[];
|
|
718
758
|
|
|
719
759
|
/**
|
|
720
760
|
* Create a Signal whose value is persisted to localStorage (when available).
|
|
@@ -853,4 +893,4 @@ interface iProject {
|
|
|
853
893
|
declare const t: TFunction;
|
|
854
894
|
declare const tSignal: Signal<TFunction>;
|
|
855
895
|
|
|
856
|
-
export { type EncodedRichText, type ExtractParamKeys, LangsysApp, LangsysAppAPI, type LocaleSource, Logger, PHRASE_MARKER_ATTR, PHRASE_MARKER_ATTRS, type ParamPrimitive, type ParamsFor, Phrase, type PhraseOptions, type RichSlot, type Signal, type Subscriber, type TArgs, type TFunction, Translate, type TranslateOptions, type TranslationParams, Translations, type Unsubscriber, type Updater, canonicalizeLocale, createSignal, currentlyLoadedLocale, encodeRichText, findUnusedParamKeys, generateCustomId, generateLegacyCustomId, getValue, type iCategories, type iContentBlock, type iCountry, type iCountryDialCode, type iCountryList, type iCurrency, type iCurrencyList, type iLangsysConfig, type iLangsysInitConfig, type ResponseObject as iLangsysResponse, type iLanguageName, type iLocaleData, type iLocaleDefault, type iLocaleFlat, type iProject, type iTranslations, interpolate, isContentBlockKnown, isEmpty, isICU, isPhraseMarked, logger, markupTokenValues, md5, md5Legacy, normalizeMarkupPlaceholders, persist, reconstitute, registerContentBlock, sTranslations, stripSentinels, t, tSignal, tokenizeElement };
|
|
896
|
+
export { type EncodedRichText, type ExtractParamKeys, LangsysApp, LangsysAppAPI, type LocaleSource, Logger, PHRASE_MARKER_ATTR, PHRASE_MARKER_ATTRS, type ParamPrimitive, type ParamsFor, Phrase, type PhraseOptions, type RichSlot, type Signal, type Subscriber, type TArgs, type TFunction, Translate, type TranslateOptions, type TranslationParams, Translations, type Unsubscriber, type Updater, canonicalizeLocale, createSignal, currentlyLoadedLocale, encodeRichText, findUnusedParamKeys, generateCustomId, generateLegacyCustomId, getValue, type iCategories, type iContentBlock, type iCountry, type iCountryDialCode, type iCountryList, type iCurrency, type iCurrencyList, type iLangsysConfig, type iLangsysInitConfig, type ResponseObject as iLangsysResponse, type iLanguageName, type iLocaleData, type iLocaleDefault, type iLocaleFlat, type iProject, type iTranslations, interpolate, isContentBlockKnown, isEmpty, isICU, isPhraseMarked, isTranslationExcluded, legacyTokenizeElement, logger, markupTokenValues, md5, md5Legacy, normalizeMarkupPlaceholders, persist, reconstitute, registerContentBlock, sTranslations, stripSentinels, t, tSignal, tokenizeElement };
|
package/dist/index.js
CHANGED
|
@@ -1076,8 +1076,14 @@ var TRANSLATABLE_ATTRIBUTES = [
|
|
|
1076
1076
|
"placeholder",
|
|
1077
1077
|
"alt",
|
|
1078
1078
|
"title",
|
|
1079
|
+
"label",
|
|
1080
|
+
// <option>, <optgroup>, <track> — the text a user reads in the picker
|
|
1079
1081
|
"aria-label",
|
|
1080
1082
|
"aria-placeholder",
|
|
1083
|
+
"aria-description",
|
|
1084
|
+
"aria-valuetext",
|
|
1085
|
+
// the spoken value of a slider/meter
|
|
1086
|
+
"aria-roledescription",
|
|
1081
1087
|
"data-error",
|
|
1082
1088
|
"data-error-message",
|
|
1083
1089
|
"data-validation-message",
|
|
@@ -1086,10 +1092,16 @@ var TRANSLATABLE_ATTRIBUTES = [
|
|
|
1086
1092
|
"data-pattern-message"
|
|
1087
1093
|
];
|
|
1088
1094
|
var PHRASE_MARKER_ATTRS = ["data-ls-phrase", "data-langsys-phrase"];
|
|
1095
|
+
function isTranslationExcluded(element) {
|
|
1096
|
+
if ((element.getAttribute("translate") ?? "").toLowerCase() === "no") return true;
|
|
1097
|
+
if (!element.hasAttribute("data-notrans")) return false;
|
|
1098
|
+
const value = (element.getAttribute("data-notrans") ?? "").trim().toLowerCase();
|
|
1099
|
+
return value !== "false" && value !== "0";
|
|
1100
|
+
}
|
|
1089
1101
|
function isPhraseMarked(element) {
|
|
1090
1102
|
return PHRASE_MARKER_ATTRS.some((attr) => {
|
|
1091
1103
|
if (!element.hasAttribute(attr)) return false;
|
|
1092
|
-
const value = (element.getAttribute(attr) ?? "").toLowerCase();
|
|
1104
|
+
const value = (element.getAttribute(attr) ?? "").trim().toLowerCase();
|
|
1093
1105
|
return value !== "false" && value !== "0";
|
|
1094
1106
|
});
|
|
1095
1107
|
}
|
|
@@ -1188,9 +1200,15 @@ async function registerContentBlock(contentBlock) {
|
|
|
1188
1200
|
function tokenizeElement(element) {
|
|
1189
1201
|
const tokens = [];
|
|
1190
1202
|
const clone = element.cloneNode(true);
|
|
1191
|
-
_walkForTokens(element, Array.from(clone.childNodes), tokens, []);
|
|
1203
|
+
_walkForTokens(element, Array.from(clone.childNodes), tokens, [], false, true);
|
|
1192
1204
|
return { tokens, content: normalizeMarkupPlaceholders(clone.outerHTML) };
|
|
1193
1205
|
}
|
|
1206
|
+
function legacyTokenizeElement(element) {
|
|
1207
|
+
const tokens = [];
|
|
1208
|
+
const clone = element.cloneNode(true);
|
|
1209
|
+
_walkForTokens(element, Array.from(clone.childNodes), tokens, [], true, false);
|
|
1210
|
+
return tokens;
|
|
1211
|
+
}
|
|
1194
1212
|
function _writeKnownContentBlockToCache(category, customId) {
|
|
1195
1213
|
const lookupCat = category || "__uncategorized__";
|
|
1196
1214
|
sTranslations.update((current) => {
|
|
@@ -1204,18 +1222,18 @@ function _writeKnownContentBlockToCache(category, customId) {
|
|
|
1204
1222
|
return { ...current };
|
|
1205
1223
|
});
|
|
1206
1224
|
}
|
|
1207
|
-
function _walkForTokens(liveRoot, cloneNodes, tokens, indices) {
|
|
1225
|
+
function _walkForTokens(liveRoot, cloneNodes, tokens, indices, duplicateSelectOptions, applyStyles) {
|
|
1208
1226
|
cloneNodes.forEach((node, index) => {
|
|
1209
1227
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
1210
1228
|
const el = node;
|
|
1211
|
-
if (el
|
|
1229
|
+
if (isTranslationExcluded(el)) return;
|
|
1212
1230
|
if (isPhraseMarked(el)) return;
|
|
1213
1231
|
}
|
|
1214
|
-
if (node.hasChildNodes()) {
|
|
1232
|
+
if (applyStyles && node.hasChildNodes()) {
|
|
1215
1233
|
_applyStylesToClone(liveRoot, node, [...indices, index]);
|
|
1216
1234
|
}
|
|
1217
1235
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
1218
|
-
_tokenizeAttributes(node, tokens);
|
|
1236
|
+
_tokenizeAttributes(node, tokens, duplicateSelectOptions);
|
|
1219
1237
|
}
|
|
1220
1238
|
const contentToken = node.nodeValue?.replace(/\s+/g, " ").trim();
|
|
1221
1239
|
if (node.nodeType === Node.TEXT_NODE && contentToken) {
|
|
@@ -1223,10 +1241,10 @@ function _walkForTokens(liveRoot, cloneNodes, tokens, indices) {
|
|
|
1223
1241
|
return;
|
|
1224
1242
|
}
|
|
1225
1243
|
if (!node.hasChildNodes()) return;
|
|
1226
|
-
_walkForTokens(liveRoot, Array.from(node.childNodes), tokens, [...indices, index]);
|
|
1244
|
+
_walkForTokens(liveRoot, Array.from(node.childNodes), tokens, [...indices, index], duplicateSelectOptions, applyStyles);
|
|
1227
1245
|
});
|
|
1228
1246
|
}
|
|
1229
|
-
function _tokenizeAttributes(element, tokens) {
|
|
1247
|
+
function _tokenizeAttributes(element, tokens, duplicateSelectOptions) {
|
|
1230
1248
|
const tagName = element.tagName.toLowerCase();
|
|
1231
1249
|
if (tagName === "img") {
|
|
1232
1250
|
const img = element;
|
|
@@ -1247,7 +1265,7 @@ function _tokenizeAttributes(element, tokens) {
|
|
|
1247
1265
|
if (value) tokens.push(normalizeMarkupPlaceholders(value));
|
|
1248
1266
|
}
|
|
1249
1267
|
}
|
|
1250
|
-
if (tagName === "select") {
|
|
1268
|
+
if (duplicateSelectOptions && tagName === "select") {
|
|
1251
1269
|
const options = element.querySelectorAll("option");
|
|
1252
1270
|
options.forEach((option) => {
|
|
1253
1271
|
const optionText = option.textContent?.trim();
|
|
@@ -1373,9 +1391,15 @@ var Translate = class {
|
|
|
1373
1391
|
return;
|
|
1374
1392
|
}
|
|
1375
1393
|
if (derivedId) {
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
|
|
1394
|
+
const legacyTokens = legacyTokenizeElement(this.element);
|
|
1395
|
+
const candidates = [
|
|
1396
|
+
generateCustomId(contentBlock.category, legacyTokens),
|
|
1397
|
+
generateLegacyCustomId(contentBlock.category, legacyTokens)
|
|
1398
|
+
];
|
|
1399
|
+
for (const candidate of candidates) {
|
|
1400
|
+
if (candidate === this.custom_id) continue;
|
|
1401
|
+
if (!isContentBlockKnown(contentBlock.category, candidate)) continue;
|
|
1402
|
+
this.custom_id = candidate;
|
|
1379
1403
|
if (this.tokens.length > 1 && this.element) {
|
|
1380
1404
|
this.translate(Array.from(this.element.childNodes));
|
|
1381
1405
|
this.lastTranslatedLocale = currentlyLoadedLocale.get();
|
|
@@ -1397,7 +1421,7 @@ var Translate = class {
|
|
|
1397
1421
|
nodes.forEach((node) => {
|
|
1398
1422
|
if (node?.nodeType === Node.ELEMENT_NODE) {
|
|
1399
1423
|
const element = node;
|
|
1400
|
-
if (element
|
|
1424
|
+
if (isTranslationExcluded(element)) return;
|
|
1401
1425
|
if (isPhraseMarked(element)) return;
|
|
1402
1426
|
this.translateAttributes(node);
|
|
1403
1427
|
}
|
|
@@ -1660,6 +1684,8 @@ exports.isContentBlockKnown = isContentBlockKnown;
|
|
|
1660
1684
|
exports.isEmpty = isEmpty;
|
|
1661
1685
|
exports.isICU = isICU;
|
|
1662
1686
|
exports.isPhraseMarked = isPhraseMarked;
|
|
1687
|
+
exports.isTranslationExcluded = isTranslationExcluded;
|
|
1688
|
+
exports.legacyTokenizeElement = legacyTokenizeElement;
|
|
1663
1689
|
exports.logger = logger;
|
|
1664
1690
|
exports.markupTokenValues = markupTokenValues;
|
|
1665
1691
|
exports.md5 = md5;
|