langsys-js-typescript 0.6.4 → 0.7.0
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 +110 -0
- package/README.md +469 -29
- package/dist/index.d.mts +1641 -372
- package/dist/index.d.ts +1641 -372
- package/dist/index.js +5400 -1240
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5349 -1241
- package/dist/index.mjs.map +1 -1
- package/dist/langsys.browser.global.js +3 -0
- package/dist/langsys.browser.mjs +3 -0
- package/dist/pure-DkFXdgVa.d.mts +933 -0
- package/dist/pure-DkFXdgVa.d.ts +933 -0
- package/dist/pure.d.mts +1 -0
- package/dist/pure.d.ts +1 -0
- package/dist/pure.js +1196 -0
- package/dist/pure.js.map +1 -0
- package/dist/pure.mjs +1145 -0
- package/dist/pure.mjs.map +1 -0
- package/package.json +18 -4
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { q as ParamPrimitive, j as LegacyKeyFile, K as TFunction, c as CatalogSnapshot, y as ServerMessage, a7 as iContentBlock } from './pure-DkFXdgVa.mjs';
|
|
2
|
+
export { C as CONTENT_BLOCK_MARKER_ATTR, a as CONTENT_BLOCK_MARKER_ATTRS, b as CONTENT_BLOCK_MARKER_ATTR_LEGACY, D as DEFAULT_SERVER_MESSAGE_CATEGORY, d as ExpressionShape, e as ExtractParamKeys, I as InterpolateOptions, L as LEGACY_FORMATS, f as LegacyConversion, g as LegacyEntryPoint, h as LegacyFormat, i as LegacyFormatError, k as LegacyKeyHit, l as LegacyKeyProblem, m as LegacyKeys, n as NamedExpression, P as PHRASE_MARKER_ATTR, o as PHRASE_MARKER_ATTRS, p as PHRASE_MARKER_ATTR_LEGACY, r as ParamsFor, R as RESOLVED_MARKER_ATTR, s as RESOLVED_MARKER_ATTRS, t as RESOLVED_MARKER_ATTR_LEGACY, u as ResolveServerMessagesOptions, S as SNAPSHOT_FORMAT, w as SNAPSHOT_VERSION, x as SUPPORTED_LEGACY_FORMATS, z as ServerMessagePieces, B as SnapshotCatalog, F as SnapshotCategory, G as SnapshotError, H as SnapshotPayload, J as SnapshotRefusal, T as TArgs, O as TranslationParams, V as VAR_PARAM_ATTR, Q as buildSnapshot, W as canonicalSnapshotJson, X as canonicalizeLocale, Y as convertLegacyCall, Z as convertLegacyPluralForms, _ as convertLegacyValue, $ as createLegacyKeys, a0 as defaultedArguments, a1 as derivePlaceholderNames, a3 as fillTemplate, a4 as findUnusedParamKeys, a5 as generateCustomId, a6 as generateLegacyCustomId, a8 as interpolate, a9 as isEmpty, aa as isICU, ab as isTranslationExcluded, ad as md5, ae as md5Legacy, af as normalizeMarkupPlaceholders, ah as parseSnapshot, ai as resolveServerMessages, aj as snakeCase, ak as snapshotChecksum, am as templateMarkers, an as toServerMessage } from './pure-DkFXdgVa.mjs';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Tiny framework-agnostic reactive value holder — our replacement for a
|
|
3
6
|
* Svelte `Writable<T>`. The shape is deliberately similar so the mental
|
|
@@ -60,6 +63,289 @@ type iCategories = {
|
|
|
60
63
|
__uncategorized__: iTranslations;
|
|
61
64
|
};
|
|
62
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Content blocks without a DOM (spec SRV-1, MARK-1, TOK-6): identify, translate
|
|
68
|
+
* and register a `<Translate>` block from a tree, so a framework rendering on a
|
|
69
|
+
* server, or rendering on the client before mount, emits the translated block
|
|
70
|
+
* and its stamped id instead of source text.
|
|
71
|
+
*
|
|
72
|
+
* A tree is the block's content, the children of the host element, as plain
|
|
73
|
+
* nodes. It is walked by the same code as the DOM (`tokenizeNodes`,
|
|
74
|
+
* `findSingleTextNode`, `isExcisedFromUnit`) through a DOM-shaped view of it, so
|
|
75
|
+
* a tree and the DOM it mirrors yield the same tokens, shape and id.
|
|
76
|
+
*
|
|
77
|
+
* `renderBlock` is synchronous and pure over the active catalog: it registers
|
|
78
|
+
* nothing and its output is the same on a server and in the browser for the
|
|
79
|
+
* same catalog, so a client's first render matches what the server sent. A
|
|
80
|
+
* binding registers with `registerBlock`, which touches no DOM.
|
|
81
|
+
*/
|
|
82
|
+
/**
|
|
83
|
+
* A node of a block's content. Attribute values are strings; `true` is an attribute with no value.
|
|
84
|
+
*
|
|
85
|
+
* `params` on a nested host's element (a phrase host or a content-block declaration) are
|
|
86
|
+
* that unit's own: it renders with them in place of the block's, so a binding names each
|
|
87
|
+
* unit's values on their own (VAR-2) and a nested phrase keys as it does standing alone.
|
|
88
|
+
* A nested host without them renders with the block's.
|
|
89
|
+
*/
|
|
90
|
+
type BlockNode = {
|
|
91
|
+
text: string;
|
|
92
|
+
} | {
|
|
93
|
+
comment: string;
|
|
94
|
+
} | {
|
|
95
|
+
tag: string;
|
|
96
|
+
attrs?: Readonly<Record<string, string | true>>;
|
|
97
|
+
children?: readonly BlockNode[];
|
|
98
|
+
params?: Readonly<Record<string, ParamPrimitive>>;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* A node of a rendered block. Every element names, as `source`, the pre-order
|
|
102
|
+
* index of the input element it renders (elements only, counted across the
|
|
103
|
+
* whole input from 0), so a binding can re-attach what the tree cannot carry —
|
|
104
|
+
* handlers, refs, keys — even where a translation reorders inline markup.
|
|
105
|
+
*/
|
|
106
|
+
type RenderedNode = {
|
|
107
|
+
text: string;
|
|
108
|
+
} | {
|
|
109
|
+
comment: string;
|
|
110
|
+
} | {
|
|
111
|
+
tag: string;
|
|
112
|
+
attrs: Record<string, string | true>;
|
|
113
|
+
children: RenderedNode[];
|
|
114
|
+
source: number;
|
|
115
|
+
};
|
|
116
|
+
type BlockShape = 'phrase' | 'block' | 'empty';
|
|
117
|
+
interface BlockOptions {
|
|
118
|
+
category?: string;
|
|
119
|
+
params?: Record<string, ParamPrimitive>;
|
|
120
|
+
/** A label for the block in the Translation Manager, as `Translate`'s option. */
|
|
121
|
+
label?: string;
|
|
122
|
+
/**
|
|
123
|
+
* The app's own `custom_id` for the block: rendered under it, and registered
|
|
124
|
+
* under it with its content when the catalog does not hold it, as
|
|
125
|
+
* `Translate`'s `custom_id` option does.
|
|
126
|
+
*/
|
|
127
|
+
id?: string;
|
|
128
|
+
/**
|
|
129
|
+
* A `custom_id` to ADOPT: a renderer already rendered this block from the
|
|
130
|
+
* catalog under it (a stamp carried with the resolved marker, MARK-3). Rendered
|
|
131
|
+
* under it, never registered.
|
|
132
|
+
*/
|
|
133
|
+
customId?: string;
|
|
134
|
+
/**
|
|
135
|
+
* `registerBlock` only: the host element on the client, whose ancestors decide
|
|
136
|
+
* GATE-10. The walk starts at its parent, since the host itself carries the
|
|
137
|
+
* resolved marker when it was rendered from the catalog.
|
|
138
|
+
*/
|
|
139
|
+
host?: Element;
|
|
140
|
+
/** `false` registers nothing, nested hosts included (VAR-7); `renderBlock` ignores it. Default `true`. */
|
|
141
|
+
register?: boolean;
|
|
142
|
+
}
|
|
143
|
+
interface RenderedBlock {
|
|
144
|
+
shape: BlockShape;
|
|
145
|
+
/** The id the block renders under: derived, a historical id the catalog holds (CID-3), or adopted. Null when empty. */
|
|
146
|
+
customId: string | null;
|
|
147
|
+
nodes: RenderedNode[];
|
|
148
|
+
/**
|
|
149
|
+
* The attributes to set on the host: the block's id (MARK-1), and, when the
|
|
150
|
+
* rendered text is a translation (a locale other than the base), the resolved
|
|
151
|
+
* marker with that locale (GATE-10), so a DOM reader on the client never files
|
|
152
|
+
* the served translation as source. Empty for an empty block.
|
|
153
|
+
*/
|
|
154
|
+
hostAttrs: Record<string, string>;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* A block as a request scope's seed carries it (SRV-4), for a client that cannot
|
|
158
|
+
* recover the block's source from its DOM: `registerBlock` accepts it in place
|
|
159
|
+
* of the source nodes.
|
|
160
|
+
*/
|
|
161
|
+
interface SeededBlock {
|
|
162
|
+
customId: string;
|
|
163
|
+
category: string;
|
|
164
|
+
tokens: string[];
|
|
165
|
+
shape: 'phrase' | 'block';
|
|
166
|
+
/**
|
|
167
|
+
* The block's content as `registerBlock` over its nodes would send it, for a
|
|
168
|
+
* client that registers it from the seed. Absent wherever no client does: a
|
|
169
|
+
* phrase-shaped entry, a block the scope's catalog holds, a `collected` one and
|
|
170
|
+
* one marked `register: false`. A seeded block without it registers nothing.
|
|
171
|
+
*/
|
|
172
|
+
content?: string;
|
|
173
|
+
/**
|
|
174
|
+
* The server's scope sends this block itself at `close()` (the `server`
|
|
175
|
+
* strategy, or `auto` for a short list), so a client never registers it:
|
|
176
|
+
* not from the seed, and not from the `Translate` over its host.
|
|
177
|
+
*/
|
|
178
|
+
collected?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* `false` when a registration over the block's nodes sends nothing, so
|
|
181
|
+
* `registerBlock(seededBlock)` never registers it: a block rendered with
|
|
182
|
+
* `register: false` (VAR-7), at any depth, whose tokens hold values from
|
|
183
|
+
* variables, carried only so a client can re-render the served text; one under
|
|
184
|
+
* an adopted id; one inside a resolved scope, and the blocks nested in either;
|
|
185
|
+
* and a unit of value markers only.
|
|
186
|
+
*/
|
|
187
|
+
register?: false;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* A phrase a request scope's render missed, as its seed carries it: `t()`, a
|
|
191
|
+
* `<Phrase>` host or a single-token block. `collected` as on `SeededBlock`: the
|
|
192
|
+
* scope sends it itself at `close()`, and the client never records it again.
|
|
193
|
+
*/
|
|
194
|
+
interface SeededPhrase {
|
|
195
|
+
category: string;
|
|
196
|
+
phrase: string;
|
|
197
|
+
collected?: boolean;
|
|
198
|
+
}
|
|
199
|
+
/** A request scope's hydration seed (SRV-4): `LangsysApp.seedCatalog(seed.catalog, seed.locale, seed)` on the client. */
|
|
200
|
+
interface RequestSeed {
|
|
201
|
+
locale: string;
|
|
202
|
+
catalog: iCategories;
|
|
203
|
+
blocks: Record<string, SeededBlock>;
|
|
204
|
+
phrases: SeededPhrase[];
|
|
205
|
+
}
|
|
206
|
+
/** The tokens and shape of a block's content, exactly as `tokenizeElement` and `Translate` decide them for the same markup. */
|
|
207
|
+
declare function tokenizeTree(nodes: readonly BlockNode[]): {
|
|
208
|
+
tokens: string[];
|
|
209
|
+
shape: BlockShape;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Render a block from its content: the translated tree, the id it renders
|
|
213
|
+
* under and the marker to stamp. Synchronous, registers nothing, and reads only
|
|
214
|
+
* the active catalog (a request scope's, or the page's), so the same input and
|
|
215
|
+
* catalog render the same bytes on a server and in the browser.
|
|
216
|
+
*
|
|
217
|
+
* Nested marked hosts are units of their own (MARK-4), rendered in place: a
|
|
218
|
+
* phrase host as its rich phrase, a declared block as its own block (stamped
|
|
219
|
+
* with its id), a stamped one under its id. `translate="no"`, `data-notrans`
|
|
220
|
+
* and code elements are returned as given.
|
|
221
|
+
*/
|
|
222
|
+
declare function renderBlock(nodes: readonly BlockNode[], options?: BlockOptions): RenderedBlock;
|
|
223
|
+
/** The notices already given, one per reason. */
|
|
224
|
+
/**
|
|
225
|
+
* Report, as a debug notice once per process per reason, that a binding could
|
|
226
|
+
* not render a block through `renderBlock`. What happens next depends on the
|
|
227
|
+
* reason, and the notice says which:
|
|
228
|
+
*
|
|
229
|
+
* - a framework component in its subtree, a raw-HTML prop, a raw-text element,
|
|
230
|
+
* a suspense placeholder: SRV-1's one sanctioned fallback, not a fault. The
|
|
231
|
+
* block is served as source with no resolved marker and its app-supplied id
|
|
232
|
+
* stamped where it has one, and the client renders and registers it after
|
|
233
|
+
* hydration.
|
|
234
|
+
* - `variable`: the block holds a value from a variable the binding cannot name
|
|
235
|
+
* without its build-time transform, so it renders from the catalog and
|
|
236
|
+
* registers nothing, on the server or the client (VAR-7).
|
|
237
|
+
*
|
|
238
|
+
* The notice is the core's (bindings write no console output of their own). One
|
|
239
|
+
* raised before `init()` is held until debug is known (see `debugNotice`).
|
|
240
|
+
*/
|
|
241
|
+
declare function warnUnrenderedBlock(reason: string): void;
|
|
242
|
+
/**
|
|
243
|
+
* Register a block for discovery, with no DOM: what `Translate` registers when
|
|
244
|
+
* it mounts, for a framework that owns the host's text nodes and renders
|
|
245
|
+
* through `renderBlock`. A single-token unit records its phrase miss as `t()`
|
|
246
|
+
* does; a block the catalog lacks, under no historical id, registers with its
|
|
247
|
+
* content; an adopted id (`customId`) registers nothing; nested phrase hosts and
|
|
248
|
+
* declared or unresolved-stamped blocks register as their own units. Inside a
|
|
249
|
+
* request scope it is held for `close()`. Fire-and-forget, and never throws
|
|
250
|
+
* synchronously: the returned promise settles once the registration has run.
|
|
251
|
+
*
|
|
252
|
+
* On a server outside every request scope it resolves at once and registers
|
|
253
|
+
* nothing: there is no scope to hold the block for `close()`, and no `init()` to
|
|
254
|
+
* wait for when the client is the one that initialises, so waiting would keep the
|
|
255
|
+
* tree alive for the life of the process. A debug notice says so, once.
|
|
256
|
+
*
|
|
257
|
+
* Under `requireCredentials` an unconfigured client fails loudly here as on the
|
|
258
|
+
* DOM classes (WIRE-4): on the page the promise rejects with
|
|
259
|
+
* `LangsysConfigurationError`, checked again once `init()` has answered, so a
|
|
260
|
+
* block registered before `init()` refuses rejects too. A `register: false` block
|
|
261
|
+
* is a read and never rejects. A binding that leaves the
|
|
262
|
+
* promise unhandled surfaces it. On a server it never rejects, since an unhandled
|
|
263
|
+
* rejection would end a Node process rather than one request, and the request's
|
|
264
|
+
* `t()` throws already.
|
|
265
|
+
*
|
|
266
|
+
* With `host`, the host is the binding's from this call on: an enclosing
|
|
267
|
+
* `Translate` leaves it alone rather than taking it over as an unmanaged marked host.
|
|
268
|
+
*
|
|
269
|
+
* One-shot: it records what the block is now. A binding calls it again on each
|
|
270
|
+
* change `t` goes through — a locale switch, a new catalog, a navigation — as
|
|
271
|
+
* `Translate` re-enters its lookup (HINT-13).
|
|
272
|
+
*
|
|
273
|
+
* The flows it serves: on a server the outermost host renders and registers,
|
|
274
|
+
* and nested declared hosts are covered through its tree. On the client after
|
|
275
|
+
* hydration, the blocks a scope rendered are registered from its seed, one
|
|
276
|
+
* `registerBlock(seededBlock)` per `seed().blocks` entry after `init()`, since
|
|
277
|
+
* the DOM already holds translated text. That loop is for bindings that mount
|
|
278
|
+
* the DOM classes; a binding that renders trees registers each block from its
|
|
279
|
+
* nodes as it renders. Either way a block or phrase the seed marks `collected`
|
|
280
|
+
* is never registered, since the server's scope sends it itself, and neither is a
|
|
281
|
+
* block the seed marks `register: false`, whose tokens hold per-user values.
|
|
282
|
+
*/
|
|
283
|
+
declare function registerBlock(input: readonly BlockNode[] | SeededBlock, options?: BlockOptions): Promise<void>;
|
|
284
|
+
/**
|
|
285
|
+
* A host element's content as a tree: its child nodes as `BlockNode`s, a
|
|
286
|
+
* `<template>`'s content included. For a binding that holds a DOM, on a server
|
|
287
|
+
* or on the client, so no binding walks a DOM of its own to tokenize.
|
|
288
|
+
*/
|
|
289
|
+
declare function blockNodesOf(element: {
|
|
290
|
+
childNodes: ArrayLike<Node>;
|
|
291
|
+
}): BlockNode[];
|
|
292
|
+
/**
|
|
293
|
+
* Write a rendered block into the host's EXISTING nodes: each text node's value,
|
|
294
|
+
* each translatable attribute and each nested host's unit markers (its id and
|
|
295
|
+
* resolved marker, MARK-1, GATE-10), in place, never replacing a node, so a
|
|
296
|
+
* framework's references to them stay live; and, given a whole `RenderedBlock`,
|
|
297
|
+
* its `hostAttrs` on the host. Only when the DOM has the rendered tree's
|
|
298
|
+
* structure: the same nodes in the same places, each element where its `source`
|
|
299
|
+
* says. Otherwise nothing is touched and it returns
|
|
300
|
+
* `{ applied: false, reason: 'structure' }`: a translation that reorders inline
|
|
301
|
+
* markup cannot be applied to nodes a framework owns.
|
|
302
|
+
*
|
|
303
|
+
* With `self`, `element` is the tree's root rather than its parent: the tree was
|
|
304
|
+
* rendered from `[{ tag, attrs, children: blockNodesOf(element) }]`, as a
|
|
305
|
+
* standalone phrase host is (`data-ls-phrase` on the root, at `source` 0), and
|
|
306
|
+
* `element` is matched against its one root node.
|
|
307
|
+
*/
|
|
308
|
+
declare function applyRendered(element: Element, rendered: RenderedBlock | readonly RenderedNode[], options?: {
|
|
309
|
+
self?: boolean;
|
|
310
|
+
}): {
|
|
311
|
+
applied: boolean;
|
|
312
|
+
reason?: 'structure';
|
|
313
|
+
};
|
|
314
|
+
/** Serialise a block's content as HTML, for the content snapshot a block registers with. Not identity. */
|
|
315
|
+
declare function serializeTree(nodes: readonly BlockNode[]): string;
|
|
316
|
+
|
|
317
|
+
interface HttpResponse {
|
|
318
|
+
status: number;
|
|
319
|
+
statusText: string;
|
|
320
|
+
url: string;
|
|
321
|
+
data: string;
|
|
322
|
+
}
|
|
323
|
+
interface ResponseObject {
|
|
324
|
+
status: boolean;
|
|
325
|
+
page?: number;
|
|
326
|
+
page_count?: number;
|
|
327
|
+
records_per_page?: number;
|
|
328
|
+
data?: object[] | object;
|
|
329
|
+
errors?: string[];
|
|
330
|
+
http?: HttpResponse;
|
|
331
|
+
/**
|
|
332
|
+
* Server-computed write capability for this session, returned by
|
|
333
|
+
* `authorize-project` and by every catalog fetch. Envelope-level on
|
|
334
|
+
* `/translations`: the `data` member there IS the category map, so a stray
|
|
335
|
+
* non-category key inside it would be read as a category.
|
|
336
|
+
*/
|
|
337
|
+
write_enabled?: boolean;
|
|
338
|
+
/**
|
|
339
|
+
* Project setting: record a miss only while the loaded locale is the base locale.
|
|
340
|
+
* A top-level sibling of `data` on both catalog routes, and inside `data` on
|
|
341
|
+
* `authorize-project`. Absent means off.
|
|
342
|
+
*/
|
|
343
|
+
discovery_base_locale_only?: boolean;
|
|
344
|
+
/** Catalog size counters returned alongside `data` on `/translations`. */
|
|
345
|
+
words?: number;
|
|
346
|
+
untranslated_words?: number;
|
|
347
|
+
}
|
|
348
|
+
|
|
63
349
|
/**
|
|
64
350
|
* Minimum interface for a user-locale source.
|
|
65
351
|
*
|
|
@@ -80,6 +366,33 @@ interface LocaleSource {
|
|
|
80
366
|
/** Register a callback for locale changes. Returns an unsubscribe function. */
|
|
81
367
|
subscribe(run: (locale: string) => void): () => void;
|
|
82
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* A short-lived write grant (HS256 JWT) minted by the host app's own backend,
|
|
371
|
+
* sent as `X-Write-Grant`. A valid grant makes the session write-enabled
|
|
372
|
+
* server-side, so `write_enabled` comes back true and phrase discovery
|
|
373
|
+
* registers normally — used by login-walled apps our discovery renderer
|
|
374
|
+
* cannot reach.
|
|
375
|
+
*
|
|
376
|
+
* If the token only exists after login, still configure a PROVIDER at `init`
|
|
377
|
+
* that returns `null` until then, rather than leaving `writeGrant` unset and
|
|
378
|
+
* calling `setWriteGrant()` later. An unset grant tells the SDK that no grant
|
|
379
|
+
* can ever arrive, so when authorization comes back read-only it releases the
|
|
380
|
+
* queued misses — which for a login-walled app means they are only reported to
|
|
381
|
+
* a renderer that cannot log in, which is the situation grants exist to solve.
|
|
382
|
+
* A provider returning `null` keeps the queue held until the grant appears.
|
|
383
|
+
*
|
|
384
|
+
* Prefer the FUNCTION form. Grants have a ~5 minute TTL while `init()` runs
|
|
385
|
+
* once and an app session can last hours, so a bare string is expired minutes
|
|
386
|
+
* in and every later write silently degrades the session to read-only. The
|
|
387
|
+
* provider is called immediately before each request, so the host app refreshes
|
|
388
|
+
* by simply returning a fresh token. `null`/`undefined` means "no grant yet"
|
|
389
|
+
* (e.g. before login) and is sent as no header at all.
|
|
390
|
+
*
|
|
391
|
+
* The resolved token is never cached — not on the config singleton, not on the
|
|
392
|
+
* API client — which is what keeps it safe in a long-lived SSR process shared
|
|
393
|
+
* across users.
|
|
394
|
+
*/
|
|
395
|
+
type WriteGrant = string | (() => string | null | undefined | Promise<string | null | undefined>);
|
|
83
396
|
interface iLangsysInitConfig {
|
|
84
397
|
/** The ID (UUID) of the project created in Langsys */
|
|
85
398
|
projectid: string;
|
|
@@ -96,6 +409,31 @@ interface iLangsysInitConfig {
|
|
|
96
409
|
* @default 'en'
|
|
97
410
|
*/
|
|
98
411
|
baseLocale?: string;
|
|
412
|
+
/**
|
|
413
|
+
* Point the SDK at a different API host — a local instance, a staging
|
|
414
|
+
* host, or a stateful test double.
|
|
415
|
+
*
|
|
416
|
+
* Prefer this over `LangsysAppAPI.setBaseUrl()`. The setter has to run
|
|
417
|
+
* BEFORE `init()`, and running it after leaves the SDK permanently inert:
|
|
418
|
+
* `init()` has already authorized against the default host and failed, and
|
|
419
|
+
* the locale subscription is installed closed over that failed result, so
|
|
420
|
+
* it no-ops for the life of the app. Nothing throws, and no later locale
|
|
421
|
+
* change or URL fix recovers it. This option is applied inside `init()`
|
|
422
|
+
* before authorization, in the same call that consumes it, so there is no
|
|
423
|
+
* ordering left to get wrong.
|
|
424
|
+
*
|
|
425
|
+
* @default 'https://api.langsys.dev/api'
|
|
426
|
+
*/
|
|
427
|
+
apiUrl?: string;
|
|
428
|
+
/**
|
|
429
|
+
* Fail loudly when credentials are missing (WIRE-4). Off by default: `init()` without a
|
|
430
|
+
* project id, API key or locale store leaves the client unconfigured, and `t()` renders
|
|
431
|
+
* source text with its params filled. On, `t()` throws `LangsysConfigurationError`
|
|
432
|
+
* instead, so a build or test run with missing credentials stops at the first render.
|
|
433
|
+
*
|
|
434
|
+
* @default false
|
|
435
|
+
*/
|
|
436
|
+
requireCredentials?: boolean;
|
|
99
437
|
/**
|
|
100
438
|
* Enable debug console messages.
|
|
101
439
|
* @default false
|
|
@@ -111,6 +449,21 @@ interface iLangsysInitConfig {
|
|
|
111
449
|
* @default 'client'
|
|
112
450
|
*/
|
|
113
451
|
ssrTokenStrategy?: 'client' | 'server' | 'auto';
|
|
452
|
+
/**
|
|
453
|
+
* Turn on the legacy-key mode (spec MIG): the app's kept source-language
|
|
454
|
+
* files, each parsed, as `{ name, format?, namespace?, data }`. `t()` then
|
|
455
|
+
* resolves its argument as a key first; a key's value is the phrase, never
|
|
456
|
+
* the key. Formats are `i18next`, `vue-i18n` and `plain` (the default); any
|
|
457
|
+
* other makes `init` throw, naming the file. Unset, `t()` does no key lookup.
|
|
458
|
+
*/
|
|
459
|
+
legacyKeys?: LegacyKeyFile[];
|
|
460
|
+
/**
|
|
461
|
+
* The category server messages are registered and rendered under (spec
|
|
462
|
+
* MSG-6). It must be the same category the server registers templates
|
|
463
|
+
* under, or every lookup misses and falls back to the entry's `message`.
|
|
464
|
+
* @default 'Errors'
|
|
465
|
+
*/
|
|
466
|
+
messagesCategory?: string;
|
|
114
467
|
/**
|
|
115
468
|
* Pre-fetched translation data to bypass the initial API call.
|
|
116
469
|
* Useful for SSR scenarios to avoid duplicate fetches.
|
|
@@ -118,6 +471,17 @@ interface iLangsysInitConfig {
|
|
|
118
471
|
initialTranslations?: iCategories;
|
|
119
472
|
/** Locale that `initialTranslations` corresponds to. */
|
|
120
473
|
initialTranslationsLocale?: string;
|
|
474
|
+
/**
|
|
475
|
+
* Short-lived write grant for login-walled apps. See `WriteGrant` — prefer
|
|
476
|
+
* the function form; the bare string is quickstart-only. Can also be
|
|
477
|
+
* supplied after `init()` via `LangsysApp.setWriteGrant()`.
|
|
478
|
+
*
|
|
479
|
+
* Note: configuring a grant makes write capability per-user, so a
|
|
480
|
+
* process-level SSR write decision would be unsound. `ssrTokenStrategy`
|
|
481
|
+
* therefore degrades to `'client'` while a grant is configured (logged at
|
|
482
|
+
* init when `debug` is on).
|
|
483
|
+
*/
|
|
484
|
+
writeGrant?: WriteGrant;
|
|
121
485
|
}
|
|
122
486
|
interface iLangsysConfig {
|
|
123
487
|
projectid: string;
|
|
@@ -125,8 +489,22 @@ interface iLangsysConfig {
|
|
|
125
489
|
sUserLocale: LocaleSource;
|
|
126
490
|
baseLocale: string;
|
|
127
491
|
debug?: boolean;
|
|
128
|
-
/**
|
|
129
|
-
|
|
492
|
+
/**
|
|
493
|
+
* Permission level of the API key, set automatically after successful
|
|
494
|
+
* authorization. Display/debug only — NEVER branch on this.
|
|
495
|
+
*
|
|
496
|
+
* A public key shipped in browser JS is extractable, so write capability
|
|
497
|
+
* is decided per-session by the server, not by the key alone: `ip_write`
|
|
498
|
+
* keys are read-only everywhere except from allow-listed IPs. Use the
|
|
499
|
+
* server-computed `writeEnabled` signal for every write decision. The
|
|
500
|
+
* `(string & {})` arm keeps unknown future key types from breaking
|
|
501
|
+
* downstream typechecks.
|
|
502
|
+
*/
|
|
503
|
+
key_type?: 'read' | 'write' | 'ip_write' | (string & {});
|
|
504
|
+
/** Short-lived write grant sent as `X-Write-Grant`. See `WriteGrant`. */
|
|
505
|
+
writeGrant?: WriteGrant;
|
|
506
|
+
/** The category server messages render under (MSG-6). */
|
|
507
|
+
messagesCategory?: string;
|
|
130
508
|
/**
|
|
131
509
|
* Token creation behavior during SSR.
|
|
132
510
|
* - 'client': queue tokens for client-side sending (default)
|
|
@@ -138,71 +516,156 @@ interface iLangsysConfig {
|
|
|
138
516
|
}
|
|
139
517
|
|
|
140
518
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* `
|
|
146
|
-
* an object without `name`, or with a key not in the phrase, is a TS error.
|
|
147
|
-
*/
|
|
148
|
-
/** Allowed value types for interpolation. Mirrors what ICU MessageFormat will accept. */
|
|
149
|
-
type ParamPrimitive = string | number | Date | boolean;
|
|
150
|
-
/**
|
|
151
|
-
* Pull placeholder names out of a phrase.
|
|
152
|
-
* @example
|
|
153
|
-
* ExtractParamKeys<"Hello, {name}!"> // "name"
|
|
154
|
-
* ExtractParamKeys<"{first} and {last}"> // "first" | "last"
|
|
155
|
-
* ExtractParamKeys<"no placeholders here"> // never
|
|
156
|
-
*/
|
|
157
|
-
type ExtractParamKeys<S extends string> = S extends `${string}{${infer K}}${infer Rest}` ? K | ExtractParamKeys<Rest> : never;
|
|
158
|
-
/** Build the params object type required for a given phrase. */
|
|
159
|
-
type ParamsFor<S extends string> = {
|
|
160
|
-
[K in ExtractParamKeys<S>]: ParamPrimitive;
|
|
161
|
-
};
|
|
162
|
-
/**
|
|
163
|
-
* Rest-tuple for the optional params argument.
|
|
164
|
-
* Empty when the phrase has no placeholders → 3rd arg is forbidden.
|
|
165
|
-
* Required tuple when the phrase has placeholders → 3rd arg must be passed.
|
|
519
|
+
* The client has no credentials (WIRE-4): `init()` was refused for a missing project id,
|
|
520
|
+
* API key or locale store, or was never called. An explicit write throws this rather than
|
|
521
|
+
* sending, and so does `t()` under `requireCredentials`. A read renders source text instead,
|
|
522
|
+
* and a call whose result reports a skipped write resolves skipped with reason
|
|
523
|
+
* `not-configured`, since a component reaches it and a component path never throws.
|
|
166
524
|
*/
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
525
|
+
declare class LangsysConfigurationError extends Error {
|
|
526
|
+
constructor(message: string);
|
|
527
|
+
}
|
|
170
528
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
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.
|
|
529
|
+
* What a request sends under (SRV-2): a project id and API key, with the write grant that
|
|
530
|
+
* goes with them. The module-wide ones are what `init()` set; a request scope may carry
|
|
531
|
+
* its own.
|
|
182
532
|
*/
|
|
183
|
-
interface
|
|
184
|
-
|
|
185
|
-
|
|
533
|
+
interface ApiCredentials {
|
|
534
|
+
readonly projectid: string;
|
|
535
|
+
readonly key: string;
|
|
536
|
+
readonly writeGrant?: WriteGrant;
|
|
537
|
+
}
|
|
538
|
+
interface SendOptions {
|
|
539
|
+
/**
|
|
540
|
+
* Let the request outlive the page that started it (`fetch` keepalive).
|
|
541
|
+
* Used for the teardown flush, where the document is being torn down and a
|
|
542
|
+
* normal request would be cancelled with it.
|
|
543
|
+
*
|
|
544
|
+
* NOT `navigator.sendBeacon`, which is the usual answer to this problem but
|
|
545
|
+
* cannot carry custom headers — our authorization is `x-Authorization`, so
|
|
546
|
+
* a beacon would be rejected. `keepalive` supports headers and survives
|
|
547
|
+
* teardown; the trade is a ~64KB body cap across in-flight keepalive
|
|
548
|
+
* requests, which is why the teardown path chunks conservatively.
|
|
549
|
+
*/
|
|
550
|
+
keepalive?: boolean;
|
|
551
|
+
/**
|
|
552
|
+
* Send under these credentials. Without them a request sends under those of the
|
|
553
|
+
* request scope the calling code runs in, and outside every scope under the
|
|
554
|
+
* module-wide ones (SRV-2). The page's own lanes pass the module-wide ones, so a page
|
|
555
|
+
* request that starts inside a scope's async context still goes out under the page's key.
|
|
556
|
+
*/
|
|
557
|
+
credentials?: ApiCredentials;
|
|
558
|
+
}
|
|
559
|
+
declare class LangsysAppAPIClass {
|
|
560
|
+
private apiurl;
|
|
561
|
+
config: iLangsysConfig;
|
|
562
|
+
constructor();
|
|
563
|
+
setup(config: iLangsysConfig): void;
|
|
564
|
+
/** Forget the credentials: `init()` was refused for missing ones, so nothing may send under old ones. */
|
|
565
|
+
clearCredentials(): void;
|
|
566
|
+
/** True when the module-wide credentials carry a write grant (in either form). */
|
|
567
|
+
hasWriteGrant(): boolean;
|
|
568
|
+
/** The module-wide credentials `init()` set: the page's, and a scope's that was given none. */
|
|
569
|
+
moduleCredentials(): ApiCredentials;
|
|
570
|
+
/** What a request made now sends under, unless it names its own (SRV-2): the active scope's, else the module-wide ones. */
|
|
571
|
+
currentCredentials(): ApiCredentials;
|
|
572
|
+
/**
|
|
573
|
+
* Resolve the write grant a request sends with, calling the consumer's provider
|
|
574
|
+
* if that is the configured form.
|
|
575
|
+
*
|
|
576
|
+
* The result is deliberately NOT cached — not here, not on the config
|
|
577
|
+
* singleton. Grants are ~5 minute JWTs and per-user, while this client is
|
|
578
|
+
* a process-wide singleton; in a long-lived SSR server one cached token
|
|
579
|
+
* would be attached to every later user's requests. Resolving per request
|
|
580
|
+
* is also what lets the host app refresh by simply returning a new token.
|
|
581
|
+
*/
|
|
582
|
+
private resolveWriteGrant;
|
|
583
|
+
/** Build request headers fresh per call. Never memoized — see `resolveWriteGrant`. */
|
|
584
|
+
private buildHeaders;
|
|
585
|
+
/** Optional hook to point the client at a different API host. */
|
|
586
|
+
setBaseUrl(url: string): void;
|
|
587
|
+
validate(config: iLangsysConfig): Promise<ResponseObject>;
|
|
588
|
+
getTranslations(locale: string, opts?: SendOptions): Promise<ResponseObject>;
|
|
589
|
+
/**
|
|
590
|
+
* Register phrases and/or content blocks via the current unified endpoint.
|
|
591
|
+
* Each item carries `type: 'phrase' | 'content_block'`. Replaces the
|
|
592
|
+
* deprecated `projects/{id}/tokens` and `projects/{id}/content-blocks` routes.
|
|
593
|
+
*/
|
|
594
|
+
createTranslatableItems(items: Array<Record<string, unknown>>, opts?: SendOptions): Promise<ResponseObject>;
|
|
595
|
+
/**
|
|
596
|
+
* Report that a page appears to contain unregistered content, so the
|
|
597
|
+
* discovery renderer can visit it from an allow-listed IP and register
|
|
598
|
+
* what it finds.
|
|
599
|
+
*
|
|
600
|
+
* URL only — never a phrase payload. This is the read-only lane: anonymous
|
|
601
|
+
* callers can say "look here", never "store this". Answers 204 whether or
|
|
602
|
+
* not the hint is acted on, deliberately so it can't be used as an oracle
|
|
603
|
+
* for key validity or registration state. No `project_id`: the key already
|
|
604
|
+
* resolves to exactly one project server-side.
|
|
605
|
+
*/
|
|
606
|
+
postDiscoveryHint(pageUrl: string, opts?: SendOptions): Promise<ResponseObject>;
|
|
607
|
+
post(path: string, data?: Record<string, unknown>, opts?: SendOptions): Promise<ResponseObject>;
|
|
608
|
+
get(path: string, data?: Record<string, unknown>, opts?: SendOptions): Promise<ResponseObject>;
|
|
609
|
+
delete(path: string, data?: Record<string, unknown>, opts?: SendOptions): Promise<ResponseObject>;
|
|
610
|
+
patch(path: string, data?: Record<string, unknown>, opts?: SendOptions): Promise<ResponseObject>;
|
|
611
|
+
put(path: string, data?: Record<string, unknown>, opts?: SendOptions): Promise<ResponseObject>;
|
|
612
|
+
private send;
|
|
186
613
|
}
|
|
614
|
+
declare const LangsysAppAPI: LangsysAppAPIClass;
|
|
187
615
|
|
|
188
|
-
/**
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
* every time translations or the loaded locale change) so reactive
|
|
196
|
-
* templates re-render automatically.
|
|
197
|
-
*/
|
|
616
|
+
/** Where `t()` reads and records: the page's state, or a request scope's (SRV-7). */
|
|
617
|
+
interface CatalogView {
|
|
618
|
+
catalog(): iCategories;
|
|
619
|
+
locale(): string;
|
|
620
|
+
miss(category: string, key: string, onlyForRegistration: boolean): void;
|
|
621
|
+
fromSnapshot(): boolean;
|
|
622
|
+
}
|
|
198
623
|
declare class Translations {
|
|
199
624
|
private config;
|
|
200
625
|
private locale;
|
|
201
626
|
private lastLoaded;
|
|
202
627
|
private missingTokens;
|
|
628
|
+
/** Phrases already reported under REG-11, so the warning fires once each. */
|
|
629
|
+
private warnedEllipsis;
|
|
630
|
+
/** The legacy-key mode (MIG-1): null unless configured, and then `t()` does no key lookup at all. */
|
|
631
|
+
private legacyKeys;
|
|
632
|
+
private warnedLegacy;
|
|
203
633
|
private timer;
|
|
634
|
+
/**
|
|
635
|
+
* Catalog fetches still in flight. While any is, queued tokens are held: the flush
|
|
636
|
+
* deduplicates against the catalog, and the catalog being fetched is not there yet.
|
|
637
|
+
*/
|
|
638
|
+
private catalogFetchesInFlight;
|
|
639
|
+
/**
|
|
640
|
+
* The published catalog is a snapshot, not the live catalog (SNAP-2). Lookups
|
|
641
|
+
* read it, but registration is decided only against the live catalog
|
|
642
|
+
* (REG-13), so until one arrives every phrase is queued and the queue is held.
|
|
643
|
+
*/
|
|
644
|
+
private catalogFromSnapshot;
|
|
645
|
+
/**
|
|
646
|
+
* CACHE-2: a failed catalog fetch, remembered per project and locale. Until `retryAt` a
|
|
647
|
+
* lookup for that pair renders source text and nothing is fetched. The window follows
|
|
648
|
+
* REG-8's clock on the read side: 3s, doubling on each consecutive failure to 5 minutes,
|
|
649
|
+
* cleared by the first success. Held on this instance, so it lasts the page session in a
|
|
650
|
+
* browser and the life of the server object on a server, and is never written anywhere.
|
|
651
|
+
*/
|
|
652
|
+
private catalogFailures;
|
|
653
|
+
/** CACHE-2: a catalog request in flight per project and locale, shared by concurrent fetches. */
|
|
654
|
+
private catalogRequests;
|
|
204
655
|
private flushScheduled;
|
|
656
|
+
private updateInFlight;
|
|
657
|
+
private debounceTimer;
|
|
658
|
+
private consecutiveFailures;
|
|
659
|
+
/** Epoch ms before which no send may be attempted. See RETRY_BACKOFF_*. */
|
|
660
|
+
private retryNotBefore;
|
|
661
|
+
private teardownInstalled;
|
|
662
|
+
/** Unregisters this instance from an injected teardown signal (`setTeardownSignal`). */
|
|
663
|
+
private releaseTeardownFlush;
|
|
205
664
|
private isFirstClientRun;
|
|
665
|
+
/** Process-level write decision for the SSR lane. See `canWrite`. */
|
|
666
|
+
private ssrWriteEnabled;
|
|
667
|
+
/** Thrown by `t()` when `init()` was refused for missing credentials under `requireCredentials`. */
|
|
668
|
+
private credentialsError;
|
|
206
669
|
private readyResolve;
|
|
207
670
|
private readyPromise;
|
|
208
671
|
debug: Logger;
|
|
@@ -217,6 +680,54 @@ declare class Translations {
|
|
|
217
680
|
* to avoid acting on an empty cache during the cold-start race.
|
|
218
681
|
*/
|
|
219
682
|
ready(): Promise<void>;
|
|
683
|
+
/**
|
|
684
|
+
* Resolve `ready()` without a catalog, for the paths where none is coming:
|
|
685
|
+
* `init` refused the config, or authorization failed.
|
|
686
|
+
*
|
|
687
|
+
* `Phrase` and `Translate` both await this gate before rendering, so
|
|
688
|
+
* leaving it pending on those paths is not a degraded mode — it is a
|
|
689
|
+
* permanent blank render, with no error, on the commonest misconfigurations
|
|
690
|
+
* there are. Settling makes them fall back to their source text, which is
|
|
691
|
+
* what an SDK that cannot reach its backend should show.
|
|
692
|
+
*
|
|
693
|
+
* Idempotent. A later successful load still updates the stores and
|
|
694
|
+
* re-renders every subscriber.
|
|
695
|
+
*/
|
|
696
|
+
settle(): void;
|
|
697
|
+
/**
|
|
698
|
+
* WIRE-4: `init()` was refused for missing credentials. Reads render source through the
|
|
699
|
+
* catalog-failed path, so no miss is recorded and nothing is sent; and the capability
|
|
700
|
+
* settles as not writable, so nothing waits on it, and the read-only release drops what
|
|
701
|
+
* an earlier render queued, since it can never be sent. With `requireCredentials`, `t()`
|
|
702
|
+
* throws `error`.
|
|
703
|
+
*/
|
|
704
|
+
markUnconfigured(error: LangsysConfigurationError, requireCredentials: boolean): void;
|
|
705
|
+
/**
|
|
706
|
+
* Under `requireCredentials`, throw the configuration error while the client is
|
|
707
|
+
* unconfigured (WIRE-4). `t()` calls it, and so does `Translate`'s render, whose
|
|
708
|
+
* multi-phrase blocks never reach `t()`.
|
|
709
|
+
*/
|
|
710
|
+
assertConfigured(): void;
|
|
711
|
+
/** `init()` accepted credentials: undo an earlier refusal, so the first catalog decides afresh. */
|
|
712
|
+
markConfigured(): void;
|
|
713
|
+
/**
|
|
714
|
+
* The capability is known to be "no": the client has no credentials, or authorization was
|
|
715
|
+
* refused. Without this a block registration waiting for the capability would wait forever.
|
|
716
|
+
*/
|
|
717
|
+
settleNotWritable(): void;
|
|
718
|
+
/**
|
|
719
|
+
* A route change re-enters the SDK (HINT-13). Call it from the router's after-navigation
|
|
720
|
+
* hook. It publishes a fresh `t` through `tSignal` — the same dependency a locale change
|
|
721
|
+
* advances — so a component bound to `t` re-renders its mounted nodes, and each miss is
|
|
722
|
+
* recorded at the URL the page is now on. The core's own `Translate` and `Phrase` re-enter
|
|
723
|
+
* too, but only while their host is attached to the document, so an instance left
|
|
724
|
+
* undestroyed on a removed node records nothing.
|
|
725
|
+
*
|
|
726
|
+
* It sends nothing itself. What follows is the ordinary miss path: one report per URL,
|
|
727
|
+
* the jitter, the decline rules, GATE-9 and GATE-10 all apply, and a phrase already queued
|
|
728
|
+
* or registered is not queued again. Calling it twice for the same URL reports nothing more.
|
|
729
|
+
*/
|
|
730
|
+
notifyNavigation(): void;
|
|
220
731
|
/** Current translation function. Reads fresh state on every call. */
|
|
221
732
|
get t(): TFunction;
|
|
222
733
|
/** Subscribe to translation snapshot changes. Fires immediately with the current value. */
|
|
@@ -236,8 +747,148 @@ declare class Translations {
|
|
|
236
747
|
*/
|
|
237
748
|
lookup(phrase: string, category: string): string | null;
|
|
238
749
|
private buildTFn;
|
|
750
|
+
/** The page's catalog, locale and miss queue: what `t()` uses outside every request scope. */
|
|
751
|
+
private readonly pageView;
|
|
752
|
+
/**
|
|
753
|
+
* The body of `t()` over a catalog view: the page's, or a request scope's
|
|
754
|
+
* (SRV-7). One implementation, so a scope looks up, converts legacy keys,
|
|
755
|
+
* strips C0 controls and interpolates exactly as the page does, and differs
|
|
756
|
+
* only in whose catalog it reads and whose misses it records.
|
|
757
|
+
*/
|
|
758
|
+
renderWith(view: CatalogView, phrase: string, rest: unknown[]): string;
|
|
759
|
+
/**
|
|
760
|
+
* Turn on the legacy-key mode with the app's kept source-language files, or
|
|
761
|
+
* turn it off with null (MIG-1). Throws `LegacyFormatError` for a file this
|
|
762
|
+
* core does not read (MIG-7), before anything resolves through it.
|
|
763
|
+
*/
|
|
764
|
+
setLegacyKeys(files: LegacyKeyFile[] | null | undefined): void;
|
|
765
|
+
/**
|
|
766
|
+
* A key hit is the key's converted value, and the key's namespace is the
|
|
767
|
+
* category unless the call passed one (MIG-3, MIG-5). A miss is the argument
|
|
768
|
+
* as literal source text, which Langsys `t()` does not convert (MIG-2), and
|
|
769
|
+
* is noted at debug so a mistyped or removed key is visible (MIG-6). A value
|
|
770
|
+
* the conversion does not recognise registers as written and warns at every
|
|
771
|
+
* log level, naming the file and the key (MIG-4).
|
|
772
|
+
*/
|
|
773
|
+
private resolveLegacyKey;
|
|
774
|
+
private warnOnce;
|
|
775
|
+
/**
|
|
776
|
+
* Record a miss on the page's lanes, as `t()` outside every scope does: for a
|
|
777
|
+
* request scope rebuilt from a seed on the client (`scopeFromSeed`), whose
|
|
778
|
+
* render's misses belong to the page.
|
|
779
|
+
*/
|
|
780
|
+
recordPageMiss(category: string, token: string): void;
|
|
239
781
|
private missingToken;
|
|
782
|
+
/**
|
|
783
|
+
* Whether this session may write. The single decision point for the write
|
|
784
|
+
* lane — never infer capability from `key_type`.
|
|
785
|
+
*
|
|
786
|
+
* In the browser the server-computed signal is authoritative, and `undefined`
|
|
787
|
+
* (not yet authorized) correctly holds rather than writing or hinting.
|
|
788
|
+
*
|
|
789
|
+
* Under SSR the signal is deliberately never written (it is a process-wide
|
|
790
|
+
* singleton and write capability is per-session), so we fall back to the
|
|
791
|
+
* value the last catalog fetch reported for THIS process. That is only sound
|
|
792
|
+
* while capability depends on the server's IP alone, which is constant for
|
|
793
|
+
* the process. A configured write grant makes it per-user instead, so the
|
|
794
|
+
* server lane is unsafe and we degrade to flushing after hydration — where
|
|
795
|
+
* the decision is per-user and correct.
|
|
796
|
+
*/
|
|
797
|
+
/** Whether this session may write now, by the same decision the flush makes (see `canWrite`). */
|
|
798
|
+
mayWrite(): boolean;
|
|
799
|
+
private canWrite;
|
|
800
|
+
/**
|
|
801
|
+
* Record the server's write decision from an `authorize-project` or catalog
|
|
802
|
+
* response. Browser: updates the shared signal. Server: keeps a
|
|
803
|
+
* process-level copy used only when no grant is configured (see `canWrite`).
|
|
804
|
+
*
|
|
805
|
+
* `keyType` is the key type the same response describes. The authorization path
|
|
806
|
+
* passes it, because it runs before `init()` hands this instance the new config:
|
|
807
|
+
* reading `this.config` there would describe the PREVIOUS init's key on a re-init.
|
|
808
|
+
*/
|
|
809
|
+
applyWriteEnabled(value: boolean | undefined, keyType?: string | undefined): void;
|
|
810
|
+
/**
|
|
811
|
+
* Whether a miss is worth holding in the REGISTRATION queue. Distinct from
|
|
812
|
+
* `canWrite()`, which decides whether to send: this decides whether keeping
|
|
813
|
+
* it could ever lead to a send.
|
|
814
|
+
*
|
|
815
|
+
* What happens to a declined miss differs by arm, and the difference is
|
|
816
|
+
* load-bearing rather than incidental:
|
|
817
|
+
*
|
|
818
|
+
* - In the BROWSER the discovery lane has already recorded it, so the page
|
|
819
|
+
* is still reported and nothing is lost.
|
|
820
|
+
* - Under SSR it is recorded NOWHERE — `recordMissForDiscovery` bails
|
|
821
|
+
* without a `window`. The safety net there is that the client re-runs
|
|
822
|
+
* `t()` after hydration and re-collects, which covers content rendered
|
|
823
|
+
* on both sides and does NOT cover content that only ever executes
|
|
824
|
+
* server-side. That gap is real and documented rather than closed.
|
|
825
|
+
*
|
|
826
|
+
* Two ways a queue becomes undrainable:
|
|
827
|
+
*
|
|
828
|
+
* SSR under `'client'` — the queue is meant to be flushed after hydration
|
|
829
|
+
* by `setup()`, but that reads the BROWSER's instance of this module. The
|
|
830
|
+
* push happened in the Node process's instance. Under real SSR (Next,
|
|
831
|
+
* Remix, Astro) those are different objects in different processes, and no
|
|
832
|
+
* channel carries tokens client-ward — `initialTranslations` goes the other
|
|
833
|
+
* way. So the tokens are silently dropped AND retained forever, since the
|
|
834
|
+
* 3s drain timer only exists in the browser branch of `setup()`. In a
|
|
835
|
+
* long-lived server that plateaus at "every phrase the app has ever
|
|
836
|
+
* server-rendered", held live and never sent. Declining to collect makes
|
|
837
|
+
* the limitation explicit instead of silent: server-side discovery requires
|
|
838
|
+
* `ssrTokenStrategy: 'server'`.
|
|
839
|
+
*
|
|
840
|
+
* Browser, definitively read-only — `writeEnabled` is `false` and no grant
|
|
841
|
+
* is configured, so no path exists by which this session becomes writable.
|
|
842
|
+
* (`undefined` still queues: capability isn't known yet and those misses
|
|
843
|
+
* must be held, not dropped. A configured grant also queues, since one
|
|
844
|
+
* arriving flips the session write-enabled.)
|
|
845
|
+
*/
|
|
846
|
+
private shouldQueueForWrite;
|
|
240
847
|
private scheduleTokenFlush;
|
|
848
|
+
/**
|
|
849
|
+
* Start the backstop interval if it isn't already running.
|
|
850
|
+
*
|
|
851
|
+
* Demand-driven deliberately. It used to start in `setup()` and run for the
|
|
852
|
+
* life of the page whether or not anything was ever queued — a wakeup every
|
|
853
|
+
* three seconds forever, for a queue that is empty almost all of the time.
|
|
854
|
+
* That is a battery and background-work cost on mobile, and React Native
|
|
855
|
+
* takes this same branch because it defines `window`.
|
|
856
|
+
*
|
|
857
|
+
* The tick disarms itself once the queue drains; the next miss re-arms it
|
|
858
|
+
* through `scheduleTokenFlush`. While sends keep failing the queue stays
|
|
859
|
+
* non-empty and the interval keeps running, which is the case a backstop
|
|
860
|
+
* exists for.
|
|
861
|
+
*/
|
|
862
|
+
private ensureBackstop;
|
|
863
|
+
private stopBackstop;
|
|
864
|
+
/**
|
|
865
|
+
* Stop background work. Queued tokens stay queued, and further discovery
|
|
866
|
+
* re-arms the backstop — this releases the timer, it does not discard work.
|
|
867
|
+
*/
|
|
868
|
+
destroy(): void;
|
|
869
|
+
/**
|
|
870
|
+
* Send whatever is still queued while the page is going away.
|
|
871
|
+
*
|
|
872
|
+
* Even with the debounce there is always a window where a miss is queued
|
|
873
|
+
* and the document is torn down — a tab closing, a client-side navigation,
|
|
874
|
+
* or the discovery renderer finishing its grace period. A normal request is
|
|
875
|
+
* cancelled with the document; `keepalive` survives it.
|
|
876
|
+
*
|
|
877
|
+
* Deliberately NOT `navigator.sendBeacon`, the usual answer here: it cannot
|
|
878
|
+
* set custom headers, and our authorization is `x-Authorization`, so every
|
|
879
|
+
* beacon would be rejected.
|
|
880
|
+
*
|
|
881
|
+
* Fire-and-forget with no local bookkeeping — the page is leaving, so there
|
|
882
|
+
* is no "after" in which a cache write or queue mutation could matter, and
|
|
883
|
+
* skipping them keeps this off the critical path of teardown.
|
|
884
|
+
*/
|
|
885
|
+
private flushOnTeardown;
|
|
886
|
+
/**
|
|
887
|
+
* `visibilitychange → hidden` is the reliable teardown signal; `pagehide`
|
|
888
|
+
* backs it up. `unload`/`beforeunload` are deliberately not used — they are
|
|
889
|
+
* unreliable on mobile and block bfcache.
|
|
890
|
+
*/
|
|
891
|
+
private installTeardownFlush;
|
|
241
892
|
setup(config: iLangsysConfig): void;
|
|
242
893
|
/**
|
|
243
894
|
* Record that a locale's catalog is already present without fetching —
|
|
@@ -247,42 +898,66 @@ declare class Translations {
|
|
|
247
898
|
* later switch *to* it still flows through `change()` normally.
|
|
248
899
|
*/
|
|
249
900
|
markLoaded(locale: string): void;
|
|
901
|
+
/**
|
|
902
|
+
* A catalog is present for rendering, but it is not the catalog of record:
|
|
903
|
+
* a snapshot (SNAP-3). Unlike `markLoaded` nothing is recorded as fetched,
|
|
904
|
+
* so the next settle for the locale still fetches the catalog and replaces
|
|
905
|
+
* it. What waits on `ready()` renders from it now.
|
|
906
|
+
*/
|
|
907
|
+
markSeeded(): void;
|
|
250
908
|
/**
|
|
251
909
|
* React to a user-locale change. Fetches translations if we haven't
|
|
252
910
|
* loaded this locale within the last 60s (or if forced).
|
|
253
911
|
*/
|
|
254
912
|
change(locale: string, force?: boolean, skipFetch?: boolean): Promise<boolean>;
|
|
913
|
+
/** The project and locale a catalog belongs to: its cache key, request key and CACHE-2 pair. */
|
|
914
|
+
private catalogScope;
|
|
915
|
+
/** One catalog request for a project and locale, recording the outcome for CACHE-2's window. */
|
|
916
|
+
private fetchCatalog;
|
|
917
|
+
/**
|
|
918
|
+
* Record a failed send and push the next attempt out exponentially:
|
|
919
|
+
* 3s, 6s, 12s … capped at 5 minutes. Reset on the first success.
|
|
920
|
+
*/
|
|
921
|
+
private noteSendFailure;
|
|
255
922
|
private updateTokens;
|
|
923
|
+
/**
|
|
924
|
+
* WIRE-4: the catalog fetch failed. Settle `ready()` so consumers render their
|
|
925
|
+
* source text, and mark the catalog unusable for deciding what is missing.
|
|
926
|
+
*
|
|
927
|
+
* The second half is the one that needs saying. Measured before it existed, with
|
|
928
|
+
* write capability already granted: a network-failed fetch let `t()` misses queue
|
|
929
|
+
* and POST phrases that were already registered, and a failed locale switch did
|
|
930
|
+
* the same, because the new scope had just reset the catalog to empty.
|
|
931
|
+
*/
|
|
932
|
+
private markCatalogUnavailable;
|
|
933
|
+
/**
|
|
934
|
+
* Fetch and publish the catalog for `this.locale`. Resolves false when it could not.
|
|
935
|
+
*
|
|
936
|
+
* The response publishes only while the locale and cache scope it was requested for are
|
|
937
|
+
* still current. A locale change while it is in flight supersedes it, and it is dropped
|
|
938
|
+
* when it lands: published, it would show the previous locale's catalog under the new
|
|
939
|
+
* one, persist it under the new locale's cache key, and stamp the new locale as loaded,
|
|
940
|
+
* so `change()` would not fetch that locale for another 60s. A superseded response still
|
|
941
|
+
* resolves true or false, which `fetchCatalog` records against its own pair (CACHE-2).
|
|
942
|
+
*/
|
|
256
943
|
private getTranslations;
|
|
257
944
|
}
|
|
258
945
|
|
|
259
|
-
interface HttpResponse {
|
|
260
|
-
status: number;
|
|
261
|
-
statusText: string;
|
|
262
|
-
url: string;
|
|
263
|
-
data: string;
|
|
264
|
-
}
|
|
265
|
-
interface ResponseObject {
|
|
266
|
-
status: boolean;
|
|
267
|
-
page?: number;
|
|
268
|
-
page_count?: number;
|
|
269
|
-
records_per_page?: number;
|
|
270
|
-
data?: object[] | object;
|
|
271
|
-
errors?: string[];
|
|
272
|
-
http?: HttpResponse;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
946
|
interface iCountry {
|
|
947
|
+
/** Country name in the requested locale */
|
|
276
948
|
label: string;
|
|
949
|
+
/** ISO 3166-1 alpha-2 country code (e.g., "ES") */
|
|
277
950
|
code: string;
|
|
951
|
+
/** In the EU: VAT applies, and a business VAT ID makes it a reverse charge */
|
|
952
|
+
eu?: boolean;
|
|
278
953
|
}
|
|
279
954
|
type iCountryList = iCountry[];
|
|
280
955
|
interface iCountryDialCode {
|
|
281
956
|
/** ISO 3166-1 alpha-2 country code (e.g., "US", "CA", "GB") */
|
|
282
957
|
country_code: string;
|
|
283
|
-
/** International dialing code
|
|
958
|
+
/** International dialing code, digits only (e.g., "1", "44", "506") */
|
|
284
959
|
dial_code: string;
|
|
285
|
-
/**
|
|
960
|
+
/** Country name in the requested locale with its dialing code (e.g., "Costa Rica (+506)") */
|
|
286
961
|
name: string;
|
|
287
962
|
}
|
|
288
963
|
|
|
@@ -317,34 +992,192 @@ declare class LangsysAppClass {
|
|
|
317
992
|
private config;
|
|
318
993
|
Translations: Translations;
|
|
319
994
|
translationsLoadingPromise: Promise<unknown>;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
995
|
+
/**
|
|
996
|
+
* The reference lists read so far (REF-1), keyed by list and display locale: a list the API
|
|
997
|
+
* answered is held for the core's lifetime, a read in flight is shared, and a failed read is
|
|
998
|
+
* remembered for CACHE-2's window under the credentials it was sent with. Memory only: on a server the core is the long-lived
|
|
999
|
+
* module, so the hold already outlives the request, and nothing goes to a cache another
|
|
1000
|
+
* host reads (CACHE-1).
|
|
1001
|
+
*/
|
|
1002
|
+
private referenceLists;
|
|
327
1003
|
debug: Logger;
|
|
328
1004
|
constructor();
|
|
329
1005
|
/** Current translation function. Reads fresh state on every call. */
|
|
330
1006
|
get t(): TFunction;
|
|
1007
|
+
/**
|
|
1008
|
+
* Supply (or replace) the write grant after `init()` has run.
|
|
1009
|
+
*
|
|
1010
|
+
* This is for apps whose token only exists once the user logs in — i.e.
|
|
1011
|
+
* after the SDK has already booted. It is NOT the refresh path: the token
|
|
1012
|
+
* is resolved fresh before every request and never cached, so refresh is a
|
|
1013
|
+
* pull. Pass a provider function and let your auth layer decide when to
|
|
1014
|
+
* mint; that also makes this safe to call while a request is in flight,
|
|
1015
|
+
* since an in-flight request has already resolved its own token.
|
|
1016
|
+
*
|
|
1017
|
+
* Pass `undefined` to drop the grant (e.g. on logout).
|
|
1018
|
+
*/
|
|
1019
|
+
setWriteGrant(grant: WriteGrant | undefined): Promise<void>;
|
|
1020
|
+
/**
|
|
1021
|
+
* Apply an `authorize-project` payload. Shared by `init` and the
|
|
1022
|
+
* re-authorization in `setWriteGrant`, so a policy or capability change is
|
|
1023
|
+
* picked up identically by both — previously the re-auth path silently
|
|
1024
|
+
* ignored `auto_discovery` even though the response carries it.
|
|
1025
|
+
*/
|
|
1026
|
+
private applyAuthorization;
|
|
1027
|
+
/** A route change re-enters the SDK (HINT-13). See `Translations.notifyNavigation`. */
|
|
1028
|
+
notifyNavigation(): void;
|
|
1029
|
+
/**
|
|
1030
|
+
* Put a catalog in place SYNCHRONOUSLY, with no network and no await.
|
|
1031
|
+
*
|
|
1032
|
+
* The hydration hand-off primitive: a framework's client entry calls this
|
|
1033
|
+
* with the catalog the server already rendered from, before mount, so the
|
|
1034
|
+
* first paint has translations instead of source text. `t()` returns the
|
|
1035
|
+
* translation on the very next line — there is nothing to await, which is
|
|
1036
|
+
* the whole point. `init()` can run afterwards, at its own pace, to
|
|
1037
|
+
* authorize and take over refreshes.
|
|
1038
|
+
*
|
|
1039
|
+
* Does exactly what the `initialTranslations` hand-off inside `init()` does
|
|
1040
|
+
* — injects `__uncategorized__`, stamps every `__category__`, publishes the
|
|
1041
|
+
* catalog and the locale — because it IS that code now. Two
|
|
1042
|
+
* implementations of "seed a catalog" would be two sets of rules about what
|
|
1043
|
+
* a seeded catalog looks like.
|
|
1044
|
+
*
|
|
1045
|
+
* Safe to call before `init()`, after it, or both: `init()` will not
|
|
1046
|
+
* re-seed a locale that is already seeded, so an SSR payload passed through
|
|
1047
|
+
* config cannot clobber a catalog a client entry put there first.
|
|
1048
|
+
*
|
|
1049
|
+
* `served`, a request scope's `seed()`, hands over what the server rendered
|
|
1050
|
+
* before any DOM class mounts: each block's source tokens, which a served
|
|
1051
|
+
* translation re-renders from, and which blocks and phrases the server sends
|
|
1052
|
+
* itself (`collected`), which the client then never registers. Nothing is
|
|
1053
|
+
* registered here; `registerBlock(seededBlock)` after `init()` does that.
|
|
1054
|
+
*/
|
|
1055
|
+
/**
|
|
1056
|
+
* The request header that asks the app's own API for the user's language
|
|
1057
|
+
* (FRM-6): `{ 'Accept-Language': 'es-es' }` for the locale the user chose,
|
|
1058
|
+
* inside a request scope the scope's, else the loaded catalog's. Empty before
|
|
1059
|
+
* any locale is known. Spread it into the headers of the app's API calls, so an
|
|
1060
|
+
* API that answers per the negotiated language (FRM-5) answers in the user's,
|
|
1061
|
+
* not the browser's default.
|
|
1062
|
+
*/
|
|
1063
|
+
localeHeaders(): Record<string, string>;
|
|
1064
|
+
seedCatalog(catalog: iCategories, locale: string, served?: {
|
|
1065
|
+
blocks?: Record<string, SeededBlock>;
|
|
1066
|
+
phrases?: readonly SeededPhrase[];
|
|
1067
|
+
}): void;
|
|
1068
|
+
/**
|
|
1069
|
+
* Load a catalog snapshot (spec SNAP-2) SYNCHRONOUSLY as the preloaded
|
|
1070
|
+
* catalog for `locale` — the current user locale by default, else the
|
|
1071
|
+
* snapshot's base locale. A first render or an offline session then has
|
|
1072
|
+
* its translations with no network call.
|
|
1073
|
+
*
|
|
1074
|
+
* The snapshot is a cache, never the catalog of record (SNAP-3): once
|
|
1075
|
+
* `init()` runs, the catalog is fetched as usual and replaces it, and a
|
|
1076
|
+
* phrase the snapshot lacks is found there. With no network the snapshot
|
|
1077
|
+
* keeps rendering, and anything it lacks shows its source text.
|
|
1078
|
+
*
|
|
1079
|
+
* Throws `SnapshotError`, naming the reason, for a file that is not a
|
|
1080
|
+
* snapshot, an unsupported version, a missing member, or a checksum the
|
|
1081
|
+
* contents no longer match — an edited snapshot is refused, not served.
|
|
1082
|
+
* Returns false when the snapshot holds no catalog for the locale.
|
|
1083
|
+
*/
|
|
1084
|
+
loadSnapshot(snapshot: string | CatalogSnapshot, locale?: string): boolean;
|
|
1085
|
+
/** Publish a catalog and its locale — the one set of rules for what a seeded catalog looks like. */
|
|
1086
|
+
private publishSeed;
|
|
1087
|
+
/**
|
|
1088
|
+
* Render a server message entry (spec MSG-5): its `template` through `t()`
|
|
1089
|
+
* under the messages category, filled from its `params`. When the catalog
|
|
1090
|
+
* holds no translation for the template, the entry's own `message` is shown
|
|
1091
|
+
* instead, which is the text the server filled and possibly localised; an
|
|
1092
|
+
* entry with no `message` shows its template filled. An entry with no
|
|
1093
|
+
* `template` is not looked up and shows its `message`. `message` is never
|
|
1094
|
+
* used as a lookup key.
|
|
1095
|
+
*
|
|
1096
|
+
* The lookup goes through `t()` on a miss too, so the template is recorded
|
|
1097
|
+
* like any other missing phrase. A plural in the translation renders from a
|
|
1098
|
+
* numeric param through the catalog's ICU, with no ICU written by the caller.
|
|
1099
|
+
*/
|
|
1100
|
+
renderServerMessage(entry: ServerMessage, category?: string): string;
|
|
1101
|
+
/** Whether a catalog is already published for this locale. */
|
|
1102
|
+
private isAlreadySeeded;
|
|
1103
|
+
/** Read the current locale's catalog again, and the locale data `getLocaleName` reads, on its next load. */
|
|
331
1104
|
refresh(): Promise<boolean>;
|
|
332
1105
|
/**
|
|
333
1106
|
* Initialize Langsys. Configuration must include at least `projectid`,
|
|
334
1107
|
* `key`, and `UserLocaleStore` (anything satisfying `LocaleSource` — see
|
|
335
1108
|
* its docstring for framework-specific adapters).
|
|
336
1109
|
*/
|
|
1110
|
+
/**
|
|
1111
|
+
* Whether `init()` accepted a project id, an API key and a locale store (WIRE-4). False
|
|
1112
|
+
* before `init()` runs and after one refused for missing credentials. Authorization is a
|
|
1113
|
+
* separate question: a configured client whose key the server refuses is still configured.
|
|
1114
|
+
*/
|
|
1115
|
+
isConfigured(): boolean;
|
|
1116
|
+
/**
|
|
1117
|
+
* `init()` without credentials (WIRE-4). Nothing throws here: `t()` renders source text with
|
|
1118
|
+
* its params filled, no miss is queued and no request is made, a component's block
|
|
1119
|
+
* registration resolves skipped with reason `not-configured`, and an explicit write throws
|
|
1120
|
+
* `LangsysConfigurationError`. With `requireCredentials`, `t()` throws it too.
|
|
1121
|
+
*/
|
|
1122
|
+
private refuseUnconfigured;
|
|
337
1123
|
init(initConfig: iLangsysInitConfig): Promise<ResponseObject>;
|
|
1124
|
+
/** The countries' dial codes, named in the display locale (REF-1). */
|
|
338
1125
|
getDialCodes(inLocale?: string): Promise<iCountryDialCode[]>;
|
|
1126
|
+
/** The currencies, named in the display locale (REF-1). */
|
|
339
1127
|
getCurrencies(inLocale?: string): Promise<iCurrencyList>;
|
|
1128
|
+
/** The countries, named in the display locale (REF-1). */
|
|
340
1129
|
getCountries(inLocale?: string): Promise<iCountryList>;
|
|
341
1130
|
getCountryName(forCountryCode: string, inLocale?: string): Promise<string>;
|
|
342
1131
|
getCurrencyName(forCurrencyCode: string, inLocale?: string): Promise<string>;
|
|
1132
|
+
/** The locales in one of the API's three forms, named in the display locale (REF-1). */
|
|
343
1133
|
getLocalesFormat(format?: '' | 'flat' | 'data', inLocale?: string): Promise<iLocaleDefault | iLocaleFlat[] | iLocaleData[]>;
|
|
344
1134
|
getLocalesFlat(inLocale?: string): Promise<iLocaleFlat[]>;
|
|
1135
|
+
/** `forceRefresh` is an explicit refresh: it reads the list again, held or inside a failure window, and holds what that read answers. */
|
|
345
1136
|
getLocalesData(inLocale?: string, forceRefresh?: boolean): Promise<iLocaleData[]>;
|
|
346
1137
|
getLocales(inLocale?: string): Promise<iLocaleDefault>;
|
|
1138
|
+
/**
|
|
1139
|
+
* Read one reference list in a display locale (REF-1), or answer the one held for it.
|
|
1140
|
+
*
|
|
1141
|
+
* The display locale is the one given, else the current one, canonical as the core serves
|
|
1142
|
+
* it; one not shaped as a locale is refused before anything is sent, since it is written
|
|
1143
|
+
* into the request. A locale form is asked for that locale alone and answers that locale's entry,
|
|
1144
|
+
* whose key and codes are in the organisation's code format, so it is matched and recoded
|
|
1145
|
+
* in WIRE-3's form; a locale the API does not hold answers `empty()`, held like any
|
|
1146
|
+
* answer. Nothing here throws but `requireCredentials` (WIRE-4): a refused locale, a failed
|
|
1147
|
+
* read and an answer not of the list's shape answer `empty()` with the cause logged, and
|
|
1148
|
+
* a failed read is remembered for CACHE-2's window under its credentials, inside which a
|
|
1149
|
+
* call under them sends nothing; a call under other credentials still reads, though one made
|
|
1150
|
+
* while a read is in flight shares it, whoever sent it.
|
|
1151
|
+
* An unconfigured core sends nothing and logs nothing, whatever the display locale,
|
|
1152
|
+
* `init()`'s refusal being its one warning. Each caller gets its own copy of a held list,
|
|
1153
|
+
* so what one caller does to it never reaches the next; `copy: false` is for the core's own
|
|
1154
|
+
* lookups, which only read it.
|
|
1155
|
+
*/
|
|
1156
|
+
private readReferenceList;
|
|
1157
|
+
/**
|
|
1158
|
+
* An explicit refresh of one list and display locale: the held list and every failure
|
|
1159
|
+
* window go, so the next call reads. Each credentials' count of failures in a row stays, so
|
|
1160
|
+
* a refresh that fails again widens CACHE-2's window as any failure in a row does.
|
|
1161
|
+
*/
|
|
1162
|
+
private forgetReferenceList;
|
|
1163
|
+
/**
|
|
1164
|
+
* A reference list's display locale (REF-1): the one given, else the current one, each
|
|
1165
|
+
* canonical, as the core normalises the locale it serves. The current locale is the one
|
|
1166
|
+
* `t()` and the catalog serve, on both profiles: the request scope's, else the store's. So
|
|
1167
|
+
* a given `iw-IL` and a store holding `iw-IL` both name their lists in `he-il`.
|
|
1168
|
+
*/
|
|
1169
|
+
private displayLocale;
|
|
347
1170
|
getLocaleNameWithLookup(forLocale: string, shortName?: boolean, inLocale?: string): Promise<string>;
|
|
1171
|
+
/**
|
|
1172
|
+
* Synchronous locale-name lookup against the in-memory locales cache.
|
|
1173
|
+
*
|
|
1174
|
+
* The cache is only populated once `await getLocalesData(inLocale)` — or
|
|
1175
|
+
* any `getLocaleNameWithLookup` call — has settled for that display locale.
|
|
1176
|
+
* Called before then this returns `''`, the same value a genuinely unknown
|
|
1177
|
+
* locale returns, so the two cases are told apart by the warning rather
|
|
1178
|
+
* than by the return value. Use `getLocaleNameWithLookup` when you can
|
|
1179
|
+
* await; it loads the data first.
|
|
1180
|
+
*/
|
|
348
1181
|
getLocaleName(forLocale: string, shortName?: boolean, inLocale?: string): string;
|
|
349
1182
|
/** @deprecated use `getLocaleNameWithLookup` or `getLocaleName` */
|
|
350
1183
|
getLanguageName(forLocale: string, shortName?: boolean, inLocale?: string): Promise<string>;
|
|
@@ -353,17 +1186,21 @@ declare class LangsysAppClass {
|
|
|
353
1186
|
*
|
|
354
1187
|
* - In browsers, uses `navigator.languages` with fallback to `navigator.language`.
|
|
355
1188
|
* - In SSR environments, parses the `Accept-Language` header.
|
|
1189
|
+
*
|
|
1190
|
+
* With `supportedLocales`, returns the best CLDR-aware match from that list
|
|
1191
|
+
* or `false` when none of the user's preferences are supported — so the
|
|
1192
|
+
* documented `detectPreferredLocale(header, supported) || 'en-us'` idiom
|
|
1193
|
+
* actually reaches your default. Returning the user's own unsupported
|
|
1194
|
+
* locale instead would send the app to fetch a catalog that does not
|
|
1195
|
+
* exist, and the caller's `||` would never fire.
|
|
1196
|
+
*
|
|
1197
|
+
* Without `supportedLocales`, returns the user's first preference in
|
|
1198
|
+
* canonical form, or `false` when no preference is detectable.
|
|
356
1199
|
*/
|
|
357
1200
|
detectPreferredLocale(acceptLanguageHeader?: string | null, supportedLocales?: string[]): string | false;
|
|
358
1201
|
private getUserLanguagePreferences;
|
|
359
1202
|
private parseAcceptLanguageHeader;
|
|
360
1203
|
private findBestLocaleMatch;
|
|
361
|
-
/**
|
|
362
|
-
* Resolve the effective locale for a data-fetching helper: explicit
|
|
363
|
-
* argument, else the user's current locale, else the base locale —
|
|
364
|
-
* always in BCP 47 canonical form so cache keys and API routes agree.
|
|
365
|
-
*/
|
|
366
|
-
private resolveLocale;
|
|
367
1204
|
}
|
|
368
1205
|
declare const LangsysApp: LangsysAppClass;
|
|
369
1206
|
|
|
@@ -383,6 +1220,12 @@ interface TranslateOptions {
|
|
|
383
1220
|
* normalized back to `{name}` at capture.
|
|
384
1221
|
*/
|
|
385
1222
|
params?: Record<string, ParamPrimitive>;
|
|
1223
|
+
/**
|
|
1224
|
+
* `false` renders from the catalog and registers nothing, on either lane (VAR-7):
|
|
1225
|
+
* for a binding whose content holds values from variables it cannot name without
|
|
1226
|
+
* its build-time transform. One debug notice says so. Default `true`.
|
|
1227
|
+
*/
|
|
1228
|
+
register?: boolean;
|
|
386
1229
|
}
|
|
387
1230
|
/**
|
|
388
1231
|
* Wrap a DOM element and manage translation for its contents.
|
|
@@ -405,15 +1248,206 @@ declare class Translate {
|
|
|
405
1248
|
private isTokenizing;
|
|
406
1249
|
private lastTranslatedLocale;
|
|
407
1250
|
private custom_id;
|
|
408
|
-
private
|
|
1251
|
+
private unsubscribers;
|
|
409
1252
|
/** Sorted params key-set already checked, so value-only updates don't re-warn. */
|
|
410
1253
|
private checkedParamKeys;
|
|
411
|
-
|
|
1254
|
+
/** The block this host registered as, kept so a navigation can re-record its miss. */
|
|
1255
|
+
private contentBlock;
|
|
1256
|
+
/**
|
|
1257
|
+
* The id a renderer stamped on this host (MARK-3's identity), adopted rather
|
|
1258
|
+
* than derived. Such a host renders from the catalog entry under that id, or
|
|
1259
|
+
* its source text, and registers nothing.
|
|
1260
|
+
*/
|
|
1261
|
+
private adoptedId;
|
|
1262
|
+
/**
|
|
1263
|
+
* Whether an adopted host's source tokens have been recovered: its DOM holds
|
|
1264
|
+
* the text a server translated, never the source the catalog is keyed by.
|
|
1265
|
+
*/
|
|
1266
|
+
private sourcesRecovered;
|
|
1267
|
+
/** The locale an adopted host was served in; its served text is right for that locale. */
|
|
1268
|
+
private servedLocale;
|
|
1269
|
+
/** What the unit's value markers say (VAR-3): the runs among its tokens, and their values. */
|
|
1270
|
+
private vars;
|
|
1271
|
+
/** Each marked run's source, by its first node, where it was recovered rather than read. */
|
|
1272
|
+
private runSources;
|
|
1273
|
+
/** Each marked run's text nodes as first seen, before any render wrote them. */
|
|
1274
|
+
private runOriginals;
|
|
1275
|
+
/** Each marked value as first seen, for a render that emptied it. */
|
|
1276
|
+
private markedValues;
|
|
1277
|
+
/** Marked hosts inside this one that no instance managed, taken over by this walk. */
|
|
1278
|
+
private takenOver;
|
|
1279
|
+
/**
|
|
1280
|
+
* SRV-5: the content captured at mount is provisional. A framework can mount a
|
|
1281
|
+
* block showing a placeholder (a Suspense fallback, a lazy child's spinner)
|
|
1282
|
+
* and swap in the real content a moment later. An observer watches the host's
|
|
1283
|
+
* STRUCTURE (child lists, never text: a value update must not re-key a block);
|
|
1284
|
+
* the first registration waits until it has been quiet for `SETTLE_MS`, and a
|
|
1285
|
+
* later structural change re-derives the block and registers what it shows.
|
|
1286
|
+
*/
|
|
1287
|
+
private observer;
|
|
1288
|
+
private settleTimer;
|
|
1289
|
+
/** Whether the subtree has been quiet for the settle window since mount. */
|
|
1290
|
+
private settled;
|
|
1291
|
+
private settleWaiters;
|
|
1292
|
+
/** A structural change the tokens do not reflect yet. */
|
|
1293
|
+
private dirty;
|
|
1294
|
+
/** Whether the id is derived from the content, and so follows it when it changes. */
|
|
1295
|
+
private derivedId;
|
|
1296
|
+
/** The id this instance last registered under, so a re-key after the window can be reported. */
|
|
1297
|
+
private registeredId;
|
|
1298
|
+
/** The text this class last wrote into each text node (see `isUnitChange`). */
|
|
1299
|
+
private written;
|
|
1300
|
+
/** The whitespace at each end of a single-token unit's text as the page gave it (`writeKeepingEdges`). */
|
|
1301
|
+
private edges;
|
|
1302
|
+
/**
|
|
1303
|
+
* @param byWalk Internal: set when an enclosing walk creates this instance for
|
|
1304
|
+
* a host nothing managed, so an instance the author constructs replaces it.
|
|
1305
|
+
*/
|
|
1306
|
+
constructor(element: HTMLElement, options?: TranslateOptions, byWalk?: boolean);
|
|
1307
|
+
/**
|
|
1308
|
+
* HINT-13: after `notifyNavigation()`, re-enter the lookup as a re-render would, so a miss is
|
|
1309
|
+
* recorded at the page's new URL — only while the host is attached to the document. An
|
|
1310
|
+
* instance whose node was removed but never destroyed stays subscribed, and re-entering it
|
|
1311
|
+
* would report the old page's content for the new one.
|
|
1312
|
+
*/
|
|
1313
|
+
private reenterAfterNavigation;
|
|
412
1314
|
/** Update interpolation params (e.g. a changed count) and re-render. Mirrors `Phrase.setParams`. */
|
|
413
1315
|
setParams(params: Record<string, ParamPrimitive> | undefined): void;
|
|
414
|
-
/**
|
|
1316
|
+
/**
|
|
1317
|
+
* Watch the host's structure (SRV-5). Not for an adopted host or one in a
|
|
1318
|
+
* resolved scope: served content is already what it shows, and registers
|
|
1319
|
+
* nothing. Where there is no `MutationObserver`, the content is settled at mount.
|
|
1320
|
+
*/
|
|
1321
|
+
private observeStructure;
|
|
1322
|
+
/**
|
|
1323
|
+
* Whether a mutation changes what this unit shows. A changed child list always
|
|
1324
|
+
* does. A changed text does only until the window first closes, and only when it
|
|
1325
|
+
* is not the text this class wrote: a framework can swap a placeholder's text for
|
|
1326
|
+
* its content in place, and what registers is what the block shows when the
|
|
1327
|
+
* window closes. After that, a text change is a value update and never re-keys.
|
|
1328
|
+
*/
|
|
1329
|
+
private isUnitChange;
|
|
1330
|
+
/**
|
|
1331
|
+
* Whether the unit changed since its tokens were read, counting changes the
|
|
1332
|
+
* observer has not delivered yet: a framework's swap and a catalog arriving can
|
|
1333
|
+
* fall in the same task, before the observer's callback runs.
|
|
1334
|
+
*/
|
|
1335
|
+
private hasUnreadChange;
|
|
1336
|
+
/** Write a text node, remembering the text, so the observer can tell this write from a framework's. */
|
|
1337
|
+
private writeText;
|
|
1338
|
+
/**
|
|
1339
|
+
* Write a unit's one token with the text node's own whitespace kept at each end, as
|
|
1340
|
+
* the block path keeps it: beside an excluded element (TOK-1) it is the space between
|
|
1341
|
+
* two words. The ends are the page's, so a re-render never adds to them, and a text
|
|
1342
|
+
* the framework has rewritten since gives its own.
|
|
1343
|
+
*/
|
|
1344
|
+
private writeKeepingEdges;
|
|
1345
|
+
/**
|
|
1346
|
+
* Whether a changed child list belongs to this unit. An excised subtree, and
|
|
1347
|
+
* each nested host, is another instance's. An `<option>`'s list is changed only
|
|
1348
|
+
* by this class, writing its text.
|
|
1349
|
+
*/
|
|
1350
|
+
private isUnitStructure;
|
|
1351
|
+
private armSettle;
|
|
1352
|
+
private markSettled;
|
|
1353
|
+
private whenSettled;
|
|
1354
|
+
/**
|
|
1355
|
+
* The block's structure changed after it was read: read it again, under the id
|
|
1356
|
+
* its content now derives (an app's or a stamped id stays), stamp it, render it,
|
|
1357
|
+
* and register it if unknown. An id already sent for the earlier content stays
|
|
1358
|
+
* registered; the core cannot take a registration back.
|
|
1359
|
+
*/
|
|
1360
|
+
private rederive;
|
|
1361
|
+
/** Stop reacting to locale and catalog changes. Safe to call multiple times. */
|
|
415
1362
|
destroy(): void;
|
|
1363
|
+
/**
|
|
1364
|
+
* Take over every marked host inside this one that no instance manages
|
|
1365
|
+
* (MARK-2, MARK-3). The walk excises such a host from this unit (MARK-4), and
|
|
1366
|
+
* without an instance of its own it would never register: a phrase host
|
|
1367
|
+
* registers whole, as the one string its markup defines, and a block host
|
|
1368
|
+
* registers as its own block or, when it carries a stamped id, renders under
|
|
1369
|
+
* that id and registers nothing. Excluded subtrees are not entered, and a
|
|
1370
|
+
* marked host's own inside is its instance's to walk.
|
|
1371
|
+
*/
|
|
1372
|
+
private takeOverUnmanagedHosts;
|
|
416
1373
|
private translateUpdate;
|
|
1374
|
+
/**
|
|
1375
|
+
* An adopted host holds the text a renderer served, a translation when the
|
|
1376
|
+
* page was served in another locale, and the catalog is keyed by source. Its
|
|
1377
|
+
* source tokens come from the scope's seed, or the catalog entry the served text
|
|
1378
|
+
* was rendered from, never from the DOM (SRV-4): each text node and attribute is
|
|
1379
|
+
* given its source as the original it renders from. Until they are recovered the
|
|
1380
|
+
* host is left as served, which is right for the locale it was served in; a
|
|
1381
|
+
* later switch tries again, since a binding may hand the seed over after mount.
|
|
1382
|
+
*/
|
|
1383
|
+
/**
|
|
1384
|
+
* An adopted host re-rendered in another locale names that locale in its own
|
|
1385
|
+
* resolved marker, so the marker keeps describing the text it holds. A marker
|
|
1386
|
+
* with no locale (bare, `true`) is left as it is.
|
|
1387
|
+
*/
|
|
1388
|
+
private restampResolvedLocale;
|
|
1389
|
+
private recoverServedSources;
|
|
1390
|
+
/**
|
|
1391
|
+
* Render a single-token block.
|
|
1392
|
+
*
|
|
1393
|
+
* Prefers the content-block entry: the token may be stored as a block —
|
|
1394
|
+
* registered under an explicit `custom_id`, by an older SDK, or seeded
|
|
1395
|
+
* server-side — and flat `t()` cannot see inside block entries, so it would
|
|
1396
|
+
* report a miss for content that is present. Falls back to `t()`, which
|
|
1397
|
+
* also queues the miss for registration.
|
|
1398
|
+
*/
|
|
1399
|
+
private renderSingleToken;
|
|
1400
|
+
/**
|
|
1401
|
+
* Whether the single-token fast path may claim this element.
|
|
1402
|
+
*
|
|
1403
|
+
* "One token" is NOT the same question as "one text node", and treating
|
|
1404
|
+
* them as one was the defect. A translatable ATTRIBUTE is also a token, and
|
|
1405
|
+
* an element carrying only an attribute token — `<img alt="…">`,
|
|
1406
|
+
* `<input placeholder="…">` — has no text node anywhere in its subtree. The
|
|
1407
|
+
* fast path would then write the translation as the element's text, and the
|
|
1408
|
+
* element itself is gone.
|
|
1409
|
+
*
|
|
1410
|
+
* So the fast path requires an actual text node to write into. Everything
|
|
1411
|
+
* else goes to the node-walking path, which already translates attributes
|
|
1412
|
+
* in place and recurses, so a token on a nested element is reached too.
|
|
1413
|
+
*/
|
|
1414
|
+
private usesSingleTextNodeFastPath;
|
|
1415
|
+
/** A unit whose one token is a marked run is a phrase too (VAR-3, TOK-6): one sentence, with its values. */
|
|
1416
|
+
private isSingleRun;
|
|
1417
|
+
/** Whether this unit registers: not with `register: false`, and not when it is made only of markers (VAR-3, VAR-7). */
|
|
1418
|
+
private registers;
|
|
1419
|
+
/**
|
|
1420
|
+
* The unique non-whitespace text node under `root`, or `null` when there is
|
|
1421
|
+
* none or more than one — in which case the caller falls back to a flat
|
|
1422
|
+
* text render, since there is no single place to put the result.
|
|
1423
|
+
*/
|
|
1424
|
+
/**
|
|
1425
|
+
* Publish the resolved `custom_id` on the host element.
|
|
1426
|
+
*
|
|
1427
|
+
* A server-rendered page is otherwise unreadable: the id is derivable only
|
|
1428
|
+
* by re-running the tokenizer over the same subtree, which a reader holding
|
|
1429
|
+
* just the HTML cannot do identically — and the tokenizer's own rules are
|
|
1430
|
+
* what decide it. Stamping it makes the block self-describing, the same way
|
|
1431
|
+
* a `Phrase` host already carries `PHRASE_MARKER_ATTR`.
|
|
1432
|
+
*
|
|
1433
|
+
* Written on every resolution, including when the migration fallback adopts
|
|
1434
|
+
* a legacy id, so the attribute always names the id actually in use rather
|
|
1435
|
+
* than the one first derived.
|
|
1436
|
+
*/
|
|
1437
|
+
private stampContentBlockMarker;
|
|
1438
|
+
/** The unit's first marked run, where its single-run token comes from. */
|
|
1439
|
+
private findRun;
|
|
1440
|
+
/** The node a run is known by: its first text node or marker. */
|
|
1441
|
+
private runKey;
|
|
1442
|
+
/** A run text node's text before any render wrote it. */
|
|
1443
|
+
private runOriginal;
|
|
1444
|
+
/** A marked value: the text the framework shows now, or, where a render emptied it, what it showed. */
|
|
1445
|
+
private markedValue;
|
|
1446
|
+
/** Write `template` into a marked run, around its values where it can (see `renderRun`). */
|
|
1447
|
+
private writeRun;
|
|
1448
|
+
/** A marked run inside a block: its translation under the block's id, else its source. */
|
|
1449
|
+
private translateRun;
|
|
1450
|
+
private findSingleTextNode;
|
|
417
1451
|
private tokenizeContent;
|
|
418
1452
|
private handleContentBlock;
|
|
419
1453
|
private translate;
|
|
@@ -427,15 +1461,185 @@ declare class Translate {
|
|
|
427
1461
|
/**
|
|
428
1462
|
* Interpolate `{name}`-style params into a resolved text, matching `t()`:
|
|
429
1463
|
* unknown keys fall through untouched, and the untranslated fallback is
|
|
430
|
-
* interpolated too.
|
|
1464
|
+
* interpolated too. Runs without params as well, since a text carrying ICU
|
|
1465
|
+
* renders its `other` branch rather than its raw source (ICU-1). Plain text,
|
|
1466
|
+
* braces included, comes back exactly as written.
|
|
431
1467
|
*/
|
|
432
1468
|
private applyParams;
|
|
433
1469
|
private translateAttributes;
|
|
434
1470
|
private translateAttribute;
|
|
435
1471
|
}
|
|
436
1472
|
|
|
437
|
-
/**
|
|
438
|
-
|
|
1473
|
+
/**
|
|
1474
|
+
* Framework-agnostic content-block helpers.
|
|
1475
|
+
*
|
|
1476
|
+
* The DOM-mutating `Translate` class in `translate.ts` is one consumer of
|
|
1477
|
+
* these — it walks a live element, tokenizes it, registers the content
|
|
1478
|
+
* block, and mutates the same element in place. That model fits Svelte
|
|
1479
|
+
* and vanilla JS, where the SDK genuinely owns the DOM node.
|
|
1480
|
+
*
|
|
1481
|
+
* For React/Vue/Angular wrappers, mutating the DOM directly is an anti-
|
|
1482
|
+
* pattern — those frameworks own their rendered output. Wrappers should
|
|
1483
|
+
* import the helpers below to handle the framework-agnostic parts (token
|
|
1484
|
+
* discovery, content-block registration, dedup against the local cache)
|
|
1485
|
+
* and render translated content through their framework's native means.
|
|
1486
|
+
*
|
|
1487
|
+
* Wrapper pattern:
|
|
1488
|
+
*
|
|
1489
|
+
* 1. After mount, grab the rendered DOM via the framework's ref/binding.
|
|
1490
|
+
* 2. `const { tokens, content } = tokenizeElement(el);`
|
|
1491
|
+
* 3. `const customId = generateCustomId(category, tokens);`
|
|
1492
|
+
* 4. `if (!isContentBlockKnown(category, customId)) {`
|
|
1493
|
+
* ` await registerContentBlock({ custom_id: customId, category, content, tokens, label });`
|
|
1494
|
+
* `}`
|
|
1495
|
+
* 5. Subscribe to `sTranslations` + `currentlyLoadedLocale`; on change,
|
|
1496
|
+
* look up each token via `Translations.lookupContent(category, customId, token)`
|
|
1497
|
+
* and render via your framework's templating.
|
|
1498
|
+
*/
|
|
1499
|
+
|
|
1500
|
+
/**
|
|
1501
|
+
* Attributes that mark a subtree as a self-managed "keep-together" phrase,
|
|
1502
|
+
* which the block tokenizer must skip rather than split at tag boundaries.
|
|
1503
|
+
*
|
|
1504
|
+
* Two spellings, because two SDKs mark the same thing:
|
|
1505
|
+
* - `data-ls-phrase` — set by this SDK's framework `<Phrase>` components.
|
|
1506
|
+
* - `data-langsys-phrase` — `langsys-php`'s author-facing marker, which
|
|
1507
|
+
* survives into `translatePage()` output.
|
|
1508
|
+
*
|
|
1509
|
+
* The distinction is invisible in normal use, but on SSR handoff there is only
|
|
1510
|
+
* ONE DOM: a page rendered by PHP and then hydrated by a JS SDK is walked by
|
|
1511
|
+
* both implementations, so this tokenizer has to recognise PHP's marker or it
|
|
1512
|
+
* re-tokenizes a subtree PHP deliberately kept whole. Recognising both is
|
|
1513
|
+
* additive — `data-langsys-phrase` never appears in DOM our components emit.
|
|
1514
|
+
*/
|
|
1515
|
+
/**
|
|
1516
|
+
* True when an element is marked as a self-managed phrase by either SDK.
|
|
1517
|
+
*
|
|
1518
|
+
* Presence alone means intent, like any boolean HTML attribute — but an
|
|
1519
|
+
* explicit `="false"` or `="0"` opts OUT, compared after trimming. Mirrors
|
|
1520
|
+
* `langsys-php`'s `isPhraseMarked()` exactly (same method name in both SDKs,
|
|
1521
|
+
* so the correspondence is checkable at a glance). Without the opt-out we
|
|
1522
|
+
* would skip a subtree the author had deliberately un-marked, and it would
|
|
1523
|
+
* then be translated by neither SDK.
|
|
1524
|
+
*/
|
|
1525
|
+
declare function isPhraseMarked(element: Element): boolean;
|
|
1526
|
+
/**
|
|
1527
|
+
* True iff the local translations cache already has the content block
|
|
1528
|
+
* for the given `(category, customId)` — meaning the backend knows about
|
|
1529
|
+
* it and there's no need to re-register.
|
|
1530
|
+
*
|
|
1531
|
+
* The cache is keyed under `'__uncategorized__'` for null/empty categories,
|
|
1532
|
+
* matching the convention the server uses in its GET /translations response.
|
|
1533
|
+
*/
|
|
1534
|
+
declare function isContentBlockKnown(category: string, customId: string): boolean;
|
|
1535
|
+
/**
|
|
1536
|
+
* Why a registration did not reach the server, or did not take (REG-10).
|
|
1537
|
+
*
|
|
1538
|
+
* - `not-configured`: the client has no credentials, so nothing can be sent
|
|
1539
|
+
* (WIRE-4);
|
|
1540
|
+
* - `not-write-enabled`: the session may not write, so the block was handed to
|
|
1541
|
+
* the discovery lane instead;
|
|
1542
|
+
* - `catalog-unavailable`: the catalog fetch failed, so an unknown block cannot
|
|
1543
|
+
* be told from a registered one (WIRE-4);
|
|
1544
|
+
* - `base-locale-gate`: the project registers only at its base locale (GATE-9);
|
|
1545
|
+
* - `deferred`: the block was found inside a request scope, which holds it for its
|
|
1546
|
+
* flush after the response (SRV-3, SRV-7);
|
|
1547
|
+
* - `client-strategy`: the SSR token strategy leaves collection to the client
|
|
1548
|
+
* (SSR-1), or a write grant made capability per-user (SSR-2);
|
|
1549
|
+
* - `refused`: the server answered the request with a failure;
|
|
1550
|
+
* - `failed`: the request did not complete.
|
|
1551
|
+
*/
|
|
1552
|
+
type RegistrationReason = 'not-configured' | 'not-write-enabled' | 'catalog-unavailable' | 'base-locale-gate' | 'deferred' | 'client-strategy' | 'refused' | 'failed';
|
|
1553
|
+
/**
|
|
1554
|
+
* What a registration did. Success only when the server stored the block. A
|
|
1555
|
+
* write skipped on purpose is `{ status: false, skipped: true, reason }`, and one
|
|
1556
|
+
* that failed is `{ status: false, reason, errors }`, so a caller can tell them
|
|
1557
|
+
* apart and neither reads as success.
|
|
1558
|
+
*/
|
|
1559
|
+
type RegistrationResult = {
|
|
1560
|
+
status: true;
|
|
1561
|
+
} | {
|
|
1562
|
+
status: false;
|
|
1563
|
+
skipped: true;
|
|
1564
|
+
reason: 'not-configured' | 'not-write-enabled' | 'catalog-unavailable' | 'base-locale-gate' | 'deferred' | 'client-strategy';
|
|
1565
|
+
} | {
|
|
1566
|
+
status: false;
|
|
1567
|
+
skipped?: false;
|
|
1568
|
+
reason: 'refused' | 'failed';
|
|
1569
|
+
errors?: unknown[];
|
|
1570
|
+
};
|
|
1571
|
+
/**
|
|
1572
|
+
* POST a content block to the backend, then stamp its custom_id into the local
|
|
1573
|
+
* translations cache, so later mounts in this session, and later reloads since
|
|
1574
|
+
* the cache is persisted, skip the POST.
|
|
1575
|
+
*
|
|
1576
|
+
* A block has one request in flight at a time under one set of credentials
|
|
1577
|
+
* (REG-7). A call for a block whose request has not answered yet resolves with
|
|
1578
|
+
* that request's result and sends nothing: a binding calls `registerBlock` on
|
|
1579
|
+
* every change `t` goes through, and a page load makes several of those before
|
|
1580
|
+
* the first request returns. Once it answers, the block is known, or, refused or
|
|
1581
|
+
* failed, may be sent again. A request answers only for the project and key it
|
|
1582
|
+
* went out under, so a call under others, after a re-init, sends its own.
|
|
1583
|
+
*
|
|
1584
|
+
* One behaviour on every path (REG-10): never throws, always logs, and never
|
|
1585
|
+
* reports success for work that did not happen. A skipped write returns a
|
|
1586
|
+
* non-success result naming its reason.
|
|
1587
|
+
*/
|
|
1588
|
+
declare function registerContentBlock(contentBlock: iContentBlock): Promise<RegistrationResult>;
|
|
1589
|
+
/**
|
|
1590
|
+
* Walk an `HTMLElement`, collecting tokens for translation and producing a
|
|
1591
|
+
* cloned-and-stylized snapshot of its content for the Translation Manager.
|
|
1592
|
+
*
|
|
1593
|
+
* Tokens harvested:
|
|
1594
|
+
* - text-node content (whitespace-normalized, trimmed)
|
|
1595
|
+
* - translatable attribute values (`placeholder`, `alt`, `title`, `aria-*`, etc.)
|
|
1596
|
+
* - button/input/select value text
|
|
1597
|
+
*
|
|
1598
|
+
* Skips subtrees marked with `translate="no"`.
|
|
1599
|
+
*
|
|
1600
|
+
* The returned `content` is a string snapshot of the element's children, as the
|
|
1601
|
+
* tree path registers a block's nodes: the host is the unit, not part of its
|
|
1602
|
+
* content. The live element is not mutated. Computed semantic styles (color,
|
|
1603
|
+
* font, spacing, etc.) are inlined onto the clone so translators see content
|
|
1604
|
+
* styled the way end-users do. It holds no value and no nested unit's content
|
|
1605
|
+
* (`scrubContent`).
|
|
1606
|
+
*
|
|
1607
|
+
* Requires that `element` is mounted in the document so `getComputedStyle`
|
|
1608
|
+
* can read its style. For SSR / pre-mount callers, the style capture step
|
|
1609
|
+
* silently no-ops and the snapshot is plain (without inlined styles).
|
|
1610
|
+
*/
|
|
1611
|
+
declare function tokenizeElement(element: HTMLElement): {
|
|
1612
|
+
tokens: string[];
|
|
1613
|
+
content: string;
|
|
1614
|
+
vars: UnitVars;
|
|
1615
|
+
};
|
|
1616
|
+
/**
|
|
1617
|
+
* The token list this element would have produced before 0.6.3, when every
|
|
1618
|
+
* `<option>`'s text was harvested twice — once by a `<select>` special case and
|
|
1619
|
+
* again by the ordinary text-node walk.
|
|
1620
|
+
*
|
|
1621
|
+
* **Lookup only.** Content blocks registered by an older SDK are keyed by an id
|
|
1622
|
+
* derived from this list, so `Translate` falls back to it when the corrected id
|
|
1623
|
+
* misses; without that, every block containing a `<select>` would silently
|
|
1624
|
+
* re-key and lose its translations on upgrade. Registration always uses the
|
|
1625
|
+
* corrected list, so the legacy-keyed population can only shrink.
|
|
1626
|
+
*
|
|
1627
|
+
* Skips the computed-style capture that `tokenizeElement` performs — this is a
|
|
1628
|
+
* cache-key computation, not a snapshot for translators.
|
|
1629
|
+
*
|
|
1630
|
+
* @deprecated Migration aid; will be removed once catalogs have been rebased.
|
|
1631
|
+
*/
|
|
1632
|
+
declare function legacyTokenizeElement(element: HTMLElement): string[];
|
|
1633
|
+
/** What the walk learns from value markers (VAR-3), alongside the tokens. */
|
|
1634
|
+
interface UnitVars {
|
|
1635
|
+
/** Each marker's value, by name: the params the unit renders with, unless the caller names them. */
|
|
1636
|
+
values: Record<string, string>;
|
|
1637
|
+
/** The indices of tokens that are marked runs. */
|
|
1638
|
+
runs: number[];
|
|
1639
|
+
/** The indices of tokens made only of markers, with no text of their own. */
|
|
1640
|
+
markerOnly: number[];
|
|
1641
|
+
}
|
|
1642
|
+
|
|
439
1643
|
interface PhraseOptions {
|
|
440
1644
|
/** Category the phrase registers under (disambiguation for translators). */
|
|
441
1645
|
category?: string;
|
|
@@ -448,6 +1652,12 @@ interface PhraseOptions {
|
|
|
448
1652
|
* normalized back to `{n}` at capture.
|
|
449
1653
|
*/
|
|
450
1654
|
params?: Record<string, ParamPrimitive>;
|
|
1655
|
+
/**
|
|
1656
|
+
* `false` renders from the catalog and registers nothing (VAR-7): for a binding
|
|
1657
|
+
* whose phrase holds values from variables it cannot name without its build-time
|
|
1658
|
+
* transform. One debug notice says so. Default `true`.
|
|
1659
|
+
*/
|
|
1660
|
+
register?: boolean;
|
|
451
1661
|
}
|
|
452
1662
|
/**
|
|
453
1663
|
* DOM handler for a markup-bearing "rich" phrase (`<Phrase>`).
|
|
@@ -465,13 +1675,37 @@ declare class Phrase {
|
|
|
465
1675
|
private host;
|
|
466
1676
|
private category;
|
|
467
1677
|
private params;
|
|
1678
|
+
private register;
|
|
1679
|
+
/** Each marked value in the host (VAR-3), by name: params the caller's own override. */
|
|
1680
|
+
private values;
|
|
468
1681
|
private phrase;
|
|
469
1682
|
private slots;
|
|
1683
|
+
/** The key a phrase holding an opaque element was registered under before TOK-1: looked up on a miss, never registered. */
|
|
1684
|
+
private earlier;
|
|
1685
|
+
/**
|
|
1686
|
+
* A phrase made only of value markers (VAR-3) or of opaque elements (TOK-1) has no
|
|
1687
|
+
* text of its own, and registers nothing, on mount or on a navigation.
|
|
1688
|
+
*/
|
|
1689
|
+
private textless;
|
|
1690
|
+
/** The text of each opaque element that takes params, as the page gave it: where a caller's param can land (TOK-1). */
|
|
1691
|
+
private opaqueTexts;
|
|
1692
|
+
/**
|
|
1693
|
+
* The markup slot each element in the host renders. An element's place is its
|
|
1694
|
+
* slot only while the host shows the source's order; a translation that
|
|
1695
|
+
* reorders its markup, served or rendered here, moves them.
|
|
1696
|
+
*/
|
|
1697
|
+
private slotOf;
|
|
1698
|
+
/** A served translation whose source phrase is not recovered yet (see `recoverServed`). */
|
|
1699
|
+
private servedUnrecovered;
|
|
470
1700
|
private unsubscribers;
|
|
471
1701
|
private ready;
|
|
472
1702
|
/** Sorted params key-set already checked, so value-only updates don't re-warn. */
|
|
473
1703
|
private checkedParamKeys;
|
|
474
|
-
|
|
1704
|
+
/**
|
|
1705
|
+
* @param byWalk Internal: set when an enclosing walk creates this instance for
|
|
1706
|
+
* a host nothing managed, so an instance the author constructs replaces it.
|
|
1707
|
+
*/
|
|
1708
|
+
constructor(host: HTMLElement, options?: PhraseOptions, byWalk?: boolean);
|
|
475
1709
|
/** Update interpolation params (e.g. a changed count) and re-render. */
|
|
476
1710
|
setParams(params: Record<string, ParamPrimitive> | undefined): void;
|
|
477
1711
|
/** Stop reacting to locale/translation changes. Safe to call multiple times. */
|
|
@@ -483,6 +1717,15 @@ declare class Phrase {
|
|
|
483
1717
|
* re-warn on every render.
|
|
484
1718
|
*/
|
|
485
1719
|
private _checkParams;
|
|
1720
|
+
/**
|
|
1721
|
+
* Inside a resolved scope the host holds a translation a server served, and the
|
|
1722
|
+
* catalog is keyed by source: the source phrase is the one catalog entry that
|
|
1723
|
+
* renders as the served text (`recoverPhraseSource`), never the DOM's text. Its
|
|
1724
|
+
* elements are mapped to the source's slots by the order the served translation
|
|
1725
|
+
* opened them. Until it is recovered the host renders from its served text,
|
|
1726
|
+
* which is right for the locale it was served in.
|
|
1727
|
+
*/
|
|
1728
|
+
private recoverServed;
|
|
486
1729
|
private _render;
|
|
487
1730
|
}
|
|
488
1731
|
|
|
@@ -514,6 +1757,12 @@ declare class Phrase {
|
|
|
514
1757
|
interface RichSlot {
|
|
515
1758
|
/** Shallow clone of the original element — preserves tag, class, href, etc. */
|
|
516
1759
|
template: HTMLElement;
|
|
1760
|
+
/**
|
|
1761
|
+
* For an element opaque in the phrase (TOK-1), the element itself: it renders its
|
|
1762
|
+
* own content as given, so reconstitution moves it rather than a copy, and what a
|
|
1763
|
+
* framework renders inside it stays the framework's.
|
|
1764
|
+
*/
|
|
1765
|
+
source?: HTMLElement;
|
|
517
1766
|
}
|
|
518
1767
|
interface EncodedRichText {
|
|
519
1768
|
/**
|
|
@@ -523,6 +1772,18 @@ interface EncodedRichText {
|
|
|
523
1772
|
phrase: string;
|
|
524
1773
|
/** Captured inline elements, indexed by markup-token slot number. */
|
|
525
1774
|
slots: RichSlot[];
|
|
1775
|
+
/** Each marked value, by name (VAR-3): the params the phrase renders with, unless the caller names them. */
|
|
1776
|
+
values: Record<string, string>;
|
|
1777
|
+
/**
|
|
1778
|
+
* For a phrase holding an opaque element, the key it was registered under before
|
|
1779
|
+
* TOK-1 bound this path, with that key's slots and values: looked up on a miss for
|
|
1780
|
+
* `phrase`, never registered.
|
|
1781
|
+
*/
|
|
1782
|
+
earlier?: {
|
|
1783
|
+
phrase: string;
|
|
1784
|
+
slots: RichSlot[];
|
|
1785
|
+
values: Record<string, string>;
|
|
1786
|
+
};
|
|
526
1787
|
}
|
|
527
1788
|
/**
|
|
528
1789
|
* Walk an element's subtree and produce the encoded phrase + captured slots.
|
|
@@ -530,6 +1791,24 @@ interface EncodedRichText {
|
|
|
530
1791
|
* Text nodes contribute their (whitespace-collapsed) text. Each child element
|
|
531
1792
|
* becomes a markup-token pair wrapping its recursively-encoded contents, and a
|
|
532
1793
|
* shallow clone of the element is captured as a slot. Nesting is preserved.
|
|
1794
|
+
*
|
|
1795
|
+
* COALESCING ADJACENT TEXT NODES IS CORRECT HERE — do not "fix" it.
|
|
1796
|
+
*
|
|
1797
|
+
* `_encodeNodes` concatenates adjacent text nodes and this function collapses
|
|
1798
|
+
* whitespace, producing ONE phrase string. That string is the lookup key:
|
|
1799
|
+
* `Phrase` calls `Translations.t(phrase, category)` and never computes a
|
|
1800
|
+
* `custom_id`. A sentence must survive the round trip whole — that is the
|
|
1801
|
+
* entire reason `<Phrase>` exists, since splitting `Based on {n}
|
|
1802
|
+
* <strong>reviews</strong>` into fragments makes correct agreement impossible
|
|
1803
|
+
* in languages with more than two plural forms.
|
|
1804
|
+
*
|
|
1805
|
+
* The `_walkForTokens` contract in `content-block.ts` says the OPPOSITE, for
|
|
1806
|
+
* the content-block path, where token-array arity is identity. Both are right
|
|
1807
|
+
* for their own path. If you arrived here from that comment intending to make
|
|
1808
|
+
* this consistent with it, the paths are not meant to agree.
|
|
1809
|
+
*
|
|
1810
|
+
* Pinned by `tests/content-block-identity.test.ts` ("Phrase path"), which
|
|
1811
|
+
* fails if this coalescing is removed.
|
|
533
1812
|
*/
|
|
534
1813
|
declare function encodeRichText(root: HTMLElement): EncodedRichText;
|
|
535
1814
|
/**
|
|
@@ -538,340 +1817,306 @@ declare function encodeRichText(root: HTMLElement): EncodedRichText;
|
|
|
538
1817
|
* carries machine-findable delimiters for reconstitution.
|
|
539
1818
|
*/
|
|
540
1819
|
declare function markupTokenValues(slotCount: number): Record<string, string>;
|
|
541
|
-
|
|
542
|
-
* Rebuild DOM nodes from a resolved string containing sentinel-delimited spans.
|
|
543
|
-
*
|
|
544
|
-
* The original (framework-owned) elements are reused as wrappers — so scoped
|
|
545
|
-
* CSS classes / attributes survive — with the translated text placed inside.
|
|
546
|
-
*
|
|
547
|
-
* Robust to model mistakes: if sentinels are unbalanced (a tag dropped or a
|
|
548
|
-
* stray close), we fall back to a single plain-text node (markers stripped) —
|
|
549
|
-
* "lose the markup, keep the meaning" rather than throw.
|
|
550
|
-
*/
|
|
551
|
-
declare function reconstitute(resolved: string, slots: RichSlot[], doc?: Document): Node[];
|
|
1820
|
+
declare function reconstitute(resolved: string, slots: RichSlot[], doc?: Document, onElement?: (element: HTMLElement, slot: number) => void): Node[];
|
|
552
1821
|
/** Remove any sentinel markers from a string, leaving the bare text. */
|
|
553
1822
|
declare function stripSentinels(value: string): string;
|
|
554
1823
|
|
|
555
|
-
declare class LangsysAppAPIClass {
|
|
556
|
-
private apiurl;
|
|
557
|
-
config: iLangsysConfig;
|
|
558
|
-
private headers;
|
|
559
|
-
constructor();
|
|
560
|
-
setup(config: iLangsysConfig): void;
|
|
561
|
-
/** Optional hook to point the client at a different API host. */
|
|
562
|
-
setBaseUrl(url: string): void;
|
|
563
|
-
validate(config: iLangsysConfig): Promise<ResponseObject>;
|
|
564
|
-
getTranslations(locale: string): Promise<ResponseObject>;
|
|
565
|
-
/**
|
|
566
|
-
* Register phrases and/or content blocks via the current unified endpoint.
|
|
567
|
-
* Each item carries `type: 'phrase' | 'content_block'`. Replaces the
|
|
568
|
-
* deprecated `projects/{id}/tokens` and `projects/{id}/content-blocks` routes.
|
|
569
|
-
*/
|
|
570
|
-
createTranslatableItems(items: Array<Record<string, unknown>>): Promise<ResponseObject>;
|
|
571
|
-
post(path: string, data?: Record<string, unknown>): Promise<ResponseObject>;
|
|
572
|
-
get(path: string, data?: Record<string, unknown>): Promise<ResponseObject>;
|
|
573
|
-
delete(path: string, data?: Record<string, unknown>): Promise<ResponseObject>;
|
|
574
|
-
patch(path: string, data?: Record<string, unknown>): Promise<ResponseObject>;
|
|
575
|
-
put(path: string, data?: Record<string, unknown>): Promise<ResponseObject>;
|
|
576
|
-
private send;
|
|
577
|
-
}
|
|
578
|
-
declare const LangsysAppAPI: LangsysAppAPIClass;
|
|
579
|
-
|
|
580
|
-
interface iContentBlock {
|
|
581
|
-
custom_id: string;
|
|
582
|
-
category: string;
|
|
583
|
-
content: string;
|
|
584
|
-
label?: string;
|
|
585
|
-
tokens: string[];
|
|
586
|
-
}
|
|
587
|
-
|
|
588
1824
|
/**
|
|
589
|
-
*
|
|
1825
|
+
* Minimal synchronous storage contract for `persist` — structurally a subset of
|
|
1826
|
+
* the DOM `Storage` interface, so `localStorage` satisfies it as-is.
|
|
590
1827
|
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
* For React/Vue/Angular wrappers, mutating the DOM directly is an anti-
|
|
597
|
-
* pattern — those frameworks own their rendered output. Wrappers should
|
|
598
|
-
* import the helpers below to handle the framework-agnostic parts (token
|
|
599
|
-
* discovery, content-block registration, dedup against the local cache)
|
|
600
|
-
* and render translated content through their framework's native means.
|
|
601
|
-
*
|
|
602
|
-
* Wrapper pattern:
|
|
603
|
-
*
|
|
604
|
-
* 1. After mount, grab the rendered DOM via the framework's ref/binding.
|
|
605
|
-
* 2. `const { tokens, content } = tokenizeElement(el);`
|
|
606
|
-
* 3. `const customId = generateCustomId(category, tokens);`
|
|
607
|
-
* 4. `if (!isContentBlockKnown(category, customId)) {`
|
|
608
|
-
* ` await registerContentBlock({ custom_id: customId, category, content, tokens, label });`
|
|
609
|
-
* `}`
|
|
610
|
-
* 5. Subscribe to `sTranslations` + `currentlyLoadedLocale`; on change,
|
|
611
|
-
* look up each token via `Translations.lookupContent(category, customId, token)`
|
|
612
|
-
* and render via your framework's templating.
|
|
1828
|
+
* Synchronous on purpose: `persist` seeds its signal during module
|
|
1829
|
+
* initialization, so an async backend could not be read in time. React Native
|
|
1830
|
+
* apps inject a synchronous adapter (MMKV, or an in-memory mirror of
|
|
1831
|
+
* AsyncStorage hydrated at startup) via `setPersistStorage`.
|
|
613
1832
|
*/
|
|
614
|
-
|
|
1833
|
+
interface PersistStorage {
|
|
1834
|
+
getItem(key: string): string | null;
|
|
1835
|
+
setItem(key: string, value: string): void;
|
|
1836
|
+
/** Optional. Used to drop superseded keys; absent on minimal adapters. */
|
|
1837
|
+
removeItem?(key: string): void;
|
|
1838
|
+
}
|
|
615
1839
|
/**
|
|
616
|
-
*
|
|
617
|
-
*
|
|
1840
|
+
* Inject the storage backend used by every `persist` signal — those created
|
|
1841
|
+
* afterwards AND those that already exist.
|
|
618
1842
|
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
1843
|
+
* Re-hydrating the existing ones is the point, not a nicety: the catalog store
|
|
1844
|
+
* is built at module load, long before a React Native app can hand us MMKV, so
|
|
1845
|
+
* an injection that only applied going forward would apply to none of the
|
|
1846
|
+
* signals anyone cares about.
|
|
623
1847
|
*
|
|
624
|
-
*
|
|
625
|
-
* ONE DOM: a page rendered by PHP and then hydrated by a JS SDK is walked by
|
|
626
|
-
* both implementations, so this tokenizer has to recognise PHP's marker or it
|
|
627
|
-
* re-tokenizes a subtree PHP deliberately kept whole. Recognising both is
|
|
628
|
-
* additive — `data-langsys-phrase` never appears in DOM our components emit.
|
|
1848
|
+
* Pass `null` to drop the injection and return to auto-detection.
|
|
629
1849
|
*/
|
|
630
|
-
declare
|
|
1850
|
+
declare function setPersistStorage(storage: PersistStorage | null): void;
|
|
631
1851
|
/**
|
|
632
|
-
*
|
|
633
|
-
*
|
|
1852
|
+
* Create a Signal whose value is persisted to storage when one is available: an
|
|
1853
|
+
* injected `PersistStorage` first, else `localStorage`. Falls back to in-memory
|
|
1854
|
+
* behaviour during SSR, or when storage is unavailable or quota-exceeded.
|
|
1855
|
+
* Stored value is JSON-encoded.
|
|
634
1856
|
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
*
|
|
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.
|
|
1857
|
+
* `legacyKey` is READ but never written, so a renamed key migrates its cache on
|
|
1858
|
+
* first load instead of silently dropping it. A host app that downgrades the
|
|
1859
|
+
* SDK still finds its old value where it left it.
|
|
641
1860
|
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
*
|
|
1861
|
+
* DO NOT persist `writeEnabled`, `autoDiscovery`, or any other session
|
|
1862
|
+
* capability. "Cache it like translations for a faster warm start" is the
|
|
1863
|
+
* obvious next optimization and it is wrong twice over:
|
|
645
1864
|
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
* True when an element is marked as a self-managed phrase by either SDK.
|
|
1865
|
+
* 1. A persisted value is restored SYNCHRONOUSLY at module init, so the client
|
|
1866
|
+
* would mount with a concrete capability while the server rendered from
|
|
1867
|
+
* `undefined` — a hydration mismatch on every consumer, not just the ones
|
|
1868
|
+
* that resolve auth before first render.
|
|
1869
|
+
* 2. Write capability is per-session and IP-dependent: the same key answers
|
|
1870
|
+
* differently from different networks, and a grant makes it per-user.
|
|
1871
|
+
* Persisting it caches one session's answer across later sessions on a
|
|
1872
|
+
* shared device — the same unsoundness as caching it on an SSR singleton.
|
|
655
1873
|
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
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.
|
|
1874
|
+
* Translations are safe to persist because they are project-scoped content, not
|
|
1875
|
+
* a permission decision. Capability is re-derived from the server every time.
|
|
662
1876
|
*/
|
|
663
|
-
declare function
|
|
1877
|
+
declare function persist<T>(key: string, initial: T, legacyKey?: string): Signal<T>;
|
|
1878
|
+
|
|
664
1879
|
/**
|
|
665
|
-
*
|
|
1880
|
+
* A host's own "the app is going away" signal, for runtimes with no `document` to listen to.
|
|
666
1881
|
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
*
|
|
1882
|
+
* In a browser the core flushes queued registrations itself, on `visibilitychange → hidden` and
|
|
1883
|
+
* `pagehide` (REG-3). React Native defines `window` but has no `document`, so those listeners
|
|
1884
|
+
* cannot exist there, and anything still queued when the app is backgrounded or killed would be
|
|
1885
|
+
* lost. `setTeardownSignal` lets the host supply the signal instead: the React Native binding
|
|
1886
|
+
* adapts `AppState` leaving `active`, and the core keeps the send.
|
|
670
1887
|
*
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
* whether to call `registerContentBlock`.
|
|
1888
|
+
* Same shape as `setPersistStorage`: a module-level injection that reaches every `Translations`
|
|
1889
|
+
* instance, those set up before it and after.
|
|
674
1890
|
*/
|
|
675
|
-
declare function generateCustomId(category: string, tokens: string[]): string;
|
|
676
1891
|
/**
|
|
677
|
-
*
|
|
678
|
-
*
|
|
679
|
-
* **Lookup only — never register under this.** Blocks registered by an older
|
|
680
|
-
* SDK are keyed by it, so `Translate` falls back to it when the corrected id
|
|
681
|
-
* misses, which keeps existing translations resolving instead of orphaning
|
|
682
|
-
* them. Registering under it would keep minting ids from a hash that both
|
|
683
|
-
* diverges across SDKs and can collide.
|
|
684
|
-
*
|
|
685
|
-
* Note the collision is a property of the FINAL hashed string, not the phrase:
|
|
686
|
-
* `JSON.stringify` shifts every character's offset, so the same phrase pair can
|
|
687
|
-
* collide standalone and not collide here — and changing `category` moves every
|
|
688
|
-
* character into different lanes. Always reason at this level, not at `md5()`.
|
|
689
|
-
*
|
|
690
|
-
* @deprecated Migration aid; will be removed once catalogs have been rebased.
|
|
1892
|
+
* Subscribe `fire` to the host's teardown signal and return a function that unsubscribes it.
|
|
1893
|
+
* The core calls `fire` nothing else: firing it runs the ordinary teardown flush.
|
|
691
1894
|
*/
|
|
692
|
-
|
|
1895
|
+
type TeardownSubscribe = (fire: () => void) => (() => void) | void;
|
|
693
1896
|
/**
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
1897
|
+
* Inject the host's teardown signal, replacing any previous one. The previous subscription is
|
|
1898
|
+
* released first, so a re-injection — React Native's Fast Refresh, a test — never leaves two
|
|
1899
|
+
* live subscriptions that would flush twice. `null` releases the current one and injects none.
|
|
697
1900
|
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
1901
|
+
* Fire it once per departure, edge-triggered, as the app leaves the foreground: the flush sends
|
|
1902
|
+
* what is queued, and firing again while nothing is queued sends nothing.
|
|
700
1903
|
*/
|
|
701
|
-
declare function
|
|
1904
|
+
declare function setTeardownSignal(subscribe: TeardownSubscribe | null): void;
|
|
1905
|
+
|
|
702
1906
|
/**
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
* subsequent reloads, since the cache is persisted — skip the POST.
|
|
1907
|
+
* Request scopes (spec SRV-7): rendering on a server without one visitor's
|
|
1908
|
+
* state reaching the next.
|
|
706
1909
|
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
1910
|
+
* The core keeps one catalog, one locale and one miss queue in module state,
|
|
1911
|
+
* which is right for a page and wrong for a server process rendering for many
|
|
1912
|
+
* visitors at once. A scope holds its own: its locale, its view of the catalog,
|
|
1913
|
+
* the misses its render records, and the seed its page hands the client. A
|
|
1914
|
+
* binding opens one in its framework's request hook, renders inside `run()`,
|
|
1915
|
+
* serialises `seed()` into the page, and calls `close()` after the response.
|
|
710
1916
|
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
1917
|
+
* A catalog is fetched at most once per request, and shared read-only (frozen)
|
|
1918
|
+
* with scopes rendering the same locale under the same credentials within a
|
|
1919
|
+
* minute, so a busy locale costs one fetch rather than one per visitor. Nothing a
|
|
1920
|
+
* scope writes is visible to another scope or to the page.
|
|
1921
|
+
*
|
|
1922
|
+
* A scope may carry its own project id and API key (SRV-2), for a process that
|
|
1923
|
+
* renders for more than one project: every request the scope makes goes out under
|
|
1924
|
+
* them, and a scope given none sends under the module-wide ones `init()` set.
|
|
713
1925
|
*/
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1926
|
+
interface RequestScopeOptions {
|
|
1927
|
+
/** The request's locale, as the framework or SRV-6 resolved it. */
|
|
1928
|
+
locale: string;
|
|
1929
|
+
/** A catalog already in hand (a snapshot, an upstream fetch); otherwise it is fetched. */
|
|
1930
|
+
catalog?: iCategories;
|
|
1931
|
+
/** The page's URL, for the records this scope keeps. */
|
|
1932
|
+
url?: string;
|
|
1933
|
+
/**
|
|
1934
|
+
* The project id and API key this scope's requests send under, with the write grant
|
|
1935
|
+
* that goes with them (SRV-2): its catalog fetch, its `close()`, and every request a
|
|
1936
|
+
* render inside it makes. Without them the scope sends under the module-wide ones.
|
|
1937
|
+
* Never part of `seed()`.
|
|
1938
|
+
*
|
|
1939
|
+
* Its key's write capability is the one its catalog fetch reports, so a scope handed
|
|
1940
|
+
* both a catalog and credentials of its own has no answer for its key and sends
|
|
1941
|
+
* nothing at `close()`; the client registers what it missed.
|
|
1942
|
+
*/
|
|
1943
|
+
credentials?: ApiCredentials;
|
|
1944
|
+
}
|
|
1945
|
+
/** A phrase a scope's render missed. */
|
|
1946
|
+
interface ScopeMiss {
|
|
1947
|
+
category: string;
|
|
1948
|
+
phrase: string;
|
|
1949
|
+
}
|
|
1950
|
+
interface RequestScope {
|
|
1951
|
+
readonly locale: string;
|
|
1952
|
+
/** `t()` over this scope's catalog; its misses are this scope's. */
|
|
1953
|
+
readonly t: TFunction;
|
|
1954
|
+
/**
|
|
1955
|
+
* Run `fn` inside the scope: top-level `t()`, `LangsysApp.t`, `tSignal.get()`,
|
|
1956
|
+
* and the lookups and registrations `Translate` and `Phrase` make, resolve to
|
|
1957
|
+
* it. Across `await` only once `setRequestScopeStorage` has an
|
|
1958
|
+
* `AsyncLocalStorage`; otherwise only code that finishes inside `fn`.
|
|
1959
|
+
*
|
|
1960
|
+
* The `t` that `tSignal.get()` returns reads the catalog when it is CALLED, not
|
|
1961
|
+
* when it was taken: kept and called after `fn` returns, it reads the page's.
|
|
1962
|
+
* A host wrapping `t` for its components returns `scope.t`, which is this
|
|
1963
|
+
* scope's wherever it is called.
|
|
1964
|
+
*/
|
|
1965
|
+
run<R>(fn: () => R): R;
|
|
1966
|
+
/**
|
|
1967
|
+
* Make the scope current for the rest of the current async context, where the
|
|
1968
|
+
* render cannot be wrapped in `run()`. Requires `setRequestScopeStorage` to
|
|
1969
|
+
* have been given an `AsyncLocalStorage`; throws otherwise.
|
|
1970
|
+
*
|
|
1971
|
+
* It holds only in the continuation that itself performs the render: called
|
|
1972
|
+
* inside a function the host awaits and returns from (a Nuxt plugin, a Nitro
|
|
1973
|
+
* request hook, an awaited helper), the scope is gone when that function
|
|
1974
|
+
* returns, before or after its own `await` alike. So call `enter()` in the
|
|
1975
|
+
* function that renders, `run()` everywhere else, and hand the scope to the
|
|
1976
|
+
* app through the framework's own injection when the host awaits you.
|
|
1977
|
+
*/
|
|
1978
|
+
enter(): void;
|
|
1979
|
+
/**
|
|
1980
|
+
* The hydration seed (SRV-4): on the client, `LangsysApp.seedCatalog(seed.catalog,
|
|
1981
|
+
* seed.locale, seed)` before hydration. `blocks` names every block the scope
|
|
1982
|
+
* rendered through `renderBlock`, by id, with its category, source tokens and
|
|
1983
|
+
* shape, for a client that cannot recover a block's source from DOM already
|
|
1984
|
+
* holding the translation; hand each to `registerBlock` after `init()`.
|
|
1985
|
+
* `phrases` lists the phrases the render missed. A block or phrase the scope
|
|
1986
|
+
* sends itself at `close()` is marked `collected`, and the client never
|
|
1987
|
+
* registers it.
|
|
1988
|
+
*/
|
|
1989
|
+
seed(): RequestSeed;
|
|
1990
|
+
/** The phrases this scope's render missed. */
|
|
1991
|
+
misses(): readonly ScopeMiss[];
|
|
1992
|
+
/**
|
|
1993
|
+
* After the response (SRV-3): send what the render missed, when the key the
|
|
1994
|
+
* scope sends under may write and the SSR strategy collects on the server,
|
|
1995
|
+
* then stop recording. Resolves what happened; never throws. Calling it
|
|
1996
|
+
* again resolves the first result.
|
|
1997
|
+
*/
|
|
1998
|
+
close(): Promise<RegistrationResult>;
|
|
1999
|
+
}
|
|
718
2000
|
/**
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
* - button/input/select value text
|
|
726
|
-
*
|
|
727
|
-
* Skips subtrees marked with `translate="no"`.
|
|
728
|
-
*
|
|
729
|
-
* The returned `content` is a string snapshot — the live element is not
|
|
730
|
-
* mutated. Computed semantic styles (color, font, spacing, etc.) are inlined
|
|
731
|
-
* onto the clone so translators see content styled the way end-users do.
|
|
732
|
-
*
|
|
733
|
-
* Requires that `element` is mounted in the document so `getComputedStyle`
|
|
734
|
-
* can read its style. For SSR / pre-mount callers, the style capture step
|
|
735
|
-
* silently no-ops and the snapshot is plain (without inlined styles).
|
|
2001
|
+
* Open a request scope. Resolves once its catalog is in hand: the one passed,
|
|
2002
|
+
* one fetched within the last minute for the same credentials and locale, or a
|
|
2003
|
+
* fresh fetch. A fetch that fails leaves the scope rendering source text and
|
|
2004
|
+
* recording nothing, as the page does while its catalog is unavailable
|
|
2005
|
+
* (WIRE-4). Credentials without both a project id and a key reject with
|
|
2006
|
+
* `LangsysConfigurationError`, before anything is sent.
|
|
736
2007
|
*/
|
|
737
|
-
declare function
|
|
738
|
-
tokens: string[];
|
|
739
|
-
content: string;
|
|
740
|
-
};
|
|
2008
|
+
declare function createRequestScope(options: RequestScopeOptions): Promise<RequestScope>;
|
|
741
2009
|
/**
|
|
742
|
-
* The
|
|
743
|
-
*
|
|
744
|
-
*
|
|
2010
|
+
* The request scope a server rendered, rebuilt on the client from its seed, at
|
|
2011
|
+
* once and synchronously: for a host whose client render must run inside the
|
|
2012
|
+
* same scope as the server's (React's hydration render, which reads the server
|
|
2013
|
+
* snapshot), and whose provider cannot await. Its `t` reads the seed's catalog
|
|
2014
|
+
* in the seed's locale, so the hydration render produces the served text.
|
|
745
2015
|
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
* Skips the computed-style capture that `tokenizeElement` performs — this is a
|
|
753
|
-
* cache-key computation, not a snapshot for translators.
|
|
2016
|
+
* It sends nothing itself: the server's scope decided at its own `close()`. What
|
|
2017
|
+
* its render misses, and the blocks it registers, go to the page's lanes as a
|
|
2018
|
+
* render outside any scope would, which skip what the seed marks `collected`
|
|
2019
|
+
* (the seed's blocks and phrases are remembered here, as `seedCatalog` remembers
|
|
2020
|
+
* them), so under the `client` strategy the client still registers them.
|
|
754
2021
|
*
|
|
755
|
-
*
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
/**
|
|
760
|
-
* Create a Signal whose value is persisted to localStorage (when available).
|
|
761
|
-
* Falls back to in-memory behavior during SSR or when storage is unavailable
|
|
762
|
-
* or quota-exceeded. Stored value is JSON-encoded.
|
|
2022
|
+
* A host that wraps `t` for its components returns `scope.t`, never a `t` taken
|
|
2023
|
+
* from `tSignal.get()` inside `run()`: that one reads the catalog when it is
|
|
2024
|
+
* called, and called later in the render, outside `run()`, it reads the page's.
|
|
763
2025
|
*/
|
|
764
|
-
declare function
|
|
2026
|
+
declare function scopeFromSeed(seed: RequestSeed): RequestScope;
|
|
2027
|
+
/** The request scope the current code runs in, if any. */
|
|
2028
|
+
declare function currentRequestScope(): RequestScope | undefined;
|
|
2029
|
+
/** Forget every shared catalog, so the next scope for each locale fetches afresh. */
|
|
2030
|
+
declare function clearSharedCatalogs(): void;
|
|
765
2031
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
/**
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
* Content-block `custom_id`s are derived from this, so it's a wire identity —
|
|
774
|
-
* see `generateCustomId`. Before 0.6.0 this packed UTF-16 code units directly
|
|
775
|
-
* into byte lanes, which agreed with a real MD5 only for ASCII: non-ASCII
|
|
776
|
-
* input both diverged from every other Langsys SDK and could collide (a
|
|
777
|
-
* character's high byte was OR-ed into the neighbouring lane, vanishing
|
|
778
|
-
* whenever that lane already carried those bits — so `éa` and `ǩa` hashed
|
|
779
|
-
* alike). `md5Legacy` preserves the old behavior for migration lookups only.
|
|
780
|
-
*/
|
|
781
|
-
declare function md5(inputString: string): string;
|
|
2032
|
+
/** The ambient-context shape `AsyncLocalStorage` has, so the core never imports `node:async_hooks`. */
|
|
2033
|
+
interface ScopeStorage {
|
|
2034
|
+
getStore(): unknown;
|
|
2035
|
+
run<R>(store: unknown, fn: () => R): R;
|
|
2036
|
+
/** Makes a store current for the rest of the async context; needed only by `scope.enter()`. */
|
|
2037
|
+
enterWith?(store: unknown): void;
|
|
2038
|
+
}
|
|
782
2039
|
/**
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
2040
|
+
* Hand the core an `AsyncLocalStorage` (or anything of its shape), so a scope
|
|
2041
|
+
* entered with `scope.run()` stays current across `await` inside the render.
|
|
2042
|
+
* Pass `null` to go back to synchronous nesting.
|
|
786
2043
|
*
|
|
787
|
-
*
|
|
2044
|
+
* The storage is opt-in, and what it guarantees depends on the host's async
|
|
2045
|
+
* model: under zone.js, for one, promises do not carry it across concurrent
|
|
2046
|
+
* renders. A binding may instead carry the scope through its own dependency
|
|
2047
|
+
* injection, one scope per request, and read `scope.t` and the scope's values
|
|
2048
|
+
* directly, with no storage at all.
|
|
788
2049
|
*/
|
|
789
|
-
declare function
|
|
2050
|
+
declare function setRequestScopeStorage(value: ScopeStorage | null): void;
|
|
790
2051
|
|
|
791
2052
|
/**
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
796
|
-
*
|
|
797
|
-
* `{var, time}` — valid ICU with the locale-default style.
|
|
798
|
-
* - Doesn't match plain interpolation slots like `{name}` or `{count}`.
|
|
799
|
-
*
|
|
800
|
-
* Exported because the Translation Manager UI needs the same detection logic
|
|
801
|
-
* to decide whether to render a plural-slot editor vs. a single text field.
|
|
2053
|
+
* Report, as a debug notice once per process per reason, that a unit registers
|
|
2054
|
+
* nothing because the text it holds came from a variable that cannot be named
|
|
2055
|
+
* (VAR-7): `register: false` on a DOM class, or a binding without its build-time
|
|
2056
|
+
* transform. Its catalogued translations still render. The reason names the cause,
|
|
2057
|
+
* such as the transform to enable.
|
|
802
2058
|
*/
|
|
803
|
-
declare function
|
|
2059
|
+
declare function warnUnregistered(reason: string): void;
|
|
2060
|
+
|
|
804
2061
|
/**
|
|
805
|
-
*
|
|
806
|
-
*
|
|
807
|
-
*
|
|
808
|
-
*
|
|
809
|
-
* so DOM content accepts `%name%` as a collision-free authoring escape.
|
|
810
|
-
* Normalization runs at every markup capture boundary (content-block
|
|
811
|
-
* tokenizer, `Translate` original-value snapshots, `Phrase` encoding), so the
|
|
812
|
-
* catalog, the wire, and translators only ever see the canonical `{name}`
|
|
813
|
-
* form — and plain `{name}` keeps working for vanilla-HTML authors.
|
|
814
|
-
*
|
|
815
|
-
* Keys must be identifiers (`[A-Za-z_][A-Za-z0-9_]*`), so literal `%` in
|
|
816
|
-
* prose ("20% off", "50% to 60%") can't match. `t()` phrases are JS strings
|
|
817
|
-
* with no compiler collision and stay `{name}`-only.
|
|
2062
|
+
* The catalog. Inside a request scope `get()` answers with the scope's catalog,
|
|
2063
|
+
* as `t()` does, so a binding that reads the catalog through this signal on a
|
|
2064
|
+
* server renders the visitor's, never another request's. `set`, `update` and
|
|
2065
|
+
* `subscribe` are the page's.
|
|
818
2066
|
*/
|
|
819
|
-
declare
|
|
2067
|
+
declare const sTranslations: Signal<iCategories>;
|
|
820
2068
|
/**
|
|
821
|
-
*
|
|
822
|
-
*
|
|
823
|
-
* Texts are expected in canonical form (post-`normalizeMarkupPlaceholders`),
|
|
824
|
-
* so a key counts as used when it appears as `{key}` or as the argument of an
|
|
825
|
-
* ICU slot (`{key, plural, …}`). Surrounding whitespace is tolerated.
|
|
2069
|
+
* The locale the catalog is for. Inside a request scope `get()` answers with the
|
|
2070
|
+
* scope's locale (SRV-7), for the same reason as `sTranslations`.
|
|
826
2071
|
*/
|
|
827
|
-
declare
|
|
2072
|
+
declare const currentlyLoadedLocale: Signal<string>;
|
|
828
2073
|
/**
|
|
829
|
-
*
|
|
2074
|
+
* Server-computed write capability for THIS session. The single source of
|
|
2075
|
+
* truth for every write decision — never infer it from `key_type`, which
|
|
2076
|
+
* cannot express `ip_write` (read-only everywhere except from allow-listed
|
|
2077
|
+
* IPs, which is exactly how the discovery renderer registers content).
|
|
830
2078
|
*
|
|
831
|
-
*
|
|
832
|
-
* -
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
* -
|
|
837
|
-
* placeholders are left untouched so missing data is visible to the
|
|
838
|
-
* developer rather than silently rendering empty strings. Number and
|
|
839
|
-
* Date values are formatted per the target locale's CLDR rules
|
|
840
|
-
* (`Intl.NumberFormat` / `Intl.DateTimeFormat`) — pass numbers as
|
|
841
|
-
* strings to opt out (e.g. IDs and codes that must not get grouping
|
|
842
|
-
* separators).
|
|
2079
|
+
* Tri-state, and the three states are genuinely distinct:
|
|
2080
|
+
* - `undefined` — not yet authorized. HOLD: neither register nor hint.
|
|
2081
|
+
* Collapsing this to `false` would drop every miss that lands before
|
|
2082
|
+
* `validate()` resolves and fire a spurious discovery hint for it.
|
|
2083
|
+
* - `true` — register missing tokens (never hint).
|
|
2084
|
+
* - `false` — never attempt a write (no 403 churn); hint instead.
|
|
843
2085
|
*
|
|
844
|
-
*
|
|
845
|
-
* so the developer sees a broken-but-visible string instead of a runtime crash.
|
|
846
|
-
* Backend-side validation in langsys-ai is supposed to prevent this — the
|
|
847
|
-
* fall-through is defense in depth.
|
|
2086
|
+
* Refreshed from `authorize-project` at init and from every catalog fetch.
|
|
848
2087
|
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
2088
|
+
* BROWSER-AUTHORITATIVE: only ever written when `window` exists. Write
|
|
2089
|
+
* capability is per-session (it varies by requester IP and by write grant),
|
|
2090
|
+
* while this module is a process-wide singleton — in a long-lived SSR server
|
|
2091
|
+
* shared across every request and every user, caching one request's answer
|
|
2092
|
+
* here would apply it to every later visitor. Under SSR it stays `undefined`,
|
|
2093
|
+
* which is the honest answer rather than a leaked one.
|
|
851
2094
|
*/
|
|
852
|
-
declare
|
|
853
|
-
|
|
2095
|
+
declare const writeEnabled: Signal<boolean | undefined>;
|
|
854
2096
|
/**
|
|
855
|
-
*
|
|
856
|
-
* the platform's native `Intl` — no bundled locale data.
|
|
2097
|
+
* Whether this API key is permitted to report page URLs for content discovery.
|
|
857
2098
|
*
|
|
858
|
-
*
|
|
859
|
-
*
|
|
860
|
-
*
|
|
861
|
-
*
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
* and
|
|
868
|
-
*
|
|
2099
|
+
* A POLICY, not a capability — deliberately independent of `writeEnabled`, and
|
|
2100
|
+
* the two must not be collapsed. `writeEnabled` answers "can this session write
|
|
2101
|
+
* from here"; this answers "may this key report at all". The canonical public
|
|
2102
|
+
* website is `writeEnabled: false` **and** `autoDiscovery: true` — read-only,
|
|
2103
|
+
* and permitted to report — which is exactly the case a single merged flag
|
|
2104
|
+
* would break.
|
|
2105
|
+
*
|
|
2106
|
+
* It exists because the SDK cannot tell whether it is running on a public
|
|
2107
|
+
* website. A mobile app holding a read key would otherwise report "page URLs"
|
|
2108
|
+
* that are not pages, and no heuristic settles it — our own URL heuristic is
|
|
2109
|
+
* already defeated by a routerless SPA. Beyond that, a customer may simply not
|
|
2110
|
+
* want visitors' page paths leaving the browser, whatever the SDK infers.
|
|
869
2111
|
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
872
|
-
*
|
|
2112
|
+
* Plain boolean rather than the tri-state `writeEnabled` uses, and false until
|
|
2113
|
+
* known. That is only safe because it is evaluated where the report is SENT
|
|
2114
|
+
* rather than where the miss is recorded: by then authorization has long since
|
|
2115
|
+
* resolved, and if it somehow hasn't, not reporting is the correct conservative
|
|
2116
|
+
* default. Checked at collection time instead, false-until-known would silently
|
|
2117
|
+
* drop early misses for keys that do permit reporting.
|
|
873
2118
|
*/
|
|
874
|
-
declare
|
|
2119
|
+
declare const autoDiscovery: Signal<boolean | undefined>;
|
|
875
2120
|
|
|
876
2121
|
interface iProject {
|
|
877
2122
|
id: string;
|
|
@@ -892,5 +2137,29 @@ interface iProject {
|
|
|
892
2137
|
|
|
893
2138
|
declare const t: TFunction;
|
|
894
2139
|
declare const tSignal: Signal<TFunction>;
|
|
2140
|
+
/**
|
|
2141
|
+
* Tell the SDK the route changed (HINT-13). Call it from your router's after-navigation hook,
|
|
2142
|
+
* so content that stays mounted across routes — a persistent layout — is looked up again and
|
|
2143
|
+
* its misses are recorded for the new URL. Framework bindings wire this for you.
|
|
2144
|
+
*/
|
|
2145
|
+
declare const notifyNavigation: () => void;
|
|
2146
|
+
/**
|
|
2147
|
+
* The header that asks your own API for the user's language (FRM-6):
|
|
2148
|
+
* `fetch(url, { headers: { ...localeHeaders() } })`. See `LangsysApp.localeHeaders`.
|
|
2149
|
+
*/
|
|
2150
|
+
declare const localeHeaders: () => Record<string, string>;
|
|
2151
|
+
/**
|
|
2152
|
+
* Render a server message entry (MSG-5): its template through `t()` under the
|
|
2153
|
+
* messages category, or the entry's `message` when there is no translation.
|
|
2154
|
+
* Standalone alias for `LangsysApp.renderServerMessage`.
|
|
2155
|
+
*/
|
|
2156
|
+
declare const renderServerMessage: (entry: ServerMessage, category?: string) => string;
|
|
2157
|
+
/**
|
|
2158
|
+
* Standalone alias for `LangsysApp.setWriteGrant` — supply the write grant
|
|
2159
|
+
* after `init()`, e.g. once the user logs in. Not the refresh path: the token
|
|
2160
|
+
* is resolved fresh before every request and never cached, so pass a provider
|
|
2161
|
+
* function and let your auth layer decide when to mint.
|
|
2162
|
+
*/
|
|
2163
|
+
declare const setWriteGrant: (grant: WriteGrant | undefined) => Promise<void>;
|
|
895
2164
|
|
|
896
|
-
export { type
|
|
2165
|
+
export { type ApiCredentials, type BlockNode, type BlockOptions, type BlockShape, CatalogSnapshot, type EncodedRichText, LangsysApp, LangsysAppAPI, LangsysConfigurationError, LegacyKeyFile, type LocaleSource, Logger, ParamPrimitive, type PersistStorage, Phrase, type PhraseOptions, type RegistrationReason, type RegistrationResult, type RenderedBlock, type RenderedNode, type RequestScope, type RequestScopeOptions, type RequestSeed, type RichSlot, type ScopeMiss, type ScopeStorage, type SeededBlock, type SeededPhrase, type SendOptions, ServerMessage, type Signal, type Subscriber, TFunction, type TeardownSubscribe, Translate, type TranslateOptions, Translations, type Unsubscriber, type Updater, type WriteGrant, applyRendered, autoDiscovery, blockNodesOf, clearSharedCatalogs, createRequestScope, createSignal, currentRequestScope, currentlyLoadedLocale, encodeRichText, getValue, type iCategories, 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, isContentBlockKnown, isPhraseMarked, legacyTokenizeElement, localeHeaders, logger, markupTokenValues, notifyNavigation, persist, reconstitute, registerBlock, registerContentBlock, renderBlock, renderServerMessage, sTranslations, scopeFromSeed, serializeTree, setPersistStorage, setRequestScopeStorage, setTeardownSignal, setWriteGrant, stripSentinels, t, tSignal, tokenizeElement, tokenizeTree, warnUnregistered, warnUnrenderedBlock, writeEnabled };
|