langsys-js-typescript 0.4.1 → 0.4.2
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 +7 -0
- package/README.md +10 -0
- package/dist/index.d.mts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +55 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +55 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## 0.4.2 - 2026-07-08
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **Debug-mode diagnostic for the `{key}`-in-markup mistake.** Writing `{name}` instead of `%name%` inside `<Translate>`/`<Phrase>` content fails silently — the framework compiler substitutes it before the SDK sees the text, so the base locale still renders correctly and only translated locales break. The SDK can't observe the original braces, but the fingerprint is unmistakable: `params` supplied with no matching placeholder in the captured content. When `debug` is on, that now emits a warning naming the offending key and the exact fix. The check re-runs only when the params key-SET changes, so a ticking counter doesn't spam the console, and it's silent in production.
|
|
6
|
+
- **`findUnusedParamKeys(texts, params)`** — the underlying detector, exported for wrappers that want the same check around their own rendering. Treats both `{key}` and ICU `{key, plural, …}` as used.
|
|
7
|
+
|
|
1
8
|
## 0.4.1 - 2026-07-08
|
|
2
9
|
|
|
3
10
|
### Added
|
package/README.md
CHANGED
|
@@ -195,6 +195,16 @@ In compiled frameworks, bare `{key}` written in markup never reaches the SDK —
|
|
|
195
195
|
|
|
196
196
|
Both spellings are equivalent: `%key%` is normalized to canonical `{key}` at tokenization, so the Translation Manager, the wire, and translators only ever see `{key}`, and existing `{key}` content in vanilla HTML keeps working unchanged. Keys must be identifiers (`[A-Za-z_][A-Za-z0-9_]*`), so literal `%` signs in prose ("20% off") are never touched. `t()` phrases are plain JS strings with no compiler in the way — they stay `{key}`-only.
|
|
197
197
|
|
|
198
|
+
Writing `{key}` in markup fails *silently* — the base locale still looks right, so the mistake surfaces only once someone switches language. With `debug: true`, the SDK catches it for you: if you pass `params` whose keys have no matching placeholder in the captured content, it warns and names the fix.
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
Langsys Warning <Translate> received params with no matching placeholder in its
|
|
202
|
+
content: %count%. If you wrote {count} in markup, the framework compiler
|
|
203
|
+
(Svelte/JSX) substituted it before Langsys saw the text — write %count% instead.
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The check runs whenever the params key-set changes, so a ticking `count` won't spam the console, and it's silent in production.
|
|
207
|
+
|
|
198
208
|
## Server-Side Rendering
|
|
199
209
|
|
|
200
210
|
Pre-fetch translations on the server and seed them through `initialTranslations` to skip the duplicate client fetch on hydration:
|
package/dist/index.d.mts
CHANGED
|
@@ -400,6 +400,8 @@ declare class Translate {
|
|
|
400
400
|
private lastTranslatedLocale;
|
|
401
401
|
private custom_id;
|
|
402
402
|
private unsubscribeLocale;
|
|
403
|
+
/** Sorted params key-set already checked, so value-only updates don't re-warn. */
|
|
404
|
+
private checkedParamKeys;
|
|
403
405
|
constructor(element: HTMLElement, options?: TranslateOptions);
|
|
404
406
|
/** Update interpolation params (e.g. a changed count) and re-render. Mirrors `Phrase.setParams`. */
|
|
405
407
|
setParams(params: Record<string, ParamPrimitive> | undefined): void;
|
|
@@ -410,6 +412,12 @@ declare class Translate {
|
|
|
410
412
|
private handleContentBlock;
|
|
411
413
|
private translate;
|
|
412
414
|
private getTranslation;
|
|
415
|
+
/**
|
|
416
|
+
* Debug-time check that every supplied param has a placeholder to land in.
|
|
417
|
+
* Re-runs only when the key SET changes — a changing `count` value must not
|
|
418
|
+
* re-warn on every render.
|
|
419
|
+
*/
|
|
420
|
+
private checkParams;
|
|
413
421
|
/**
|
|
414
422
|
* Interpolate `{name}`-style params into a resolved text, matching `t()`:
|
|
415
423
|
* unknown keys fall through untouched, and the untranslated fallback is
|
|
@@ -448,12 +456,20 @@ declare class Phrase {
|
|
|
448
456
|
private slots;
|
|
449
457
|
private unsubscribers;
|
|
450
458
|
private ready;
|
|
459
|
+
/** Sorted params key-set already checked, so value-only updates don't re-warn. */
|
|
460
|
+
private checkedParamKeys;
|
|
451
461
|
constructor(host: HTMLElement, options?: PhraseOptions);
|
|
452
462
|
/** Update interpolation params (e.g. a changed count) and re-render. */
|
|
453
463
|
setParams(params: Record<string, unknown>): void;
|
|
454
464
|
/** Stop reacting to locale/translation changes. Safe to call multiple times. */
|
|
455
465
|
destroy(): void;
|
|
456
466
|
private _init;
|
|
467
|
+
/**
|
|
468
|
+
* Debug-time check that every supplied param has a placeholder to land in.
|
|
469
|
+
* Re-runs only when the key SET changes — a changing `n` value must not
|
|
470
|
+
* re-warn on every render.
|
|
471
|
+
*/
|
|
472
|
+
private _checkParams;
|
|
457
473
|
private _render;
|
|
458
474
|
}
|
|
459
475
|
|
|
@@ -686,6 +702,14 @@ declare function isICU(template: string): boolean;
|
|
|
686
702
|
* with no compiler collision and stay `{name}`-only.
|
|
687
703
|
*/
|
|
688
704
|
declare function normalizeMarkupPlaceholders(text: string): string;
|
|
705
|
+
/**
|
|
706
|
+
* Params keys that have NO matching placeholder in `texts`.
|
|
707
|
+
*
|
|
708
|
+
* Texts are expected in canonical form (post-`normalizeMarkupPlaceholders`),
|
|
709
|
+
* so a key counts as used when it appears as `{key}` or as the argument of an
|
|
710
|
+
* ICU slot (`{key, plural, …}`). Surrounding whitespace is tolerated.
|
|
711
|
+
*/
|
|
712
|
+
declare function findUnusedParamKeys(texts: string[], params: Record<string, unknown> | undefined): string[];
|
|
689
713
|
/**
|
|
690
714
|
* Substitute placeholders in a translated string with values from `params`.
|
|
691
715
|
*
|
|
@@ -754,4 +778,4 @@ interface iProject {
|
|
|
754
778
|
declare const t: TFunction;
|
|
755
779
|
declare const tSignal: Signal<TFunction>;
|
|
756
780
|
|
|
757
|
-
export { type EncodedRichText, type ExtractParamKeys, LangsysApp, LangsysAppAPI, type LocaleSource, Logger, PHRASE_MARKER_ATTR, 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, generateCustomId, 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, logger, markupTokenValues, md5, normalizeMarkupPlaceholders, persist, reconstitute, registerContentBlock, sTranslations, stripSentinels, t, tSignal, tokenizeElement };
|
|
781
|
+
export { type EncodedRichText, type ExtractParamKeys, LangsysApp, LangsysAppAPI, type LocaleSource, Logger, PHRASE_MARKER_ATTR, 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, 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, logger, markupTokenValues, md5, normalizeMarkupPlaceholders, persist, reconstitute, registerContentBlock, sTranslations, stripSentinels, t, tSignal, tokenizeElement };
|
package/dist/index.d.ts
CHANGED
|
@@ -400,6 +400,8 @@ declare class Translate {
|
|
|
400
400
|
private lastTranslatedLocale;
|
|
401
401
|
private custom_id;
|
|
402
402
|
private unsubscribeLocale;
|
|
403
|
+
/** Sorted params key-set already checked, so value-only updates don't re-warn. */
|
|
404
|
+
private checkedParamKeys;
|
|
403
405
|
constructor(element: HTMLElement, options?: TranslateOptions);
|
|
404
406
|
/** Update interpolation params (e.g. a changed count) and re-render. Mirrors `Phrase.setParams`. */
|
|
405
407
|
setParams(params: Record<string, ParamPrimitive> | undefined): void;
|
|
@@ -410,6 +412,12 @@ declare class Translate {
|
|
|
410
412
|
private handleContentBlock;
|
|
411
413
|
private translate;
|
|
412
414
|
private getTranslation;
|
|
415
|
+
/**
|
|
416
|
+
* Debug-time check that every supplied param has a placeholder to land in.
|
|
417
|
+
* Re-runs only when the key SET changes — a changing `count` value must not
|
|
418
|
+
* re-warn on every render.
|
|
419
|
+
*/
|
|
420
|
+
private checkParams;
|
|
413
421
|
/**
|
|
414
422
|
* Interpolate `{name}`-style params into a resolved text, matching `t()`:
|
|
415
423
|
* unknown keys fall through untouched, and the untranslated fallback is
|
|
@@ -448,12 +456,20 @@ declare class Phrase {
|
|
|
448
456
|
private slots;
|
|
449
457
|
private unsubscribers;
|
|
450
458
|
private ready;
|
|
459
|
+
/** Sorted params key-set already checked, so value-only updates don't re-warn. */
|
|
460
|
+
private checkedParamKeys;
|
|
451
461
|
constructor(host: HTMLElement, options?: PhraseOptions);
|
|
452
462
|
/** Update interpolation params (e.g. a changed count) and re-render. */
|
|
453
463
|
setParams(params: Record<string, unknown>): void;
|
|
454
464
|
/** Stop reacting to locale/translation changes. Safe to call multiple times. */
|
|
455
465
|
destroy(): void;
|
|
456
466
|
private _init;
|
|
467
|
+
/**
|
|
468
|
+
* Debug-time check that every supplied param has a placeholder to land in.
|
|
469
|
+
* Re-runs only when the key SET changes — a changing `n` value must not
|
|
470
|
+
* re-warn on every render.
|
|
471
|
+
*/
|
|
472
|
+
private _checkParams;
|
|
457
473
|
private _render;
|
|
458
474
|
}
|
|
459
475
|
|
|
@@ -686,6 +702,14 @@ declare function isICU(template: string): boolean;
|
|
|
686
702
|
* with no compiler collision and stay `{name}`-only.
|
|
687
703
|
*/
|
|
688
704
|
declare function normalizeMarkupPlaceholders(text: string): string;
|
|
705
|
+
/**
|
|
706
|
+
* Params keys that have NO matching placeholder in `texts`.
|
|
707
|
+
*
|
|
708
|
+
* Texts are expected in canonical form (post-`normalizeMarkupPlaceholders`),
|
|
709
|
+
* so a key counts as used when it appears as `{key}` or as the argument of an
|
|
710
|
+
* ICU slot (`{key, plural, …}`). Surrounding whitespace is tolerated.
|
|
711
|
+
*/
|
|
712
|
+
declare function findUnusedParamKeys(texts: string[], params: Record<string, unknown> | undefined): string[];
|
|
689
713
|
/**
|
|
690
714
|
* Substitute placeholders in a translated string with values from `params`.
|
|
691
715
|
*
|
|
@@ -754,4 +778,4 @@ interface iProject {
|
|
|
754
778
|
declare const t: TFunction;
|
|
755
779
|
declare const tSignal: Signal<TFunction>;
|
|
756
780
|
|
|
757
|
-
export { type EncodedRichText, type ExtractParamKeys, LangsysApp, LangsysAppAPI, type LocaleSource, Logger, PHRASE_MARKER_ATTR, 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, generateCustomId, 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, logger, markupTokenValues, md5, normalizeMarkupPlaceholders, persist, reconstitute, registerContentBlock, sTranslations, stripSentinels, t, tSignal, tokenizeElement };
|
|
781
|
+
export { type EncodedRichText, type ExtractParamKeys, LangsysApp, LangsysAppAPI, type LocaleSource, Logger, PHRASE_MARKER_ATTR, 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, 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, logger, markupTokenValues, md5, normalizeMarkupPlaceholders, persist, reconstitute, registerContentBlock, sTranslations, stripSentinels, t, tSignal, tokenizeElement };
|
package/dist/index.js
CHANGED
|
@@ -268,6 +268,25 @@ var ICU_PATTERN = /\{[^{}]+,\s*(plural|select|selectordinal|number|date|time)\s*
|
|
|
268
268
|
function normalizeMarkupPlaceholders(text) {
|
|
269
269
|
return text.replace(/%([A-Za-z_][A-Za-z0-9_]*)%/g, "{$1}");
|
|
270
270
|
}
|
|
271
|
+
function escapeForRegExp(key) {
|
|
272
|
+
return key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
273
|
+
}
|
|
274
|
+
function findUnusedParamKeys(texts, params) {
|
|
275
|
+
if (!params) return [];
|
|
276
|
+
const keys = Object.keys(params);
|
|
277
|
+
if (!keys.length || !texts.length) return keys;
|
|
278
|
+
const haystack = texts.join("\0");
|
|
279
|
+
return keys.filter((key) => !new RegExp(`\\{\\s*${escapeForRegExp(key)}\\s*[,}]`).test(haystack));
|
|
280
|
+
}
|
|
281
|
+
function warnUnmatchedParams(source, texts, params, context) {
|
|
282
|
+
if (!logger.debugEnabled) return;
|
|
283
|
+
const unused = findUnusedParamKeys(texts, params);
|
|
284
|
+
if (!unused.length) return;
|
|
285
|
+
const keyList = unused.map((key) => `%${key}%`).join(", ");
|
|
286
|
+
logger.warn(
|
|
287
|
+
`${source} received params with no matching placeholder in its content: ${keyList}. If you wrote {${unused[0]}} in markup, the framework compiler (Svelte/JSX) substituted it before Langsys saw the text \u2014 write %${unused[0]}% instead.` + (context ? ` [${context}]` : "")
|
|
288
|
+
);
|
|
289
|
+
}
|
|
271
290
|
function interpolate(template, params, locale) {
|
|
272
291
|
if (isICU(template)) {
|
|
273
292
|
try {
|
|
@@ -1248,6 +1267,8 @@ var Translate = class {
|
|
|
1248
1267
|
this.isTokenizing = false;
|
|
1249
1268
|
this.lastTranslatedLocale = "";
|
|
1250
1269
|
this.unsubscribeLocale = null;
|
|
1270
|
+
/** Sorted params key-set already checked, so value-only updates don't re-warn. */
|
|
1271
|
+
this.checkedParamKeys = null;
|
|
1251
1272
|
this.element = element;
|
|
1252
1273
|
this.options = { ...options };
|
|
1253
1274
|
this.custom_id = options.custom_id || "";
|
|
@@ -1259,6 +1280,7 @@ var Translate = class {
|
|
|
1259
1280
|
/** Update interpolation params (e.g. a changed count) and re-render. Mirrors `Phrase.setParams`. */
|
|
1260
1281
|
setParams(params) {
|
|
1261
1282
|
this.options.params = params;
|
|
1283
|
+
this.checkParams();
|
|
1262
1284
|
if (!this.parseComplete) return;
|
|
1263
1285
|
this.lastTranslatedLocale = "";
|
|
1264
1286
|
const locale = currentlyLoadedLocale.get();
|
|
@@ -1292,6 +1314,7 @@ var Translate = class {
|
|
|
1292
1314
|
this.isTokenizing = true;
|
|
1293
1315
|
const { tokens, content } = tokenizeElement(this.element);
|
|
1294
1316
|
this.tokens = tokens;
|
|
1317
|
+
this.checkParams();
|
|
1295
1318
|
const { category = "" } = this.options;
|
|
1296
1319
|
if (this.tokens.length === 1) {
|
|
1297
1320
|
this.element.innerText = this.applyParams(LangsysApp.Translations.t(this.tokens[0], category));
|
|
@@ -1365,6 +1388,19 @@ var Translate = class {
|
|
|
1365
1388
|
const { category = "" } = this.options;
|
|
1366
1389
|
return LangsysApp.Translations.lookupContent(category, this.custom_id, token);
|
|
1367
1390
|
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Debug-time check that every supplied param has a placeholder to land in.
|
|
1393
|
+
* Re-runs only when the key SET changes — a changing `count` value must not
|
|
1394
|
+
* re-warn on every render.
|
|
1395
|
+
*/
|
|
1396
|
+
checkParams() {
|
|
1397
|
+
if (!this.tokens.length) return;
|
|
1398
|
+
const { params, category = "" } = this.options;
|
|
1399
|
+
const signature = params ? Object.keys(params).sort().join(",") : "";
|
|
1400
|
+
if (signature === this.checkedParamKeys) return;
|
|
1401
|
+
this.checkedParamKeys = signature;
|
|
1402
|
+
warnUnmatchedParams("<Translate>", this.tokens, params, category || void 0);
|
|
1403
|
+
}
|
|
1368
1404
|
/**
|
|
1369
1405
|
* Interpolate `{name}`-style params into a resolved text, matching `t()`:
|
|
1370
1406
|
* unknown keys fall through untouched, and the untranslated fallback is
|
|
@@ -1499,6 +1535,8 @@ var Phrase = class {
|
|
|
1499
1535
|
this.slots = [];
|
|
1500
1536
|
this.unsubscribers = [];
|
|
1501
1537
|
this.ready = false;
|
|
1538
|
+
/** Sorted params key-set already checked, so value-only updates don't re-warn. */
|
|
1539
|
+
this.checkedParamKeys = null;
|
|
1502
1540
|
this.host = host;
|
|
1503
1541
|
this.category = options.category ?? "";
|
|
1504
1542
|
this.params = options.params ?? {};
|
|
@@ -1512,7 +1550,9 @@ var Phrase = class {
|
|
|
1512
1550
|
/** Update interpolation params (e.g. a changed count) and re-render. */
|
|
1513
1551
|
setParams(params) {
|
|
1514
1552
|
this.params = params ?? {};
|
|
1515
|
-
|
|
1553
|
+
this._checkParams();
|
|
1554
|
+
if (!this.ready) return;
|
|
1555
|
+
this._render();
|
|
1516
1556
|
}
|
|
1517
1557
|
/** Stop reacting to locale/translation changes. Safe to call multiple times. */
|
|
1518
1558
|
destroy() {
|
|
@@ -1527,11 +1567,24 @@ var Phrase = class {
|
|
|
1527
1567
|
const { phrase, slots } = encodeRichText(this.host);
|
|
1528
1568
|
this.phrase = phrase;
|
|
1529
1569
|
this.slots = slots;
|
|
1570
|
+
this._checkParams();
|
|
1530
1571
|
await LangsysApp.Translations.ready();
|
|
1531
1572
|
LangsysApp.Translations.t(this.phrase, this.category);
|
|
1532
1573
|
this.ready = true;
|
|
1533
1574
|
this._render();
|
|
1534
1575
|
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Debug-time check that every supplied param has a placeholder to land in.
|
|
1578
|
+
* Re-runs only when the key SET changes — a changing `n` value must not
|
|
1579
|
+
* re-warn on every render.
|
|
1580
|
+
*/
|
|
1581
|
+
_checkParams() {
|
|
1582
|
+
if (!this.phrase) return;
|
|
1583
|
+
const signature = Object.keys(this.params).sort().join(",");
|
|
1584
|
+
if (signature === this.checkedParamKeys) return;
|
|
1585
|
+
this.checkedParamKeys = signature;
|
|
1586
|
+
warnUnmatchedParams("<Phrase>", [this.phrase], this.params, this.category || void 0);
|
|
1587
|
+
}
|
|
1535
1588
|
_render() {
|
|
1536
1589
|
if (!this.phrase) return;
|
|
1537
1590
|
const raw = LangsysApp.Translations.lookup(this.phrase, this.category) ?? this.phrase;
|
|
@@ -1559,6 +1612,7 @@ exports.canonicalizeLocale = canonicalizeLocale;
|
|
|
1559
1612
|
exports.createSignal = createSignal;
|
|
1560
1613
|
exports.currentlyLoadedLocale = currentlyLoadedLocale;
|
|
1561
1614
|
exports.encodeRichText = encodeRichText;
|
|
1615
|
+
exports.findUnusedParamKeys = findUnusedParamKeys;
|
|
1562
1616
|
exports.generateCustomId = generateCustomId;
|
|
1563
1617
|
exports.getValue = getValue;
|
|
1564
1618
|
exports.interpolate = interpolate;
|