langsys-js-typescript 0.1.0 → 0.2.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/CHANGELOG.md +10 -0
- package/README.md +17 -17
- package/dist/index.d.mts +294 -31
- package/dist/index.d.ts +294 -31
- package/dist/index.js +389 -135
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +379 -135
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 0.2.1 - 2026-06-11
|
|
2
|
+
|
|
3
|
+
### Changed
|
|
4
|
+
|
|
5
|
+
- **Migrated off the deprecated API routes onto the current endpoints.** No public API change — the same `t()`, `<Phrase>`, and `<Translate>` usage now talks to:
|
|
6
|
+
- Reads: `GET /translations?project_id=…&locale=…` (was the deprecated `GET /projects/{id}/translations`).
|
|
7
|
+
- Writes: `POST /translatable-items` with a unified `{ project_id, translatable_items: [{ type: 'phrase' | 'content_block', … }] }` body (was the deprecated `POST /projects/{id}/tokens` and `POST /projects/{id}/content-blocks`).
|
|
8
|
+
- Missing-phrase registration now chunks into batches of 200 to respect the `/translatable-items` per-request cap.
|
|
9
|
+
- Added `LangsysAppAPI.createTranslatableItems()` as the single write entry point for both phrases and content blocks.
|
|
10
|
+
|
|
1
11
|
## 0.1.0 - 2026-05-19
|
|
2
12
|
|
|
3
13
|
Initial public release. Framework-agnostic TypeScript SDK for the Langsys Translation Manager — the foundation that `langsys-js-svelte` (and future framework-specific bindings) build on.
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ This is the base SDK. Framework-specific bindings (`langsys-js-svelte`, and simi
|
|
|
9
9
|
## What's inside
|
|
10
10
|
|
|
11
11
|
- `LangsysApp` singleton — init, locale switching, localized country/currency/locale helpers, preferred-locale detection.
|
|
12
|
-
- `t(
|
|
12
|
+
- `t(phrase, category?, params?)` — the everyday translation function. The phrase is both the lookup key *and* the base-language default. Curly-brace placeholders interpolate from `params` and are compile-time type-checked.
|
|
13
13
|
- `tSignal: Signal<TFunction>` — reactive primitive that re-emits a fresh `t` on every translations/locale change. Framework bindings subscribe to this to drive re-renders.
|
|
14
14
|
- `Translate` class — wraps a DOM element, walks text + translatable attributes, registers a content block with the Translation Manager, re-translates on locale change.
|
|
15
15
|
- `Signal<T>` — tiny subscribe/set/update/get primitive. Compatible with Svelte's store contract by design, so the Svelte binding is nearly trivial.
|
|
@@ -42,13 +42,13 @@ const response = await LangsysApp.init({
|
|
|
42
42
|
|
|
43
43
|
if (!response.status) console.error('Langsys init failed', response.errors);
|
|
44
44
|
|
|
45
|
-
// 3. Translate
|
|
46
|
-
document.querySelector('h1')!.textContent = t('
|
|
47
|
-
document.querySelector('#tagline')!.textContent = t('
|
|
45
|
+
// 3. Translate — t(phrase, category?, params?)
|
|
46
|
+
document.querySelector('h1')!.textContent = t('Welcome to my app', 'Home');
|
|
47
|
+
document.querySelector('#tagline')!.textContent = t('We translate everything', 'Marketing');
|
|
48
48
|
|
|
49
49
|
// 4. Interpolate
|
|
50
50
|
document.querySelector('#greeting')!.textContent =
|
|
51
|
-
t('
|
|
51
|
+
t('Hello, {name}!', 'Greetings', { name: 'Sarah' });
|
|
52
52
|
|
|
53
53
|
// 5. Change locale — all subscribed consumers re-translate
|
|
54
54
|
userLocale.set('es-es');
|
|
@@ -56,27 +56,27 @@ userLocale.set('es-es');
|
|
|
56
56
|
|
|
57
57
|
### How `t()` works
|
|
58
58
|
|
|
59
|
-
The phrase is the lookup key. The first time `t('
|
|
59
|
+
The signature is `t(phrase, category?, params?)` — the phrase comes first, the category is optional, params come last. The phrase is the lookup key. The first time `t('Welcome to my app', 'Home')` runs with a write key, Langsys registers the phrase in the Translation Manager under the "Home" category. On subsequent locale changes, the SDK fetches translations and `t()` returns the translated version. If no translation exists yet, you get back the original phrase — your base language stays visible while translations get filled in.
|
|
60
60
|
|
|
61
61
|
#### Compile-time-checked interpolation
|
|
62
62
|
|
|
63
63
|
Placeholder names are extracted from the phrase string literal via template-literal types, so missing or extra keys in `params` are TypeScript errors at the call site:
|
|
64
64
|
|
|
65
65
|
```ts
|
|
66
|
-
t('
|
|
67
|
-
t('
|
|
68
|
-
t('
|
|
66
|
+
t('Hello, {name}!', 'Greetings', { name: 'Sarah' }); // OK
|
|
67
|
+
t('Hello, {name}!', 'Greetings', {}); // ❌ Property 'name' missing
|
|
68
|
+
t('Hello, {name}!', 'Greetings', { name: 'x', extra: 'y' }); // ❌ Unknown property 'extra'
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
Allowed value types: `string | number | Date | boolean`. Dates serialize to ISO 8601.
|
|
72
72
|
|
|
73
|
-
> Future versions will swap the simple `{name}` runtime for ICU MessageFormat — adding plural / select / date formatting — without changing the public signature. Today's `t('
|
|
73
|
+
> Future versions will swap the simple `{name}` runtime for ICU MessageFormat — adding plural / select / date formatting — without changing the public signature. Today's `t('{count} items', 'Cart', { count })` will evolve to `t('{count, plural, one {# item} other {# items}}', 'Cart', { count })`.
|
|
74
74
|
|
|
75
75
|
#### Categorization disambiguates context
|
|
76
76
|
|
|
77
77
|
```ts
|
|
78
|
-
t('
|
|
79
|
-
t('Home
|
|
78
|
+
t('Home', 'Main Menu'); // 'Inicio' in Spanish
|
|
79
|
+
t('Home', 'Home repairs'); // 'Hogar' in Spanish
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
The same phrase in different categories can have different translations. Without categorization, "Home" would only have one. Langsys's philosophy: *translate once, use everywhere* — categorize when the same phrase legitimately means different things in different parts of the app.
|
|
@@ -101,7 +101,7 @@ import { tSignal } from 'langsys-js-typescript';
|
|
|
101
101
|
|
|
102
102
|
const unsub = tSignal.subscribe((t) => {
|
|
103
103
|
// t is the current translation function — call it for the latest translations
|
|
104
|
-
document.querySelector('h1')!.textContent = t('
|
|
104
|
+
document.querySelector('h1')!.textContent = t('Welcome', 'Home');
|
|
105
105
|
});
|
|
106
106
|
```
|
|
107
107
|
|
|
@@ -122,11 +122,11 @@ export function useT(): TFunction {
|
|
|
122
122
|
// In a component:
|
|
123
123
|
function Header() {
|
|
124
124
|
const t = useT();
|
|
125
|
-
return <h1>{t('
|
|
125
|
+
return <h1>{t('Welcome, {name}!', 'Home', { name: 'Sarah' })}</h1>;
|
|
126
126
|
}
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
The dedicated [`langsys-js-react`](https://github.com/langsys/langsys-js-react) package ships that hook (as `useT`) plus a `<Translate>` component around the DOM `Translate` class. The snippet above is the whole binding if you'd rather not add the dependency.
|
|
130
130
|
|
|
131
131
|
### Svelte
|
|
132
132
|
|
|
@@ -195,7 +195,7 @@ const locale = LangsysApp.detectPreferredLocale(
|
|
|
195
195
|
);
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
-
The matcher tries exact match first (`en-US`), then language-only (`en` matches `en-GB`),
|
|
198
|
+
The matcher tries exact match first (`en-US`), then language-only (`en` matches `en-GB`). When you pass `supported` and none match, it falls back to the user's top preference (normalized); it returns `false` only when no preference can be determined at all.
|
|
199
199
|
|
|
200
200
|
## Localized country / currency / locale lists
|
|
201
201
|
|
|
@@ -261,7 +261,7 @@ import type {
|
|
|
261
261
|
- `LangsysApp.getCurrencies(inLocale?)` / `.getCurrencyName(code, inLocale?)`
|
|
262
262
|
- `LangsysApp.getLocales(inLocale?)` / `.getLocalesFlat(inLocale?)` / `.getLocalesData(inLocale?, force?)`
|
|
263
263
|
- `LangsysApp.getLocaleName(code, short?, inLocale?)` / `.getLocaleNameWithLookup(...)`
|
|
264
|
-
- Top-level: `t`, `tSignal`, `currentlyLoadedLocale`, `sTranslations`, `
|
|
264
|
+
- Top-level: `t`, `tSignal`, `currentlyLoadedLocale`, `sTranslations`, `LangsysAppAPI`, `Translate`, `createSignal`, `getValue`, `persist`, `interpolate`, `Logger`, `logger`, `md5`, `isEmpty`.
|
|
265
265
|
|
|
266
266
|
## License
|
|
267
267
|
|
package/dist/index.d.mts
CHANGED
|
@@ -60,17 +60,37 @@ type iCategories = {
|
|
|
60
60
|
__uncategorized__: iTranslations;
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Minimum interface for a user-locale source.
|
|
65
|
+
*
|
|
66
|
+
* The SDK only ever READS and SUBSCRIBES to this — never writes. That lets
|
|
67
|
+
* each framework adapt its native state into this shape with a small adapter:
|
|
68
|
+
*
|
|
69
|
+
* - Svelte: a `writable<string>` satisfies this directly.
|
|
70
|
+
* - React: wrap `useState` via `useSyncExternalStore` — a 5-line adapter.
|
|
71
|
+
* - Vue: wrap `ref<string>` via `watch` + a `.value` getter.
|
|
72
|
+
* - Angular: a `BehaviorSubject<string>` satisfies this directly.
|
|
73
|
+
* - Vanilla: use the SDK's own `createSignal<string>('en-us')`.
|
|
74
|
+
*
|
|
75
|
+
* Designed as the smallest possible contract so framework wrappers stay thin.
|
|
76
|
+
*/
|
|
77
|
+
interface LocaleSource {
|
|
78
|
+
/** Read the current locale synchronously. */
|
|
79
|
+
get(): string;
|
|
80
|
+
/** Register a callback for locale changes. Returns an unsubscribe function. */
|
|
81
|
+
subscribe(run: (locale: string) => void): () => void;
|
|
82
|
+
}
|
|
63
83
|
interface iLangsysInitConfig {
|
|
64
84
|
/** The ID (UUID) of the project created in Langsys */
|
|
65
85
|
projectid: string;
|
|
66
86
|
/** The API key associated with the project */
|
|
67
87
|
key: string;
|
|
68
88
|
/**
|
|
69
|
-
* A reactive
|
|
70
|
-
*
|
|
71
|
-
* out of the box
|
|
89
|
+
* A reactive source for the user's selected locale. See `LocaleSource`
|
|
90
|
+
* for the minimum contract. The SDK's own `createSignal('en-us')` works
|
|
91
|
+
* out of the box if you don't have a framework-native store of your own.
|
|
72
92
|
*/
|
|
73
|
-
UserLocaleStore:
|
|
93
|
+
UserLocaleStore: LocaleSource;
|
|
74
94
|
/**
|
|
75
95
|
* Base language/locale for the source strings in your code.
|
|
76
96
|
* @default 'en'
|
|
@@ -102,7 +122,7 @@ interface iLangsysInitConfig {
|
|
|
102
122
|
interface iLangsysConfig {
|
|
103
123
|
projectid: string;
|
|
104
124
|
key: string;
|
|
105
|
-
sUserLocale:
|
|
125
|
+
sUserLocale: LocaleSource;
|
|
106
126
|
baseLocale: string;
|
|
107
127
|
debug?: boolean;
|
|
108
128
|
/** Permission level of the API key, set automatically after successful authorization. */
|
|
@@ -122,7 +142,7 @@ interface iLangsysConfig {
|
|
|
122
142
|
*
|
|
123
143
|
* The big trick here is `ExtractParamKeys` — a recursive template-literal
|
|
124
144
|
* type that pulls placeholder names out of a phrase at compile time, so
|
|
125
|
-
* `t('
|
|
145
|
+
* `t('Hello, {name}!', { name: 'Sarah' })` is type-checked: passing
|
|
126
146
|
* an object without `name`, or with a key not in the phrase, is a TS error.
|
|
127
147
|
*/
|
|
128
148
|
/** Allowed value types for interpolation. Mirrors what ICU MessageFormat will accept. */
|
|
@@ -147,9 +167,22 @@ type ParamsFor<S extends string> = {
|
|
|
147
167
|
type TArgs<P extends string> = [ExtractParamKeys<P>] extends [never] ? [] : [params: ParamsFor<P>];
|
|
148
168
|
/** Loose params type — used inside generic stores where the phrase isn't a known literal. */
|
|
149
169
|
type TranslationParams = Record<string, ParamPrimitive>;
|
|
150
|
-
/**
|
|
170
|
+
/**
|
|
171
|
+
* The translation function signature.
|
|
172
|
+
*
|
|
173
|
+
* t('Save') // no category, no params
|
|
174
|
+
* t('Save', 'UI') // categorized
|
|
175
|
+
* t('Hello, {name}!', { name: 'X' }) // no category, with params
|
|
176
|
+
* t('Hello, {name}!', 'Greetings', { name: 'X' }) // category + params
|
|
177
|
+
*
|
|
178
|
+
* Two overloads discriminate on the type at position 2 (string → category,
|
|
179
|
+
* object → params). `category` is required for the second overload —
|
|
180
|
+
* pass any non-empty string. There is no "null category" wire concept:
|
|
181
|
+
* absent category means absent.
|
|
182
|
+
*/
|
|
151
183
|
interface TFunction {
|
|
152
|
-
<P extends string>(
|
|
184
|
+
<P extends string>(phrase: P, ...args: TArgs<P>): string;
|
|
185
|
+
<P extends string>(phrase: P, category: string, ...args: TArgs<P>): string;
|
|
153
186
|
}
|
|
154
187
|
|
|
155
188
|
/**
|
|
@@ -170,10 +203,20 @@ declare class Translations {
|
|
|
170
203
|
private timer;
|
|
171
204
|
private flushScheduled;
|
|
172
205
|
private isFirstClientRun;
|
|
206
|
+
private readyResolve;
|
|
207
|
+
private readyPromise;
|
|
173
208
|
debug: Logger;
|
|
174
209
|
/** Reactive holder for the current translation function. Re-emits on every catalog/locale change. */
|
|
175
210
|
tSignal: Signal<TFunction>;
|
|
176
211
|
constructor(config: iLangsysConfig);
|
|
212
|
+
/**
|
|
213
|
+
* Resolves once the first `getTranslations` call has completed (or
|
|
214
|
+
* immediately if no fetch is configured). Callers that need to read
|
|
215
|
+
* `sTranslations` before issuing a write — e.g. the `Translate` DOM
|
|
216
|
+
* class deciding whether to POST a content block — should await this
|
|
217
|
+
* to avoid acting on an empty cache during the cold-start race.
|
|
218
|
+
*/
|
|
219
|
+
ready(): Promise<void>;
|
|
177
220
|
/** Current translation function. Reads fresh state on every call. */
|
|
178
221
|
get t(): TFunction;
|
|
179
222
|
/** Subscribe to translation snapshot changes. Fires immediately with the current value. */
|
|
@@ -185,6 +228,13 @@ declare class Translations {
|
|
|
185
228
|
* a missing token — the DOM class manages its own lifecycle via content blocks.
|
|
186
229
|
*/
|
|
187
230
|
lookupContent(category: string, customId: string, token: string): string | null;
|
|
231
|
+
/**
|
|
232
|
+
* Raw stored translation for a phrase, without interpolation or
|
|
233
|
+
* missing-token registration. Returns null when no (non-empty) translation
|
|
234
|
+
* is cached. Used by the `Phrase` rich-text handler, which needs the
|
|
235
|
+
* un-interpolated template (it supplies its own markup-token values).
|
|
236
|
+
*/
|
|
237
|
+
lookup(phrase: string, category: string): string | null;
|
|
188
238
|
private buildTFn;
|
|
189
239
|
private missingToken;
|
|
190
240
|
private scheduleTokenFlush;
|
|
@@ -273,7 +323,8 @@ declare class LangsysAppClass {
|
|
|
273
323
|
refresh(): Promise<boolean>;
|
|
274
324
|
/**
|
|
275
325
|
* Initialize Langsys. Configuration must include at least `projectid`,
|
|
276
|
-
* `key`, and `UserLocaleStore` (
|
|
326
|
+
* `key`, and `UserLocaleStore` (anything satisfying `LocaleSource` — see
|
|
327
|
+
* its docstring for framework-specific adapters).
|
|
277
328
|
*/
|
|
278
329
|
init(initConfig: iLangsysInitConfig): Promise<ResponseObject>;
|
|
279
330
|
getDialCodes(inLocale?: string): Promise<iCountryDialCode[]>;
|
|
@@ -329,7 +380,6 @@ interface TranslateOptions {
|
|
|
329
380
|
declare class Translate {
|
|
330
381
|
private element;
|
|
331
382
|
private options;
|
|
332
|
-
private contentClone;
|
|
333
383
|
private tokens;
|
|
334
384
|
private parseComplete;
|
|
335
385
|
private isTokenizing;
|
|
@@ -342,17 +392,115 @@ declare class Translate {
|
|
|
342
392
|
private translateUpdate;
|
|
343
393
|
private tokenizeContent;
|
|
344
394
|
private handleContentBlock;
|
|
345
|
-
private tokenize;
|
|
346
|
-
private tokenizeAttributes;
|
|
347
|
-
private findNode;
|
|
348
395
|
private translate;
|
|
349
396
|
private getTranslation;
|
|
350
397
|
private translateAttributes;
|
|
351
398
|
private translateAttribute;
|
|
352
|
-
private generateCustomId;
|
|
353
|
-
private applyStylesToNode;
|
|
354
399
|
}
|
|
355
400
|
|
|
401
|
+
/** Attribute marker the `Translate` tokenizer uses to skip a `<Phrase>` subtree. */
|
|
402
|
+
declare const PHRASE_MARKER_ATTR = "data-ls-phrase";
|
|
403
|
+
interface PhraseOptions {
|
|
404
|
+
/** Category the phrase registers under (disambiguation for translators). */
|
|
405
|
+
category?: string;
|
|
406
|
+
/** Interpolation params — `{n}` for pluralization, `{name}`, etc. */
|
|
407
|
+
params?: Record<string, unknown>;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* DOM handler for a markup-bearing "rich" phrase (`<Phrase>`).
|
|
411
|
+
*
|
|
412
|
+
* Wraps an element whose inner content is ONE translatable phrase even though
|
|
413
|
+
* it contains inline markup. On mount it encodes the subtree to a single phrase
|
|
414
|
+
* string with neutral `{mNo}`/`{mNc}` markup tokens (the real elements stay in
|
|
415
|
+
* the SDK — see richtext.ts), registers it, and renders the translation by
|
|
416
|
+
* reconstituting the original elements around the translated text. Re-renders
|
|
417
|
+
* on locale change and whenever params change.
|
|
418
|
+
*
|
|
419
|
+
* new Phrase(el, { category: 'ProductCard', params: { n: reviewCount } });
|
|
420
|
+
*/
|
|
421
|
+
declare class Phrase {
|
|
422
|
+
private host;
|
|
423
|
+
private category;
|
|
424
|
+
private params;
|
|
425
|
+
private phrase;
|
|
426
|
+
private slots;
|
|
427
|
+
private unsubscribers;
|
|
428
|
+
private ready;
|
|
429
|
+
constructor(host: HTMLElement, options?: PhraseOptions);
|
|
430
|
+
/** Update interpolation params (e.g. a changed count) and re-render. */
|
|
431
|
+
setParams(params: Record<string, unknown>): void;
|
|
432
|
+
/** Stop reacting to locale/translation changes. Safe to call multiple times. */
|
|
433
|
+
destroy(): void;
|
|
434
|
+
private _init;
|
|
435
|
+
private _render;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Rich-text (markup-bearing) phrase encoding for the `<Phrase>` artifact.
|
|
440
|
+
*
|
|
441
|
+
* The problem: a phrase like `You have {n} <strong>reviews</strong>` must stay
|
|
442
|
+
* ONE phrase (so pluralization sees `{n}` next to `reviews`), but we must NOT
|
|
443
|
+
* send the SPA's real markup — and especially not framework-internal scoped-CSS
|
|
444
|
+
* classes (`svelte-a1b2c3`, Vue scoped hashes) — to the translator. Those
|
|
445
|
+
* classes get mangled, and worse they change on every build, which would make
|
|
446
|
+
* the phrase key drift and silently re-translate everything.
|
|
447
|
+
*
|
|
448
|
+
* The approach: replace each inline element with a pair of neutral MARKUP
|
|
449
|
+
* TOKENS — `{m0o}` ... `{m0c}` (markup, slot 0, open/close). These are ordinary
|
|
450
|
+
* ICU placeholders: valid MessageFormat argument names, so plural/select still
|
|
451
|
+
* parse, and the model treats them like `{n}` — it preserves them and PLACES
|
|
452
|
+
* them around the translated word (which is what fixes reordering languages,
|
|
453
|
+
* e.g. `<span>White</span> House` -> `Casa <span>Blanca</span>`).
|
|
454
|
+
*
|
|
455
|
+
* The real DOM elements never leave the SDK. At render time we resolve the
|
|
456
|
+
* translation, substitute each markup token with a private-use SENTINEL char
|
|
457
|
+
* (transient — never sent anywhere), then reconstitute the DOM by wrapping the
|
|
458
|
+
* sentinel-delimited spans in the ORIGINAL framework-owned elements. Scoped CSS
|
|
459
|
+
* survives because we reuse the real element; the phrase key is stable because
|
|
460
|
+
* the wire form only ever contains `{m0o}`/`{m0c}`, never build-specific hashes.
|
|
461
|
+
*/
|
|
462
|
+
/** A captured inline element: a shallow clone (tag + attributes, no children). */
|
|
463
|
+
interface RichSlot {
|
|
464
|
+
/** Shallow clone of the original element — preserves tag, class, href, etc. */
|
|
465
|
+
template: HTMLElement;
|
|
466
|
+
}
|
|
467
|
+
interface EncodedRichText {
|
|
468
|
+
/**
|
|
469
|
+
* The phrase string with inline elements replaced by `{mNo}`...`{mNc}` markup
|
|
470
|
+
* tokens. This is what gets registered + sent for translation.
|
|
471
|
+
*/
|
|
472
|
+
phrase: string;
|
|
473
|
+
/** Captured inline elements, indexed by markup-token slot number. */
|
|
474
|
+
slots: RichSlot[];
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Walk an element's subtree and produce the encoded phrase + captured slots.
|
|
478
|
+
*
|
|
479
|
+
* Text nodes contribute their (whitespace-collapsed) text. Each child element
|
|
480
|
+
* becomes a markup-token pair wrapping its recursively-encoded contents, and a
|
|
481
|
+
* shallow clone of the element is captured as a slot. Nesting is preserved.
|
|
482
|
+
*/
|
|
483
|
+
declare function encodeRichText(root: HTMLElement): EncodedRichText;
|
|
484
|
+
/**
|
|
485
|
+
* The param values to feed alongside the user's params when resolving a rich
|
|
486
|
+
* phrase. Each markup token maps to its sentinel pair so the resolved string
|
|
487
|
+
* carries machine-findable delimiters for reconstitution.
|
|
488
|
+
*/
|
|
489
|
+
declare function markupTokenValues(slotCount: number): Record<string, string>;
|
|
490
|
+
/**
|
|
491
|
+
* Rebuild DOM nodes from a resolved string containing sentinel-delimited spans.
|
|
492
|
+
*
|
|
493
|
+
* The original (framework-owned) elements are reused as wrappers — so scoped
|
|
494
|
+
* CSS classes / attributes survive — with the translated text placed inside.
|
|
495
|
+
*
|
|
496
|
+
* Robust to model mistakes: if sentinels are unbalanced (a tag dropped or a
|
|
497
|
+
* stray close), we fall back to a single plain-text node (markers stripped) —
|
|
498
|
+
* "lose the markup, keep the meaning" rather than throw.
|
|
499
|
+
*/
|
|
500
|
+
declare function reconstitute(resolved: string, slots: RichSlot[], doc?: Document): Node[];
|
|
501
|
+
/** Remove any sentinel markers from a string, leaving the bare text. */
|
|
502
|
+
declare function stripSentinels(value: string): string;
|
|
503
|
+
|
|
356
504
|
declare class LangsysAppAPIClass {
|
|
357
505
|
private apiurl;
|
|
358
506
|
config: iLangsysConfig;
|
|
@@ -363,6 +511,12 @@ declare class LangsysAppAPIClass {
|
|
|
363
511
|
setBaseUrl(url: string): void;
|
|
364
512
|
validate(config: iLangsysConfig): Promise<ResponseObject>;
|
|
365
513
|
getTranslations(locale: string): Promise<ResponseObject>;
|
|
514
|
+
/**
|
|
515
|
+
* Register phrases and/or content blocks via the current unified endpoint.
|
|
516
|
+
* Each item carries `type: 'phrase' | 'content_block'`. Replaces the
|
|
517
|
+
* deprecated `projects/{id}/tokens` and `projects/{id}/content-blocks` routes.
|
|
518
|
+
*/
|
|
519
|
+
createTranslatableItems(items: Array<Record<string, unknown>>): Promise<ResponseObject>;
|
|
366
520
|
post(path: string, data?: Record<string, unknown>): Promise<ResponseObject>;
|
|
367
521
|
get(path: string, data?: Record<string, unknown>): Promise<ResponseObject>;
|
|
368
522
|
delete(path: string, data?: Record<string, unknown>): Promise<ResponseObject>;
|
|
@@ -372,13 +526,6 @@ declare class LangsysAppAPIClass {
|
|
|
372
526
|
}
|
|
373
527
|
declare const LangsysAppAPI: LangsysAppAPIClass;
|
|
374
528
|
|
|
375
|
-
/**
|
|
376
|
-
* Create a Signal whose value is persisted to localStorage (when available).
|
|
377
|
-
* Falls back to in-memory behavior during SSR or when storage is unavailable
|
|
378
|
-
* or quota-exceeded. Stored value is JSON-encoded.
|
|
379
|
-
*/
|
|
380
|
-
declare function persist<T>(key: string, initial: T): Signal<T>;
|
|
381
|
-
|
|
382
529
|
interface iContentBlock {
|
|
383
530
|
custom_id: string;
|
|
384
531
|
category: string;
|
|
@@ -387,23 +534,139 @@ interface iContentBlock {
|
|
|
387
534
|
tokens: string[];
|
|
388
535
|
}
|
|
389
536
|
|
|
537
|
+
/**
|
|
538
|
+
* Framework-agnostic content-block helpers.
|
|
539
|
+
*
|
|
540
|
+
* The DOM-mutating `Translate` class in `translate.ts` is one consumer of
|
|
541
|
+
* these — it walks a live element, tokenizes it, registers the content
|
|
542
|
+
* block, and mutates the same element in place. That model fits Svelte
|
|
543
|
+
* and vanilla JS, where the SDK genuinely owns the DOM node.
|
|
544
|
+
*
|
|
545
|
+
* For React/Vue/Angular wrappers, mutating the DOM directly is an anti-
|
|
546
|
+
* pattern — those frameworks own their rendered output. Wrappers should
|
|
547
|
+
* import the helpers below to handle the framework-agnostic parts (token
|
|
548
|
+
* discovery, content-block registration, dedup against the local cache)
|
|
549
|
+
* and render translated content through their framework's native means.
|
|
550
|
+
*
|
|
551
|
+
* Wrapper pattern:
|
|
552
|
+
*
|
|
553
|
+
* 1. After mount, grab the rendered DOM via the framework's ref/binding.
|
|
554
|
+
* 2. `const { tokens, content } = tokenizeElement(el);`
|
|
555
|
+
* 3. `const customId = generateCustomId(category, tokens);`
|
|
556
|
+
* 4. `if (!isContentBlockKnown(category, customId)) {`
|
|
557
|
+
* ` await registerContentBlock({ custom_id: customId, category, content, tokens, label });`
|
|
558
|
+
* `}`
|
|
559
|
+
* 5. Subscribe to `sTranslations` + `currentlyLoadedLocale`; on change,
|
|
560
|
+
* look up each token via `Translations.lookupContent(category, customId, token)`
|
|
561
|
+
* and render via your framework's templating.
|
|
562
|
+
*/
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Compute the deterministic content-block id from a `(category, tokens)` pair.
|
|
566
|
+
*
|
|
567
|
+
* JSON-stringifies the tuple so each token is unambiguously delimited
|
|
568
|
+
* (the previous `tokens.join('-')` was collision-prone for tokens
|
|
569
|
+
* containing a hyphen, e.g. `'e-mail'`).
|
|
570
|
+
*
|
|
571
|
+
* Pure function — same inputs always produce the same id. Exported so
|
|
572
|
+
* framework wrappers can compute the id themselves before deciding
|
|
573
|
+
* whether to call `registerContentBlock`.
|
|
574
|
+
*/
|
|
575
|
+
declare function generateCustomId(category: string, tokens: string[]): string;
|
|
576
|
+
/**
|
|
577
|
+
* True iff the local translations cache already has the content block
|
|
578
|
+
* for the given `(category, customId)` — meaning the backend knows about
|
|
579
|
+
* it and there's no need to re-register.
|
|
580
|
+
*
|
|
581
|
+
* The cache is keyed under `'__uncategorized__'` for null/empty categories,
|
|
582
|
+
* matching the convention the server uses in its GET /translations response.
|
|
583
|
+
*/
|
|
584
|
+
declare function isContentBlockKnown(category: string, customId: string): boolean;
|
|
585
|
+
/**
|
|
586
|
+
* POST a content block to the backend, then stamp its custom_id into the
|
|
587
|
+
* local translations cache so subsequent mounts in this session — and
|
|
588
|
+
* subsequent reloads, since the cache is persisted — skip the POST.
|
|
589
|
+
*
|
|
590
|
+
* Mirrors the standalone-phrase path's cache writeback semantics. Respects
|
|
591
|
+
* `key_type === 'write'`: read-only keys silently no-op (resolves with
|
|
592
|
+
* `{ status: true }` so the caller can still render the cached translation).
|
|
593
|
+
*
|
|
594
|
+
* Errors are logged via the SDK's logger and returned as `{ status: false }`
|
|
595
|
+
* — never thrown. Callers can decide whether to surface to UI or swallow.
|
|
596
|
+
*/
|
|
597
|
+
declare function registerContentBlock(contentBlock: iContentBlock): Promise<{
|
|
598
|
+
status: boolean;
|
|
599
|
+
errors?: unknown[];
|
|
600
|
+
}>;
|
|
601
|
+
/**
|
|
602
|
+
* Walk an `HTMLElement`, collecting tokens for translation and producing a
|
|
603
|
+
* cloned-and-stylized snapshot of its outerHTML for the Translation Manager.
|
|
604
|
+
*
|
|
605
|
+
* Tokens harvested:
|
|
606
|
+
* - text-node content (whitespace-normalized, trimmed)
|
|
607
|
+
* - translatable attribute values (`placeholder`, `alt`, `title`, `aria-*`, etc.)
|
|
608
|
+
* - button/input/select value text
|
|
609
|
+
*
|
|
610
|
+
* Skips subtrees marked with `translate="no"`.
|
|
611
|
+
*
|
|
612
|
+
* The returned `content` is a string snapshot — the live element is not
|
|
613
|
+
* mutated. Computed semantic styles (color, font, spacing, etc.) are inlined
|
|
614
|
+
* onto the clone so translators see content styled the way end-users do.
|
|
615
|
+
*
|
|
616
|
+
* Requires that `element` is mounted in the document so `getComputedStyle`
|
|
617
|
+
* can read its style. For SSR / pre-mount callers, the style capture step
|
|
618
|
+
* silently no-ops and the snapshot is plain (without inlined styles).
|
|
619
|
+
*/
|
|
620
|
+
declare function tokenizeElement(element: HTMLElement): {
|
|
621
|
+
tokens: string[];
|
|
622
|
+
content: string;
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Create a Signal whose value is persisted to localStorage (when available).
|
|
627
|
+
* Falls back to in-memory behavior during SSR or when storage is unavailable
|
|
628
|
+
* or quota-exceeded. Stored value is JSON-encoded.
|
|
629
|
+
*/
|
|
630
|
+
declare function persist<T>(key: string, initial: T): Signal<T>;
|
|
631
|
+
|
|
390
632
|
declare const sTranslations: Signal<iCategories>;
|
|
391
633
|
declare const currentlyLoadedLocale: Signal<string>;
|
|
392
|
-
declare const contentBlocks: Signal<iContentBlock[]>;
|
|
393
634
|
|
|
394
635
|
declare const isEmpty: (data: unknown) => boolean;
|
|
395
636
|
declare function md5(inputString: string): string;
|
|
396
637
|
|
|
397
638
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
639
|
+
* Detects ICU MessageFormat syntax in a translation string.
|
|
640
|
+
*
|
|
641
|
+
* - Matches `{var, plural, …}`, `{var, select, …}`, `{var, selectordinal, …}`,
|
|
642
|
+
* `{var, number, …}`, `{var, date, …}`, `{var, time, …}`.
|
|
643
|
+
* - Doesn't match plain interpolation slots like `{name}` or `{count}`.
|
|
644
|
+
*
|
|
645
|
+
* Exported because the Translation Manager UI needs the same detection logic
|
|
646
|
+
* to decide whether to render a plural-slot editor vs. a single text field.
|
|
647
|
+
*/
|
|
648
|
+
declare function isICU(template: string): boolean;
|
|
649
|
+
/**
|
|
650
|
+
* Substitute placeholders in a translated string with values from `params`.
|
|
651
|
+
*
|
|
652
|
+
* Two paths:
|
|
653
|
+
* - ICU MessageFormat (target string contains `{var, plural|select|…, …}`):
|
|
654
|
+
* parsed and formatted via `intl-messageformat`, which knows every
|
|
655
|
+
* target locale's plural rules (Arabic's 6 categories, Russian's 4,
|
|
656
|
+
* etc.) and gender-select branches.
|
|
657
|
+
* - Simple `{name}` interpolation: cheap regex replacement. Unknown
|
|
658
|
+
* placeholders are left untouched so missing data is visible to the
|
|
659
|
+
* developer rather than silently rendering empty strings.
|
|
660
|
+
*
|
|
661
|
+
* Malformed ICU falls through to simple interpolation rather than throwing,
|
|
662
|
+
* so the developer sees a broken-but-visible string instead of a runtime crash.
|
|
663
|
+
* Backend-side validation in langsys-ai is supposed to prevent this — the
|
|
664
|
+
* fall-through is defense in depth.
|
|
401
665
|
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
* in for plurals/select/date formatting.
|
|
666
|
+
* `locale` is required for ICU formatting (drives plural-rule selection).
|
|
667
|
+
* Falls back to `'en'` if absent — simple-interpolation path ignores it.
|
|
405
668
|
*/
|
|
406
|
-
declare function interpolate(template: string, params: Record<string, unknown
|
|
669
|
+
declare function interpolate(template: string, params: Record<string, unknown>, locale?: string): string;
|
|
407
670
|
|
|
408
671
|
interface iProject {
|
|
409
672
|
id: string;
|
|
@@ -425,4 +688,4 @@ interface iProject {
|
|
|
425
688
|
declare const t: TFunction;
|
|
426
689
|
declare const tSignal: Signal<TFunction>;
|
|
427
690
|
|
|
428
|
-
export { type ExtractParamKeys, LangsysApp, LangsysAppAPI, Logger, type ParamPrimitive, type ParamsFor, type Signal, type Subscriber, type TArgs, type TFunction, Translate, type TranslateOptions, type TranslationParams, Translations, type Unsubscriber, type Updater,
|
|
691
|
+
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, 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, persist, reconstitute, registerContentBlock, sTranslations, stripSentinels, t, tSignal, tokenizeElement };
|