@verbatra/sdk 0.7.0 → 0.8.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/dist/index.cjs +361 -480
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1446 -1121
- package/dist/index.d.ts +1446 -1121
- package/dist/index.js +360 -481
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -4,8 +4,19 @@ import { Interactions } from '@google/genai';
|
|
|
4
4
|
import OpenAI from 'openai';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* A single, format-neutral translation unit. Placeholders are supplied already
|
|
8
|
-
*
|
|
7
|
+
* A single, format-neutral translation unit. Placeholders are supplied already extracted; core never
|
|
8
|
+
* derives them from the value, so an adapter that reads a file is the one authority on what counts as
|
|
9
|
+
* a placeholder in that format.
|
|
10
|
+
*
|
|
11
|
+
* The fields are: `key`, the entry's identifier within its namespace, non-empty and already flattened
|
|
12
|
+
* to the dotted path an adapter produced; `namespace`, the namespace the entry belongs to, empty when
|
|
13
|
+
* the format has no namespacing; `value`, the translatable string itself, which is untrusted input and
|
|
14
|
+
* may be empty; `description` and `meaning`, both optional free-form context that a provider is told to
|
|
15
|
+
* use only for disambiguation and never to translate or echo back; `placeholders`, the format's
|
|
16
|
+
* placeholder tokens in document order, used for the output integrity check and to decide which entries
|
|
17
|
+
* a placeholder-blind provider must withhold; and `isPlural`, whether the entry carries plural forms
|
|
18
|
+
* (an i18next plural key suffix, a vue-i18n pipe-separated value, or an ICU plural), which the adapter
|
|
19
|
+
* decides and which feeds the content hash.
|
|
9
20
|
*/
|
|
10
21
|
declare const translationEntrySchema$1: z.ZodObject<{
|
|
11
22
|
key: z.ZodString;
|
|
@@ -16,12 +27,16 @@ declare const translationEntrySchema$1: z.ZodObject<{
|
|
|
16
27
|
placeholders: z.ZodReadonly<z.ZodArray<z.ZodString>>;
|
|
17
28
|
isPlural: z.ZodBoolean;
|
|
18
29
|
}, z.core.$strip>;
|
|
19
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* The validated shape of one translation unit; the inferred type of {@link translationEntrySchema},
|
|
32
|
+
* made deeply readonly. See that schema for what each field carries.
|
|
33
|
+
*/
|
|
20
34
|
type TranslationEntry$1 = Readonly<z.infer<typeof translationEntrySchema$1>>;
|
|
21
35
|
|
|
22
36
|
/**
|
|
23
37
|
* Result of comparing source placeholders against translated-output placeholders as MULTISETS
|
|
24
|
-
* (counts matter
|
|
38
|
+
* (counts matter, so dropping one of two occurrences of the same token is still a miss). Always
|
|
39
|
+
* returned, never thrown: a mismatch is data the caller decides what to do about, not a failure.
|
|
25
40
|
*/
|
|
26
41
|
interface PlaceholderIntegrityResult$1 {
|
|
27
42
|
/** True when nothing is missing or extra: the multisets match, regardless of order. */
|
|
@@ -30,13 +45,22 @@ interface PlaceholderIntegrityResult$1 {
|
|
|
30
45
|
readonly missing: readonly string[];
|
|
31
46
|
/** Occurrences present in the translation but absent from source, one per surplus occurrence. Sorted. */
|
|
32
47
|
readonly extra: readonly string[];
|
|
33
|
-
/** Same multiset present in both, but in a different order. */
|
|
48
|
+
/** Same multiset present in both, but in a different order. Never true alongside a mismatch. */
|
|
34
49
|
readonly reordered: boolean;
|
|
35
50
|
}
|
|
36
51
|
|
|
37
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* A provider is either a prompt-driven LLM (`llm`) or a dedicated machine-translation API
|
|
54
|
+
* (`machine-translation`). The distinction is descriptive, not dispatch: both kinds satisfy the same
|
|
55
|
+
* {@link TranslationProvider} contract, and callers use it to set expectations (an LLM accepts free-form
|
|
56
|
+
* context and a glossary term map, an MT API generally does not).
|
|
57
|
+
*/
|
|
38
58
|
type ProviderKind = "llm" | "machine-translation";
|
|
39
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Target tone for a translation. `formal` and `informal` request the corresponding register, and
|
|
61
|
+
* `neutral` requests neither. Machine-translation providers map it to their formality setting and may
|
|
62
|
+
* report a `FORMALITY_DOWNGRADED` {@link ProviderNotice} when they cannot apply it.
|
|
63
|
+
*/
|
|
40
64
|
type Tone = "formal" | "informal" | "neutral";
|
|
41
65
|
/**
|
|
42
66
|
* Produces the placeholder set of a value for the output integrity check. Supplied
|
|
@@ -54,7 +78,7 @@ type PlaceholderComparator = (source: string, translated: string) => Placeholder
|
|
|
54
78
|
/**
|
|
55
79
|
* A batch translation request. Format- and provider-neutral: it carries no prompt,
|
|
56
80
|
* model, key, or other provider-specific field. The placeholder extractor is
|
|
57
|
-
* mandatory
|
|
81
|
+
* mandatory and is checked before the data fields are parsed.
|
|
58
82
|
*/
|
|
59
83
|
interface TranslateRequest {
|
|
60
84
|
/** BCP-47 source locale of the entries (for example, "en"). */
|
|
@@ -65,7 +89,7 @@ interface TranslateRequest {
|
|
|
65
89
|
readonly entries: readonly TranslationEntry$1[];
|
|
66
90
|
/** Optional source-term to target-term map applied by glossary-capable providers. */
|
|
67
91
|
readonly glossary?: Readonly<Record<string, string>>;
|
|
68
|
-
/** Optional target
|
|
92
|
+
/** Optional target {@link Tone}; machine-translation providers map it to formality. */
|
|
69
93
|
readonly tone?: Tone;
|
|
70
94
|
/** Mandatory placeholder extractor; the output integrity check runs against it. */
|
|
71
95
|
readonly extractPlaceholders: PlaceholderExtractor;
|
|
@@ -77,14 +101,16 @@ interface TranslateRequest {
|
|
|
77
101
|
readonly comparePlaceholders?: PlaceholderComparator;
|
|
78
102
|
/**
|
|
79
103
|
* Optional cancellation signal for this batch. When aborted, an in-flight provider call rejects
|
|
80
|
-
* with the abort, unwrapped, instead of a
|
|
81
|
-
*
|
|
104
|
+
* with the abort, unwrapped, instead of a `ProviderError`. Not a plain-data field: it is
|
|
105
|
+
* never validated alongside the data fields and never sent to a provider.
|
|
82
106
|
*/
|
|
83
107
|
readonly signal?: AbortSignal;
|
|
84
108
|
}
|
|
85
109
|
/** Token usage, when the provider reports it. Absent for providers without tokens (DeepL). */
|
|
86
110
|
interface Usage {
|
|
111
|
+
/** Tokens consumed by the request, summed across the initial call and any repair round. */
|
|
87
112
|
readonly inputTokens: number;
|
|
113
|
+
/** Tokens produced by the response, summed across the initial call and any repair round. */
|
|
88
114
|
readonly outputTokens: number;
|
|
89
115
|
}
|
|
90
116
|
/**
|
|
@@ -113,25 +139,38 @@ interface ProviderNotice {
|
|
|
113
139
|
}
|
|
114
140
|
/**
|
|
115
141
|
* Stable codes for a derived, per-key "needs review" signal. This is verbatra's own computed
|
|
116
|
-
* assessment, never a raw model self-score: four are recomputable from plain source
|
|
117
|
-
* values
|
|
118
|
-
* a provider notice.
|
|
142
|
+
* assessment, never a raw model self-score: four are recomputable from plain source and translated
|
|
143
|
+
* values, and `PROVIDER_DEGRADED` is layered on afterwards from the batch's notices.
|
|
119
144
|
*
|
|
120
145
|
* - `LENGTH_RATIO_OUTLIER`: the translated value's length is far shorter or longer than the source's.
|
|
121
|
-
*
|
|
122
|
-
* - `
|
|
146
|
+
* Only considered once the trimmed source is long enough for the ratio to mean anything.
|
|
147
|
+
* - `EQUALS_SOURCE`: the translated value equals the source value once both are trimmed, so a
|
|
148
|
+
* difference in leading or trailing whitespace alone still counts as equal. Also requires that
|
|
149
|
+
* the locales differ and that the value contains at least one letter (so a bare symbol or number
|
|
150
|
+
* is not flagged).
|
|
151
|
+
* - `GLOSSARY_TERM_MISSED`: a configured glossary source term appeared in the source but its target
|
|
152
|
+
* term did not appear in the translation.
|
|
123
153
|
* - `INTEGRITY_REORDERED`: the placeholder set matched but landed in a different order.
|
|
124
|
-
* - `PROVIDER_DEGRADED`: the batch this key came from carried a
|
|
154
|
+
* - `PROVIDER_DEGRADED`: the batch this key came from carried a `FORMALITY_DOWNGRADED` or
|
|
155
|
+
* `GLOSSARY_IGNORED` notice, either of which can silently change wording. A
|
|
156
|
+
* `PLACEHOLDER_UNSUPPORTED` notice does not raise it, since the affected entries are withheld
|
|
157
|
+
* rather than degraded.
|
|
125
158
|
*/
|
|
126
159
|
type ReviewReasonCode = "LENGTH_RATIO_OUTLIER" | "EQUALS_SOURCE" | "GLOSSARY_TERM_MISSED" | "INTEGRITY_REORDERED" | "PROVIDER_DEGRADED";
|
|
127
160
|
/** A key flagged for human review, carrying every reason code that applies. */
|
|
128
161
|
interface ReviewFlag {
|
|
162
|
+
/** Always "review": the flag exists only for flagged keys, so there is no "ok" member. */
|
|
129
163
|
readonly status: "review";
|
|
164
|
+
/** Every {@link ReviewReasonCode} that applies to this key; never empty. */
|
|
130
165
|
readonly reasons: readonly ReviewReasonCode[];
|
|
131
166
|
}
|
|
132
167
|
/** Result of a batch translation: per-key values, per-key integrity outcomes, and any notices. */
|
|
133
168
|
interface TranslateResult {
|
|
134
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* The translated value for each requested key. A key can be absent when the provider withheld it
|
|
171
|
+
* (see `PLACEHOLDER_UNSUPPORTED`) or never returned it, so callers must not assume one entry in
|
|
172
|
+
* equals one entry out.
|
|
173
|
+
*/
|
|
135
174
|
readonly values: ReadonlyMap<string, string>;
|
|
136
175
|
/** The placeholder-integrity outcome for each key (source vs translated placeholder sets). */
|
|
137
176
|
readonly integrity: ReadonlyMap<string, PlaceholderIntegrityResult$1>;
|
|
@@ -151,39 +190,20 @@ interface TranslateResult {
|
|
|
151
190
|
}
|
|
152
191
|
/**
|
|
153
192
|
* The single contract every provider implements. It is narrow enough that a machine-translation API like
|
|
154
|
-
* DeepL fits it directly, while LLM providers implement it by delegating to
|
|
155
|
-
* A new provider attaches by implementing this, then adding an entry to the
|
|
156
|
-
* in `packages/sdk/src/config/provider-config.ts` alongside its member of the
|
|
157
|
-
* union. That table is a mapped type over the union's id set, so a provider present in
|
|
158
|
-
* other fails to compile.
|
|
193
|
+
* DeepL fits it directly, while LLM providers implement it by delegating to the shared
|
|
194
|
+
* `runLlmTranslation` layer. A new provider attaches by implementing this, then adding an entry to the
|
|
195
|
+
* `providerFactories` table in `packages/sdk/src/config/provider-config.ts` alongside its member of the
|
|
196
|
+
* provider config union. That table is a mapped type over the union's id set, so a provider present in
|
|
197
|
+
* one but not the other fails to compile.
|
|
159
198
|
*
|
|
160
199
|
* Implementer invariants:
|
|
161
200
|
* - Translatable strings are UNTRUSTED. They travel only as data to the provider; never splice them into
|
|
162
201
|
* instruction text, and never act on instructions a value appears to contain.
|
|
163
202
|
* - Read the API key ONLY from the environment (inside the SDK client). The request, config, and this
|
|
164
203
|
* interface never carry a key.
|
|
165
|
-
* - Fail with a secret-free
|
|
166
|
-
* carry a key or request headers). Validate the request at the boundary
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* @example
|
|
170
|
-
* ```ts
|
|
171
|
-
* // A machine-translation provider implements translateBatch directly (the DeepL shape).
|
|
172
|
-
* function createMyMtProvider(client: MyClient): TranslationProvider {
|
|
173
|
-
* return {
|
|
174
|
-
* id: "my-mt",
|
|
175
|
-
* kind: "machine-translation",
|
|
176
|
-
* supportsGlossary: false,
|
|
177
|
-
* async translateBatch(request) {
|
|
178
|
-
* const data = validateRequest(request); // throws INVALID_REQUEST on a bad request
|
|
179
|
-
* const texts = data.entries.map((e) => e.value);
|
|
180
|
-
* const out = await client.translate(texts, data.targetLocale); // SDK reads MY_API_KEY from env
|
|
181
|
-
* // map out -> values, run the integrity check, return { values, integrity }
|
|
182
|
-
* return buildResult(data, out, request.extractPlaceholders);
|
|
183
|
-
* },
|
|
184
|
-
* };
|
|
185
|
-
* }
|
|
186
|
-
* ```
|
|
204
|
+
* - Fail with a secret-free `ProviderError`: never bind, log, or re-throw raw SDK error text (it can
|
|
205
|
+
* carry a key or request headers). Validate the request at the boundary so the integrity check can
|
|
206
|
+
* never be skipped.
|
|
187
207
|
*/
|
|
188
208
|
interface TranslationProvider {
|
|
189
209
|
/** A stable identifier for this provider (for example, "anthropic", "deepl"). */
|
|
@@ -197,16 +217,18 @@ interface TranslationProvider {
|
|
|
197
217
|
*
|
|
198
218
|
* @param request - The provider-neutral batch request (no prompt, model, or key).
|
|
199
219
|
* @returns The per-key translated values and per-key placeholder-integrity outcomes.
|
|
200
|
-
* @throws
|
|
201
|
-
* the implementation's; see each provider factory).
|
|
220
|
+
* @throws `ProviderError`, secret-free, with the code for the failure (the concrete codes are
|
|
221
|
+
* the implementation's; see each provider factory). An aborted {@link TranslateRequest.signal}
|
|
222
|
+
* instead rejects with the abort itself, unwrapped.
|
|
202
223
|
*/
|
|
203
224
|
translateBatch(request: TranslateRequest): Promise<TranslateResult>;
|
|
204
225
|
}
|
|
205
226
|
|
|
206
227
|
/**
|
|
207
|
-
* The Anthropic authoring model type, sourced from the SDK's own model union
|
|
208
|
-
* open union (known literals plus `string & {}`), so unknown
|
|
209
|
-
* are still accepted.
|
|
228
|
+
* The Anthropic authoring model type, sourced from the SDK's own model union so verbatra
|
|
229
|
+
* restates no model IDs. It is an open union (known literals plus `string & {}`), so unknown
|
|
230
|
+
* or newly released model IDs are still accepted. Type-only: it informs editor completions
|
|
231
|
+
* and is never validated at runtime, where the schema stays `z.string().min(1)`.
|
|
210
232
|
*/
|
|
211
233
|
type AnthropicModel = Anthropic.Messages.Model;
|
|
212
234
|
|
|
@@ -219,13 +241,24 @@ type AnthropicModel = Anthropic.Messages.Model;
|
|
|
219
241
|
type GeminiModel = Interactions.Model;
|
|
220
242
|
|
|
221
243
|
/**
|
|
222
|
-
* The OpenAI authoring model type, sourced from the OpenAI SDK's published chat-model
|
|
223
|
-
*
|
|
224
|
-
* runtime schema stays `z.string().min(1)`.
|
|
244
|
+
* The OpenAI authoring model type, sourced from the OpenAI SDK's published chat-model union
|
|
245
|
+
* so verbatra restates no model IDs. Type-only: it drives editor completions and is never
|
|
246
|
+
* read at runtime, where the schema stays `z.string().min(1)`.
|
|
225
247
|
*/
|
|
226
248
|
type OpenAiModel = OpenAI.ChatModel;
|
|
227
249
|
|
|
228
|
-
/**
|
|
250
|
+
/**
|
|
251
|
+
* Zod schema accepting exactly one of the eight source formats verbatra can read and write. Each
|
|
252
|
+
* member names the ecosystem it round-trips, not just a file extension, because several of them share
|
|
253
|
+
* one: `i18next-json` is i18next's nested JSON with its plural key suffixes, `vue-i18n-json` is Vue
|
|
254
|
+
* I18n's JSON with its pipe-separated plural values, `next-intl-json` is next-intl's ICU-message JSON,
|
|
255
|
+
* and `ngx-translate-json` is ngx-translate's nested JSON. The remaining four are `xliff` (the XLIFF
|
|
256
|
+
* interchange XML), `yaml` (plain nested YAML), `arb` (Flutter's Application Resource Bundle), and
|
|
257
|
+
* `properties` (Java and Spring `.properties` files).
|
|
258
|
+
*
|
|
259
|
+
* The set is closed: a format outside it cannot be represented, so adding one means adding a member
|
|
260
|
+
* here and an adapter that claims it.
|
|
261
|
+
*/
|
|
229
262
|
declare const supportedFormatSchema$1: z.ZodEnum<{
|
|
230
263
|
"i18next-json": "i18next-json";
|
|
231
264
|
"vue-i18n-json": "vue-i18n-json";
|
|
@@ -236,92 +269,110 @@ declare const supportedFormatSchema$1: z.ZodEnum<{
|
|
|
236
269
|
arb: "arb";
|
|
237
270
|
properties: "properties";
|
|
238
271
|
}>;
|
|
239
|
-
/** One of the supported source formats;
|
|
272
|
+
/** One of the eight supported source formats; the inferred type of {@link supportedFormatSchema}. */
|
|
240
273
|
type SupportedFormat$1 = z.infer<typeof supportedFormatSchema$1>;
|
|
241
274
|
|
|
242
|
-
/**
|
|
275
|
+
/**
|
|
276
|
+
* The outcome of a size-bounded text read. A file that is absent or larger than the caller's limit
|
|
277
|
+
* is reported as a state rather than thrown, so a caller can treat "no file yet" as normal and an
|
|
278
|
+
* oversized file as a distinct, actionable condition.
|
|
279
|
+
*/
|
|
243
280
|
type BoundedFileRead = {
|
|
281
|
+
/** The file was read in full. */
|
|
244
282
|
readonly kind: "ok";
|
|
283
|
+
/** The file's content, decoded as UTF-8. */
|
|
245
284
|
readonly content: string;
|
|
246
285
|
} | {
|
|
286
|
+
/** No readable file exists at the path. */
|
|
247
287
|
readonly kind: "missing";
|
|
248
288
|
} | {
|
|
289
|
+
/** The file exists but exceeds the requested byte limit, so nothing was read. */
|
|
249
290
|
readonly kind: "too-large";
|
|
250
291
|
};
|
|
251
|
-
/**
|
|
292
|
+
/**
|
|
293
|
+
* The outcome of a size-bounded binary read, used for interchange workbooks. Mirrors
|
|
294
|
+
* {@link BoundedFileRead} but yields raw bytes rather than decoded text.
|
|
295
|
+
*/
|
|
252
296
|
type BoundedBytesRead = {
|
|
297
|
+
/** The file was read in full. */
|
|
253
298
|
readonly kind: "ok";
|
|
299
|
+
/** The file's raw bytes. */
|
|
254
300
|
readonly bytes: Uint8Array;
|
|
255
301
|
} | {
|
|
302
|
+
/** No readable file exists at the path. */
|
|
256
303
|
readonly kind: "missing";
|
|
257
304
|
} | {
|
|
305
|
+
/** The file exists but exceeds the requested byte limit, so nothing was read. */
|
|
258
306
|
readonly kind: "too-large";
|
|
259
307
|
};
|
|
260
308
|
/**
|
|
261
|
-
* The
|
|
262
|
-
*
|
|
309
|
+
* The file-system port the SDK's own I/O goes through. Supplying your own implementation as a
|
|
310
|
+
* `deps.fs` option redirects that I/O, which is how the SDK's tests avoid touching disk and how an
|
|
311
|
+
* embedding application can back part of a project with something other than a local disk.
|
|
312
|
+
*
|
|
313
|
+
* Know what the seam does and does not cover before relying on it. It carries the run-status file,
|
|
314
|
+
* the lock-file, the config glossary, and workbook and interchange I/O. It does not carry the
|
|
315
|
+
* locale files: those are read and written by `@verbatra/format-adapters`, which uses `node:fs`
|
|
316
|
+
* directly and is given a path rather than this port. Supplying a `deps.fs` therefore does not make
|
|
317
|
+
* a run fully in-memory, and a project's translations are still read from and written to real disk.
|
|
318
|
+
*
|
|
319
|
+
* Reads are size-bounded by contract so that a hostile or accidentally huge file cannot exhaust
|
|
320
|
+
* memory. Writes are expected to be atomic: the default implementation writes to a temporary file
|
|
321
|
+
* and renames it into place, so a crash mid-write never leaves a half-written file behind.
|
|
322
|
+
* Directory creation is the caller's job, so an implementation whose `writeFile` targets a real
|
|
323
|
+
* directory tree must also implement `mkdir`.
|
|
263
324
|
*/
|
|
264
325
|
interface SdkFs {
|
|
265
|
-
/**
|
|
326
|
+
/** Reports whether a readable file exists at the path. */
|
|
266
327
|
fileExists(path: string): Promise<boolean>;
|
|
267
|
-
/**
|
|
268
|
-
* Read a file as UTF-8 through a single handle, bounded to maxBytes. TOCTOU-safe: the handle is
|
|
269
|
-
* fstat'd and the read never advances past the sized length, so swapping in a larger file cannot
|
|
270
|
-
* bypass the cap. A missing or unreadable path is "missing"; a file over the cap is "too-large".
|
|
271
|
-
*/
|
|
328
|
+
/** Reads a file as UTF-8 text, refusing to read more than `maxBytes`. */
|
|
272
329
|
readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
|
|
273
|
-
/**
|
|
330
|
+
/** Reads a file as raw bytes, refusing to read more than `maxBytes`. */
|
|
274
331
|
readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
|
|
275
|
-
/**
|
|
332
|
+
/** Writes UTF-8 text to the path, replacing any existing file atomically. */
|
|
276
333
|
writeFile(path: string, data: string): Promise<void>;
|
|
277
|
-
/**
|
|
334
|
+
/** Writes raw bytes to the path, replacing any existing file atomically. */
|
|
278
335
|
writeBytes(path: string, data: Uint8Array): Promise<void>;
|
|
279
336
|
/**
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
* was already present (no write performed). The backbone of {@link withLocaleWriteLock}'s
|
|
283
|
-
* exclusive-create lock: the parent-directory creation lives here so that primitive never touches
|
|
284
|
-
* `node:fs` directly and a fake `SdkFs` in tests never needs a real directory to exist.
|
|
337
|
+
* Creates a file only if it does not already exist, returning `false` when it does. This is the
|
|
338
|
+
* primitive behind the per-locale write lock, so it must be atomic against other processes.
|
|
285
339
|
*/
|
|
286
340
|
createExclusive(path: string, data: string): Promise<boolean>;
|
|
287
|
-
/**
|
|
341
|
+
/** Removes the file at the path, succeeding even when it is already absent. */
|
|
288
342
|
deleteFile(path: string): Promise<void>;
|
|
289
|
-
/**
|
|
290
|
-
* Create a directory and every missing parent (mkdir -p); a no-op if it already exists.
|
|
291
|
-
*
|
|
292
|
-
* Optional so an `SdkFs` written against an earlier version stays valid, and because an in-memory
|
|
293
|
-
* test double has no directories to create. Only the delimited export calls it, right before writing
|
|
294
|
-
* one interchange file per locale into the directory the caller named.
|
|
295
|
-
*/
|
|
343
|
+
/** Creates a directory and any missing parents. Optional; omit it if the backing store has no directories. */
|
|
296
344
|
mkdir?(path: string): Promise<void>;
|
|
297
345
|
}
|
|
298
346
|
|
|
299
347
|
/**
|
|
300
|
-
* The
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
* instead of being re-sent to the provider.
|
|
348
|
+
* The on-disk translation memory, read from and written to `verbatra.cache.json` (see
|
|
349
|
+
* {@link CACHE_FILE_NAME}). A run consults it before calling the provider, so a string that was
|
|
350
|
+
* already translated under the same configuration is reused instead of paid for again.
|
|
304
351
|
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
* empty cache and never fails a run: it is a cost optimization, not a correctness record.
|
|
352
|
+
* Entries are keyed by configuration fingerprint, then locale, then source-content hash. The
|
|
353
|
+
* fingerprint layer means that changing the provider, model, tone, or glossary does not silently
|
|
354
|
+
* reuse translations produced under the old settings; those entries simply stop matching.
|
|
309
355
|
*/
|
|
310
356
|
interface TranslationMemory {
|
|
311
|
-
/** The cache schema version
|
|
357
|
+
/** The cache schema version. A file at an unrecognized version is ignored rather than trusted. */
|
|
312
358
|
readonly version: number;
|
|
313
|
-
/** Cached values, nested fingerprint
|
|
359
|
+
/** Cached values, nested as configuration fingerprint, then locale, then source-content hash. */
|
|
314
360
|
readonly entries: Readonly<Record<string, Readonly<Record<string, Readonly<Record<string, string>>>>>>;
|
|
315
361
|
}
|
|
316
362
|
|
|
317
|
-
/**
|
|
363
|
+
/**
|
|
364
|
+
* The file name of the project's translation memory, resolved against the run's working directory.
|
|
365
|
+
* Commit it to reuse translations across machines and CI runs, or add it to `.gitignore` to treat
|
|
366
|
+
* the cache as purely local. Deleting it is always safe: the next run simply repays for the strings
|
|
367
|
+
* it would have reused.
|
|
368
|
+
*/
|
|
318
369
|
declare const CACHE_FILE_NAME = "verbatra.cache.json";
|
|
319
370
|
|
|
320
371
|
/**
|
|
321
|
-
* The
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
372
|
+
* The zod schema for the `provider` block, discriminated on `id`. Each variant's options are
|
|
373
|
+
* validated strictly, so an option that belongs to a different provider is reported as an error
|
|
374
|
+
* rather than ignored. It is embedded in {@link verbatraConfigSchema} and produces
|
|
375
|
+
* {@link ProviderConfig}.
|
|
325
376
|
*/
|
|
326
377
|
declare const providerConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
327
378
|
id: z.ZodLiteral<"anthropic">;
|
|
@@ -360,15 +411,34 @@ declare const providerConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
360
411
|
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
361
412
|
}, z.core.$strict>;
|
|
362
413
|
}, z.core.$strip>], "id">;
|
|
363
|
-
/**
|
|
414
|
+
/**
|
|
415
|
+
* The `provider` block of a verbatra config: a discriminated union on `id`, so each provider
|
|
416
|
+
* carries exactly the options it supports and nothing else.
|
|
417
|
+
*
|
|
418
|
+
* No variant has a field for an API key. Keys are read from the environment by the provider itself
|
|
419
|
+
* (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `DEEPL_API_KEY`), which is what keeps
|
|
420
|
+
* them out of config files and out of version control. The `openai-compatible` variant may name a
|
|
421
|
+
* different environment variable through `apiKeyEnvVar`, but still never holds the key itself.
|
|
422
|
+
*/
|
|
364
423
|
type ProviderConfig = z.infer<typeof providerConfigSchema>;
|
|
365
|
-
/**
|
|
424
|
+
/**
|
|
425
|
+
* The identifier of a supported translation provider: `anthropic`, `openai`, `gemini`, `deepl`, or
|
|
426
|
+
* `openai-compatible`. The last one targets a local or self-hosted server that speaks the OpenAI
|
|
427
|
+
* chat-completions API.
|
|
428
|
+
*/
|
|
366
429
|
type ProviderId = ProviderConfig["id"];
|
|
367
430
|
|
|
368
431
|
/**
|
|
369
|
-
* The
|
|
370
|
-
*
|
|
371
|
-
*
|
|
432
|
+
* The zod schema every verbatra config is validated against. {@link loadConfig} applies it, so most
|
|
433
|
+
* consumers never call it directly; reach for it when you build a config programmatically and want
|
|
434
|
+
* to validate it before handing it to {@link translate}, or when you surface config errors in your
|
|
435
|
+
* own tooling.
|
|
436
|
+
*
|
|
437
|
+
* The object is strict, so an unrecognized key is an error rather than being silently ignored: a
|
|
438
|
+
* typo in a config file is reported instead of quietly doing nothing. Beyond the per-field checks,
|
|
439
|
+
* three whole-config rules are enforced: `targetLocales` must not contain the source locale, it
|
|
440
|
+
* must not contain two locales that differ only in case (they would collide on a case-insensitive
|
|
441
|
+
* file system), and `files.pattern` must contain the `{locale}` token.
|
|
372
442
|
*/
|
|
373
443
|
declare const verbatraConfigSchema: z.ZodObject<{
|
|
374
444
|
sourceLocale: z.ZodString;
|
|
@@ -444,30 +514,40 @@ declare const verbatraConfigSchema: z.ZodObject<{
|
|
|
444
514
|
}>>;
|
|
445
515
|
}, z.core.$strict>;
|
|
446
516
|
/**
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
517
|
+
* A config exactly as it is written in a `verbatra.config.ts` file, before the SDK resolves
|
|
518
|
+
* anything. Its `glossary` may still be a path string pointing at a JSON file.
|
|
519
|
+
*
|
|
520
|
+
* This is what {@link defineConfig} returns and what {@link verbatraConfigSchema} parses. Use
|
|
521
|
+
* {@link VerbatraConfig} for the resolved shape the flows actually consume.
|
|
450
522
|
*/
|
|
451
523
|
type VerbatraConfigInput = z.infer<typeof verbatraConfigSchema>;
|
|
452
524
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
525
|
+
* A fully resolved config, ready to pass to any SDK entry point. It differs from
|
|
526
|
+
* {@link VerbatraConfigInput} in one respect: `glossary` is always an in-memory term map, because
|
|
527
|
+
* {@link loadConfig} has already read and validated any glossary file the config pointed at.
|
|
528
|
+
*
|
|
529
|
+
* Every entry point takes this shape, so a caller that builds a config by hand rather than loading
|
|
530
|
+
* one from disk must supply the glossary already resolved.
|
|
458
531
|
*/
|
|
459
532
|
type VerbatraConfig = Omit<VerbatraConfigInput, "glossary"> & {
|
|
533
|
+
/**
|
|
534
|
+
* Terms that must be translated a fixed way, already resolved to an in-memory map. A config that
|
|
535
|
+
* named a glossary file has had it read by {@link loadConfig} before it reaches here.
|
|
536
|
+
*/
|
|
460
537
|
glossary?: Readonly<Record<string, string>>;
|
|
461
538
|
};
|
|
462
539
|
|
|
463
540
|
/**
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
* runs.
|
|
541
|
+
* Reduces a model union to its literal members, discarding the bare `string` member that some
|
|
542
|
+
* vendor SDKs include to stay open to unreleased models. Without this, `string` would absorb the
|
|
543
|
+
* literals and the editor would offer no completions at all.
|
|
468
544
|
*/
|
|
469
545
|
type KnownModels<M extends string> = M extends string ? (string extends M ? never : M) : never;
|
|
470
|
-
/**
|
|
546
|
+
/**
|
|
547
|
+
* One provider's config variant with its `model` field narrowed from `string` to that vendor's
|
|
548
|
+
* known model IDs, which is what turns model names into an autocompleted, checked union while
|
|
549
|
+
* authoring.
|
|
550
|
+
*/
|
|
471
551
|
type AuthoringVariant<Id extends ProviderId, M extends string> = Extract<ProviderConfig, {
|
|
472
552
|
id: Id;
|
|
473
553
|
}> extends infer Variant ? Variant extends {
|
|
@@ -480,219 +560,306 @@ type AuthoringVariant<Id extends ProviderId, M extends string> = Extract<Provide
|
|
|
480
560
|
};
|
|
481
561
|
} : never : never;
|
|
482
562
|
/**
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
563
|
+
* Maps each provider ID to its authoring-time config variant. The three language-model providers
|
|
564
|
+
* get narrowed model unions; DeepL has no model to narrow, and `openai-compatible` deliberately
|
|
565
|
+
* keeps a free-form model string because the endpoint is a local or self-hosted server whose model
|
|
566
|
+
* list the SDK cannot know ahead of time.
|
|
487
567
|
*/
|
|
488
568
|
type AuthoringProviderVariant = {
|
|
569
|
+
/** Anthropic, with `model` narrowed to the Claude model IDs. */
|
|
489
570
|
anthropic: AuthoringVariant<"anthropic", AnthropicModel>;
|
|
571
|
+
/** OpenAI, with `model` narrowed to the OpenAI chat model IDs. */
|
|
490
572
|
openai: AuthoringVariant<"openai", OpenAiModel>;
|
|
573
|
+
/** Gemini, with `model` narrowed to the Gemini model IDs. */
|
|
491
574
|
gemini: AuthoringVariant<"gemini", GeminiModel>;
|
|
575
|
+
/** DeepL, which takes no model because it is a machine-translation API rather than a language model. */
|
|
492
576
|
deepl: Extract<ProviderConfig, {
|
|
493
577
|
id: "deepl";
|
|
494
578
|
}>;
|
|
579
|
+
/** A local or self-hosted OpenAI-compatible endpoint, whose model stays a free-form string. */
|
|
495
580
|
"openai-compatible": Extract<ProviderConfig, {
|
|
496
581
|
id: "openai-compatible";
|
|
497
582
|
}>;
|
|
498
583
|
};
|
|
499
584
|
/**
|
|
500
|
-
*
|
|
501
|
-
* `provider`
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
* `string`.
|
|
585
|
+
* A config as authored for one specific provider: the ordinary {@link VerbatraConfigInput} shape,
|
|
586
|
+
* but with `provider` narrowed to that provider's variant so its model IDs autocomplete and a model
|
|
587
|
+
* belonging to another vendor is a type error.
|
|
588
|
+
*
|
|
589
|
+
* This is the parameter type of each {@link defineConfig} overload.
|
|
506
590
|
*/
|
|
507
591
|
type AuthoringConfigFor<TId extends ProviderId = ProviderId> = Omit<VerbatraConfigInput, "provider"> & {
|
|
592
|
+
/** The provider block, narrowed to the variant for `TId`. */
|
|
508
593
|
provider: AuthoringProviderVariant[TId];
|
|
509
594
|
};
|
|
510
595
|
/**
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
* `provider` offers per-provider model completions.
|
|
596
|
+
* A config authored for any supported provider: {@link AuthoringConfigFor} left un-narrowed, which
|
|
597
|
+
* is the type of the fallback {@link defineConfig} overload.
|
|
514
598
|
*/
|
|
515
599
|
type AuthoringConfig = AuthoringConfigFor;
|
|
516
600
|
|
|
517
601
|
/**
|
|
518
|
-
*
|
|
519
|
-
* only
|
|
520
|
-
*
|
|
602
|
+
* Types a `verbatra.config.ts` while you author it. The call is a pure identity function at
|
|
603
|
+
* runtime: its only job is to give your editor the type of the config object so that keys are
|
|
604
|
+
* completed, unknown keys are rejected, and the model name is checked against the models the
|
|
605
|
+
* selected provider actually offers.
|
|
606
|
+
*
|
|
607
|
+
* This overload narrows to the Anthropic provider, so `provider.options.model` completes to the
|
|
608
|
+
* Claude model IDs and rejects a model belonging to another vendor.
|
|
609
|
+
*
|
|
610
|
+
* Validation at load time is a separate step: {@link loadConfig} still parses the file against
|
|
611
|
+
* {@link verbatraConfigSchema}, so a config that type-checks can still fail validation.
|
|
521
612
|
*
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
*
|
|
613
|
+
* @param config - The config object, narrowed to the Anthropic provider.
|
|
614
|
+
* @returns The same object, typed as {@link VerbatraConfigInput}.
|
|
615
|
+
*
|
|
616
|
+
* @example
|
|
617
|
+
* ```ts
|
|
618
|
+
* import { defineConfig } from "@verbatra/sdk";
|
|
526
619
|
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
620
|
+
* export default defineConfig({
|
|
621
|
+
* sourceLocale: "en",
|
|
622
|
+
* targetLocales: ["de", "fr"],
|
|
623
|
+
* format: "i18next-json",
|
|
624
|
+
* files: { pattern: "locales/{locale}/common.json" },
|
|
625
|
+
* provider: {
|
|
626
|
+
* id: "anthropic",
|
|
627
|
+
* options: { model: "claude-sonnet-4-6", maxTokens: 4096 },
|
|
628
|
+
* },
|
|
629
|
+
* });
|
|
630
|
+
* ```
|
|
529
631
|
*/
|
|
530
632
|
declare function defineConfig(config: AuthoringConfigFor<"anthropic">): VerbatraConfigInput;
|
|
633
|
+
/**
|
|
634
|
+
* Types a `verbatra.config.ts` narrowed to the OpenAI provider, so `provider.options.model`
|
|
635
|
+
* completes to the OpenAI chat model IDs.
|
|
636
|
+
*
|
|
637
|
+
* @param config - The config object, narrowed to the OpenAI provider.
|
|
638
|
+
* @returns The same object, typed as {@link VerbatraConfigInput}.
|
|
639
|
+
*/
|
|
531
640
|
declare function defineConfig(config: AuthoringConfigFor<"openai">): VerbatraConfigInput;
|
|
641
|
+
/**
|
|
642
|
+
* Types a `verbatra.config.ts` narrowed to the Gemini provider, so `provider.options.model`
|
|
643
|
+
* completes to the Gemini model IDs.
|
|
644
|
+
*
|
|
645
|
+
* @param config - The config object, narrowed to the Gemini provider.
|
|
646
|
+
* @returns The same object, typed as {@link VerbatraConfigInput}.
|
|
647
|
+
*/
|
|
532
648
|
declare function defineConfig(config: AuthoringConfigFor<"gemini">): VerbatraConfigInput;
|
|
649
|
+
/**
|
|
650
|
+
* Types a `verbatra.config.ts` narrowed to the DeepL provider. DeepL is a machine-translation API
|
|
651
|
+
* rather than a language model, so its options carry an optional glossary ID instead of a model.
|
|
652
|
+
*
|
|
653
|
+
* @param config - The config object, narrowed to the DeepL provider.
|
|
654
|
+
* @returns The same object, typed as {@link VerbatraConfigInput}.
|
|
655
|
+
*/
|
|
533
656
|
declare function defineConfig(config: AuthoringConfigFor<"deepl">): VerbatraConfigInput;
|
|
657
|
+
/**
|
|
658
|
+
* Types a `verbatra.config.ts` for any supported provider. This is the fallback overload, and the
|
|
659
|
+
* one that serves `openai-compatible`, where the model is a free-form string because the endpoint
|
|
660
|
+
* is a local or self-hosted server whose model list the SDK cannot know.
|
|
661
|
+
*
|
|
662
|
+
* @param config - The config object, for any provider.
|
|
663
|
+
* @returns The same object, typed as {@link VerbatraConfigInput}.
|
|
664
|
+
*/
|
|
534
665
|
declare function defineConfig(config: AuthoringConfig): VerbatraConfigInput;
|
|
535
666
|
|
|
536
|
-
/**
|
|
667
|
+
/**
|
|
668
|
+
* Where a loaded config's glossary came from, reported by {@link loadConfigWithMeta}. The resolved
|
|
669
|
+
* {@link VerbatraConfig} holds only the final term map, so this is the one place a tool can tell an
|
|
670
|
+
* inline glossary from one read out of a file, and name that file.
|
|
671
|
+
*/
|
|
537
672
|
type GlossaryProvenance = {
|
|
673
|
+
/** The config declared no glossary. */
|
|
538
674
|
readonly source: "none";
|
|
539
675
|
} | {
|
|
676
|
+
/** The glossary was written inline in the config as a term map. */
|
|
540
677
|
readonly source: "inline";
|
|
541
678
|
} | {
|
|
679
|
+
/** The glossary was read from a JSON file the config pointed at. */
|
|
542
680
|
readonly source: "file";
|
|
681
|
+
/** The absolute path the glossary was read from. */
|
|
543
682
|
readonly path: string;
|
|
544
683
|
};
|
|
545
684
|
|
|
546
|
-
/**
|
|
685
|
+
/** Options for {@link loadConfig} and {@link loadConfigWithMeta}. */
|
|
547
686
|
interface LoadConfigOptions {
|
|
548
|
-
/** Directory to
|
|
687
|
+
/** Directory to search from, and the base for relative paths. Defaults to the process working directory. */
|
|
549
688
|
readonly cwd?: string;
|
|
550
689
|
/**
|
|
551
|
-
* A
|
|
552
|
-
*
|
|
690
|
+
* A config object to validate directly instead of reading any file. Takes precedence over
|
|
691
|
+
* `configPath` and over searching. Useful for embedding verbatra in a tool that already holds the
|
|
692
|
+
* configuration in memory.
|
|
553
693
|
*/
|
|
554
694
|
readonly configOverride?: unknown;
|
|
555
|
-
/**
|
|
556
|
-
* An explicit config file to load instead of searching. A relative path resolves against `cwd`; an
|
|
557
|
-
* absolute path is used as given. Parsed and zod-validated exactly like a searched file: a missing
|
|
558
|
-
* file is `CONFIG_NOT_FOUND`, a present but invalid one is `CONFIG_INVALID`. Takes precedence over
|
|
559
|
-
* search, but `configOverride` takes precedence over it.
|
|
560
|
-
*/
|
|
695
|
+
/** An explicit config file to load, bypassing the search. A missing file is an error rather than a fallback to search. */
|
|
561
696
|
readonly configPath?: string;
|
|
562
|
-
/**
|
|
697
|
+
/** File-system port used to read the glossary file. Defaults to the real file system. */
|
|
563
698
|
readonly fs?: SdkFs;
|
|
564
699
|
}
|
|
565
|
-
/**
|
|
700
|
+
/**
|
|
701
|
+
* Where a loaded config came from, reported by {@link loadConfigWithMeta}. A tool can use it to
|
|
702
|
+
* show which file is in effect, or to tell a real file apart from an in-memory override.
|
|
703
|
+
*/
|
|
566
704
|
type ConfigSource = {
|
|
705
|
+
/**
|
|
706
|
+
* `search` means cosmiconfig found the file by walking up from the working directory;
|
|
707
|
+
* `explicit` means the caller named it through `configPath`.
|
|
708
|
+
*/
|
|
567
709
|
readonly kind: "search" | "explicit";
|
|
710
|
+
/** The absolute path of the config file that was loaded. */
|
|
568
711
|
readonly filepath: string;
|
|
569
712
|
} | {
|
|
713
|
+
/** The config came from `configOverride`, so no file was read. */
|
|
570
714
|
readonly kind: "override";
|
|
571
715
|
};
|
|
572
|
-
/**
|
|
573
|
-
* The result of {@link loadConfigWithMeta}: the resolved config, where it was loaded from, and where its
|
|
574
|
-
* glossary came from. `filepath` (when present) and a `glossary` file path are always absolute.
|
|
575
|
-
*/
|
|
716
|
+
/** A validated config together with the provenance of the config itself and of its glossary. */
|
|
576
717
|
interface LoadedConfig {
|
|
718
|
+
/** The validated, fully resolved config, with any glossary file already read into a term map. */
|
|
577
719
|
readonly config: VerbatraConfig;
|
|
720
|
+
/** Where the config itself came from. */
|
|
578
721
|
readonly source: ConfigSource;
|
|
722
|
+
/** Where the glossary came from, if the config declared one. */
|
|
579
723
|
readonly glossary: GlossaryProvenance;
|
|
580
724
|
}
|
|
581
725
|
/**
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
* file
|
|
585
|
-
* `config` field; use this directly when the provenance is needed (for example, to display it or to
|
|
586
|
-
* re-resolve a glossary file relative to the same base directory).
|
|
726
|
+
* Loads and validates the project config, and additionally reports where it came from and where its
|
|
727
|
+
* glossary came from. Use this over {@link loadConfig} when a tool needs to show the user which
|
|
728
|
+
* config file is in effect, or to distinguish an inline glossary from a glossary file.
|
|
587
729
|
*
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
730
|
+
* Resolution order is: an explicit `configOverride`, then an explicit `configPath`, then a
|
|
731
|
+
* cosmiconfig search upward from `cwd` across `verbatra.config.ts`, the `.verbatrarc` family, and a
|
|
732
|
+
* `verbatra` property in `package.json`. A `configPath` that does not exist is an error rather than
|
|
733
|
+
* a fallback to searching, so a typo in a path never silently loads a different project's config.
|
|
734
|
+
*
|
|
735
|
+
* A glossary given as a path is read and validated here, so the returned config always carries a
|
|
736
|
+
* resolved term map.
|
|
737
|
+
*
|
|
738
|
+
* @param options - Where and how to look for the config.
|
|
739
|
+
* @returns The validated config with its config-source and glossary provenance.
|
|
740
|
+
*
|
|
741
|
+
* @throws {@link SdkError} `CONFIG_NOT_FOUND`: no config was found by search, or the explicit
|
|
742
|
+
* `configPath` does not exist.
|
|
743
|
+
* @throws {@link SdkError} `CONFIG_INVALID`: the config could not be loaded or fails validation, or
|
|
744
|
+
* its glossary file is missing, oversized, not UTF-8, not valid JSON, or not a flat string map.
|
|
596
745
|
*/
|
|
597
746
|
declare function loadConfigWithMeta(options?: LoadConfigOptions): Promise<LoadedConfig>;
|
|
598
747
|
/**
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
* @
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
* @throws {@link SdkError} `CONFIG_INVALID`: a config was found but is unparseable or fails validation, or
|
|
617
|
-
* its glossary file path could not be resolved (a raw error never escapes).
|
|
748
|
+
* Loads and validates the project config. This is the normal starting point for every SDK flow:
|
|
749
|
+
* pass the result to {@link translate}, {@link check}, {@link diff}, or any other entry point.
|
|
750
|
+
*
|
|
751
|
+
* Resolution order is an explicit `configOverride`, then an explicit `configPath`, then a
|
|
752
|
+
* cosmiconfig search upward from `cwd`. A glossary declared as a file path is read and validated
|
|
753
|
+
* here, so the returned {@link VerbatraConfig} always carries a resolved term map.
|
|
754
|
+
*
|
|
755
|
+
* Reach for {@link loadConfigWithMeta} when you also need to know which file was loaded.
|
|
756
|
+
*
|
|
757
|
+
* @param options - Where and how to look for the config.
|
|
758
|
+
* @returns The validated, fully resolved config.
|
|
759
|
+
*
|
|
760
|
+
* @throws {@link SdkError} `CONFIG_NOT_FOUND`: no config was found by search, or the explicit
|
|
761
|
+
* `configPath` does not exist.
|
|
762
|
+
* @throws {@link SdkError} `CONFIG_INVALID`: the config could not be loaded or fails validation, or
|
|
763
|
+
* its glossary file is missing, oversized, not UTF-8, not valid JSON, or not a flat string map.
|
|
764
|
+
*
|
|
618
765
|
* @example
|
|
619
766
|
* ```ts
|
|
620
767
|
* import { loadConfig, translate } from "@verbatra/sdk";
|
|
621
768
|
*
|
|
622
|
-
* // Search upward from the cwd (verbatra.config.ts, .verbatrarc.json, or a package.json "verbatra" key):
|
|
623
769
|
* const config = await loadConfig();
|
|
624
|
-
* // Or load one explicit file (relative resolves against cwd; absolute as given):
|
|
625
|
-
* // const config = await loadConfig({ configPath: "verbatra.config.ts" });
|
|
626
|
-
*
|
|
627
770
|
* const summary = await translate({ config });
|
|
771
|
+
* console.log(`${summary.succeeded.length} locales up to date`);
|
|
628
772
|
* ```
|
|
629
773
|
*/
|
|
630
774
|
declare function loadConfig(options?: LoadConfigOptions): Promise<VerbatraConfig>;
|
|
631
775
|
|
|
632
776
|
/**
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
777
|
+
* The stable, structured failure codes the SDK throws. Branch on {@link SdkError.code} rather than
|
|
778
|
+
* on a message; the messages are written for humans and are not part of the contract.
|
|
779
|
+
*
|
|
780
|
+
* A code never carries a secret. API keys are read from the environment by the providers alone, so
|
|
781
|
+
* the SDK never holds one, and provider, adapter, and core errors are secret-free before the SDK
|
|
782
|
+
* wraps them.
|
|
636
783
|
*
|
|
637
|
-
* - `CONFIG_NOT_FOUND`: no config was found by search, or an explicit `configPath` does not
|
|
638
|
-
*
|
|
784
|
+
* - `CONFIG_NOT_FOUND`: no config file was found by search, or an explicit `configPath` does not
|
|
785
|
+
* exist. Thrown by {@link loadConfig} and {@link loadConfigWithMeta}.
|
|
639
786
|
* - `CONFIG_INVALID`: a config was found but is unparseable or fails validation, or its glossary
|
|
640
|
-
* file
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
* - `
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
* - `
|
|
656
|
-
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
787
|
+
* file could not be resolved or parsed. Thrown by {@link loadConfig} and
|
|
788
|
+
* {@link loadConfigWithMeta}. {@link importWorkbook} does not throw it: when a handoff sheet or
|
|
789
|
+
* file names a locale that is not a configured target locale, it records this code on that
|
|
790
|
+
* locale's {@link LocaleSummary} instead.
|
|
791
|
+
* - `UNKNOWN_FORMAT`: no adapter is registered for the configured format. Thrown by every entry
|
|
792
|
+
* point that selects an adapter, before any file is read.
|
|
793
|
+
* - `UNKNOWN_LOCALE`: a requested locale is not among the configured target locales. Thrown through
|
|
794
|
+
* the shared locale selection by {@link check}, {@link diff}, {@link keyIntegrity},
|
|
795
|
+
* {@link lockState}, {@link exportWorkbook}, {@link keyValue}, {@link editEntry}, and
|
|
796
|
+
* {@link retranslateEntry}.
|
|
797
|
+
* - `UNKNOWN_KEY`: the requested key is not present in the source resource. Thrown by
|
|
798
|
+
* {@link keyValue}, {@link editEntry}, and {@link retranslateEntry}.
|
|
799
|
+
* - `PROVIDER_CONSTRUCTION_FAILED`: the provider factory threw. Wraps the provider's own error,
|
|
800
|
+
* including a missing `*_API_KEY` environment variable. Thrown by a non-dry-run
|
|
801
|
+
* {@link translate} and by {@link retranslateEntry}.
|
|
802
|
+
* - `SOURCE_UNREADABLE`: the source locale file is absent. Thrown by every entry point that reads
|
|
803
|
+
* the source, including {@link importWorkbook}, and by {@link watch} at startup.
|
|
804
|
+
* {@link importWorkbook} additionally throws it when the handoff file itself is missing.
|
|
805
|
+
* - `SOURCE_INVALID`: the source locale file, or an interchange file, could not be parsed. Wraps
|
|
806
|
+
* the adapter or reader error.
|
|
807
|
+
* - `LOCK_FILE_INVALID`: the lock-file exists but is corrupt, oversized, or at an unsupported
|
|
808
|
+
* version. Thrown wherever the lock-file is read or updated: {@link translate}, {@link check},
|
|
809
|
+
* {@link diff}, {@link keyIntegrity}, {@link lockState}, {@link loadLockFile},
|
|
810
|
+
* {@link exportWorkbook}, {@link importWorkbook}, {@link editEntry}, and
|
|
811
|
+
* {@link retranslateEntry}.
|
|
812
|
+
* - `LOCK_CONTENDED`: a locale's write lock could not be acquired before its timeout elapsed,
|
|
813
|
+
* because another process holds it or a killed process left the lock file behind. The message
|
|
814
|
+
* names the lock file's path. Thrown by {@link editEntry} and {@link retranslateEntry}, which
|
|
815
|
+
* act on one locale. {@link translate} and {@link importWorkbook} do not throw it: they record it
|
|
816
|
+
* on the contended locale's {@link LocaleSummary} and carry on with the other locales.
|
|
663
817
|
* - `LOCALE_LAYOUT_INVALID`: the configured `files.pattern` and `files.localeStyle` cannot be
|
|
664
|
-
* combined
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
* cannot arise).
|
|
679
|
-
* - `LOCALE_FAILED` (NOT thrown): the fallback `code` recorded on a failed `LocaleSummary` when a
|
|
680
|
-
* per-locale failure carries no string code of its own. See the surfaced-not-thrown distinction
|
|
681
|
-
* on `translate`.
|
|
818
|
+
* combined, or the style has no valid path spelling for a configured locale. Thrown by
|
|
819
|
+
* {@link createLocalePathResolver}, and so by every entry point that maps a locale to a path,
|
|
820
|
+
* before any file is read and before any provider call.
|
|
821
|
+
* - `LOCALE_PATH_COLLISION`: two configured locales resolve to the same absolute path, which would
|
|
822
|
+
* make the path-to-locale direction ambiguous and let two locale workers race on one file. Thrown
|
|
823
|
+
* at the same point as `LOCALE_LAYOUT_INVALID`.
|
|
824
|
+
* - `CONCURRENCY_INVALID`: the `concurrency` input is not an integer of at least 1. Thrown by
|
|
825
|
+
* {@link translate} before any locale runs, and by {@link watch} once at startup, before any
|
|
826
|
+
* watching begins, since the value is fixed for the session rather than re-read per run.
|
|
827
|
+
* - `CONCURRENCY_BUDGET_CONFLICT`: a live run requested a `concurrency` above 1 while a token
|
|
828
|
+
* budget is configured. The two are mutually exclusive because concurrency makes the budget's
|
|
829
|
+
* stop guarantee nondeterministic. A dry run is exempt, since it never consults the budget.
|
|
830
|
+
* - `LOCALE_FAILED`: never thrown. It is the fallback code recorded on a failed
|
|
831
|
+
* {@link LocaleSummary} when a per-locale failure carries no code of its own.
|
|
682
832
|
*/
|
|
683
833
|
type SdkErrorCode = "CONFIG_NOT_FOUND" | "CONFIG_INVALID" | "UNKNOWN_FORMAT" | "UNKNOWN_LOCALE" | "UNKNOWN_KEY" | "PROVIDER_CONSTRUCTION_FAILED" | "SOURCE_UNREADABLE" | "SOURCE_INVALID" | "LOCK_FILE_INVALID" | "LOCK_CONTENDED" | "LOCALE_LAYOUT_INVALID" | "LOCALE_PATH_COLLISION" | "CONCURRENCY_INVALID" | "CONCURRENCY_BUDGET_CONFLICT" | "LOCALE_FAILED";
|
|
684
|
-
/**
|
|
834
|
+
/**
|
|
835
|
+
* The single structured error the SDK throws. Every whole-run failure surfaces as an `SdkError`
|
|
836
|
+
* carrying a stable {@link SdkErrorCode}; per-locale failures, provider notices, and integrity
|
|
837
|
+
* findings are reported as data on the {@link RunSummary} instead of being thrown.
|
|
838
|
+
*
|
|
839
|
+
* An `SdkError` never carries a secret in its message.
|
|
840
|
+
*/
|
|
685
841
|
declare class SdkError extends Error {
|
|
686
|
-
/** The stable {@link SdkErrorCode} for this failure
|
|
842
|
+
/** The stable {@link SdkErrorCode} for this failure. Branch on this, not on the message. */
|
|
687
843
|
readonly code: SdkErrorCode;
|
|
688
844
|
/**
|
|
689
845
|
* @param code - The stable failure code.
|
|
690
|
-
* @param message - A
|
|
846
|
+
* @param message - A human-readable description of the failure. Never contains a secret.
|
|
691
847
|
*/
|
|
692
848
|
constructor(code: SdkErrorCode, message: string);
|
|
693
849
|
}
|
|
694
850
|
|
|
695
|
-
/**
|
|
851
|
+
/**
|
|
852
|
+
* Zod schema accepting exactly one of the eight source formats verbatra can read and write. Each
|
|
853
|
+
* member names the ecosystem it round-trips, not just a file extension, because several of them share
|
|
854
|
+
* one: `i18next-json` is i18next's nested JSON with its plural key suffixes, `vue-i18n-json` is Vue
|
|
855
|
+
* I18n's JSON with its pipe-separated plural values, `next-intl-json` is next-intl's ICU-message JSON,
|
|
856
|
+
* and `ngx-translate-json` is ngx-translate's nested JSON. The remaining four are `xliff` (the XLIFF
|
|
857
|
+
* interchange XML), `yaml` (plain nested YAML), `arb` (Flutter's Application Resource Bundle), and
|
|
858
|
+
* `properties` (Java and Spring `.properties` files).
|
|
859
|
+
*
|
|
860
|
+
* The set is closed: a format outside it cannot be represented, so adding one means adding a member
|
|
861
|
+
* here and an adapter that claims it.
|
|
862
|
+
*/
|
|
696
863
|
declare const supportedFormatSchema: z.ZodEnum<{
|
|
697
864
|
"i18next-json": "i18next-json";
|
|
698
865
|
"vue-i18n-json": "vue-i18n-json";
|
|
@@ -703,12 +870,23 @@ declare const supportedFormatSchema: z.ZodEnum<{
|
|
|
703
870
|
arb: "arb";
|
|
704
871
|
properties: "properties";
|
|
705
872
|
}>;
|
|
706
|
-
/** One of the supported source formats;
|
|
873
|
+
/** One of the eight supported source formats; the inferred type of {@link supportedFormatSchema}. */
|
|
707
874
|
type SupportedFormat = z.infer<typeof supportedFormatSchema>;
|
|
708
875
|
|
|
709
876
|
/**
|
|
710
|
-
* A single, format-neutral translation unit. Placeholders are supplied already
|
|
711
|
-
*
|
|
877
|
+
* A single, format-neutral translation unit. Placeholders are supplied already extracted; core never
|
|
878
|
+
* derives them from the value, so an adapter that reads a file is the one authority on what counts as
|
|
879
|
+
* a placeholder in that format.
|
|
880
|
+
*
|
|
881
|
+
* The fields are: `key`, the entry's identifier within its namespace, non-empty and already flattened
|
|
882
|
+
* to the dotted path an adapter produced; `namespace`, the namespace the entry belongs to, empty when
|
|
883
|
+
* the format has no namespacing; `value`, the translatable string itself, which is untrusted input and
|
|
884
|
+
* may be empty; `description` and `meaning`, both optional free-form context that a provider is told to
|
|
885
|
+
* use only for disambiguation and never to translate or echo back; `placeholders`, the format's
|
|
886
|
+
* placeholder tokens in document order, used for the output integrity check and to decide which entries
|
|
887
|
+
* a placeholder-blind provider must withhold; and `isPlural`, whether the entry carries plural forms
|
|
888
|
+
* (an i18next plural key suffix, a vue-i18n pipe-separated value, or an ICU plural), which the adapter
|
|
889
|
+
* decides and which feeds the content hash.
|
|
712
890
|
*/
|
|
713
891
|
declare const translationEntrySchema: z.ZodObject<{
|
|
714
892
|
key: z.ZodString;
|
|
@@ -719,23 +897,32 @@ declare const translationEntrySchema: z.ZodObject<{
|
|
|
719
897
|
placeholders: z.ZodReadonly<z.ZodArray<z.ZodString>>;
|
|
720
898
|
isPlural: z.ZodBoolean;
|
|
721
899
|
}, z.core.$strip>;
|
|
722
|
-
/**
|
|
900
|
+
/**
|
|
901
|
+
* The validated shape of one translation unit; the inferred type of {@link translationEntrySchema},
|
|
902
|
+
* made deeply readonly. See that schema for what each field carries.
|
|
903
|
+
*/
|
|
723
904
|
type TranslationEntry = Readonly<z.infer<typeof translationEntrySchema>>;
|
|
724
|
-
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* All translation entries for one locale and namespace, keyed by entry key. This is the neutral
|
|
908
|
+
* intermediate representation every adapter reads into and writes back out of, so nothing downstream
|
|
909
|
+
* of an adapter parses a format's own syntax.
|
|
910
|
+
*/
|
|
725
911
|
interface LocaleResource {
|
|
726
912
|
/** The locale these entries belong to (for example, "en" or "de"). */
|
|
727
913
|
readonly locale: string;
|
|
728
|
-
/** The namespace these entries belong to. */
|
|
914
|
+
/** The namespace these entries belong to; empty when the format has no namespacing. */
|
|
729
915
|
readonly namespace: string;
|
|
730
|
-
/** The source format the resource came from, for round-trip fidelity. */
|
|
916
|
+
/** The source format the resource came from, retained for round-trip fidelity on write. */
|
|
731
917
|
readonly format: SupportedFormat;
|
|
732
|
-
/** Entries addressable by key. */
|
|
918
|
+
/** Entries addressable by key, in the order the source file defined them. */
|
|
733
919
|
readonly entries: ReadonlyMap<string, TranslationEntry>;
|
|
734
920
|
}
|
|
735
921
|
|
|
736
922
|
/**
|
|
737
923
|
* Result of comparing source placeholders against translated-output placeholders as MULTISETS
|
|
738
|
-
* (counts matter
|
|
924
|
+
* (counts matter, so dropping one of two occurrences of the same token is still a miss). Always
|
|
925
|
+
* returned, never thrown: a mismatch is data the caller decides what to do about, not a failure.
|
|
739
926
|
*/
|
|
740
927
|
interface PlaceholderIntegrityResult {
|
|
741
928
|
/** True when nothing is missing or extra: the multisets match, regardless of order. */
|
|
@@ -744,17 +931,21 @@ interface PlaceholderIntegrityResult {
|
|
|
744
931
|
readonly missing: readonly string[];
|
|
745
932
|
/** Occurrences present in the translation but absent from source, one per surplus occurrence. Sorted. */
|
|
746
933
|
readonly extra: readonly string[];
|
|
747
|
-
/** Same multiset present in both, but in a different order. */
|
|
934
|
+
/** Same multiset present in both, but in a different order. Never true alongside a mismatch. */
|
|
748
935
|
readonly reordered: boolean;
|
|
749
936
|
}
|
|
750
937
|
|
|
751
938
|
/**
|
|
752
|
-
* The result of reading a file into core's intermediate representation.
|
|
939
|
+
* The result of reading a file into core's intermediate representation. The two diagnostic lists are
|
|
940
|
+
* reported as data rather than thrown, so one bad leaf never fails a whole read.
|
|
753
941
|
*/
|
|
754
942
|
interface ReadResult {
|
|
755
943
|
/** The parsed locale resource. */
|
|
756
944
|
readonly resource: LocaleResource;
|
|
757
|
-
/**
|
|
945
|
+
/**
|
|
946
|
+
* Keys whose values are invalid for the format's message syntax. Empty for a format with no
|
|
947
|
+
* message-syntax check, which treats every value as valid.
|
|
948
|
+
*/
|
|
758
949
|
readonly invalidIcuKeys: readonly string[];
|
|
759
950
|
/**
|
|
760
951
|
* Dotted paths of leaves that were present in the source but excluded because they are not
|
|
@@ -768,22 +959,14 @@ interface ReadResult {
|
|
|
768
959
|
* The contract every format adapter implements. A new format attaches by implementing this interface
|
|
769
960
|
* and registering it in an {@link AdapterRegistry}.
|
|
770
961
|
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
* `
|
|
774
|
-
*
|
|
775
|
-
* @
|
|
776
|
-
* ```ts
|
|
777
|
-
* const registry = createDefaultRegistry();
|
|
778
|
-
* const resolution = registry.resolve("locales/en.json", { format: "i18next-json" });
|
|
779
|
-
* if (resolution.status === "resolved") {
|
|
780
|
-
* const { resource } = await resolution.adapter.read("locales/en.json", "en");
|
|
781
|
-
* await resolution.adapter.write(resource, "locales/en.json");
|
|
782
|
-
* }
|
|
783
|
-
* ```
|
|
962
|
+
* Implement it through one of `@verbatra/format-adapters`' shared factories rather than from
|
|
963
|
+
* scratch: `createTreeFileAdapter` for a nested-tree format (with `createJsonFileAdapter` as its
|
|
964
|
+
* JSON specialization) and `createFlatFileAdapter` for a flat key/value format. A format that fits
|
|
965
|
+
* neither shape implements this interface directly, and either way it first needs its member added
|
|
966
|
+
* to core's {@link SupportedFormat}.
|
|
784
967
|
*/
|
|
785
968
|
interface FormatAdapter {
|
|
786
|
-
/** The single format this adapter handles (a
|
|
969
|
+
/** The single format this adapter handles (a {@link SupportedFormat} from core). */
|
|
787
970
|
readonly format: SupportedFormat;
|
|
788
971
|
/**
|
|
789
972
|
* Detect whether this adapter can handle a file, by path extension and an optional content sample.
|
|
@@ -801,7 +984,7 @@ interface FormatAdapter {
|
|
|
801
984
|
* @param filePath - The file to read.
|
|
802
985
|
* @param locale - The locale to tag the resource with.
|
|
803
986
|
* @returns The parsed resource and the keys whose values are invalid for the format.
|
|
804
|
-
* @throws
|
|
987
|
+
* @throws `AdapterError` when the content is malformed, oversized, or structurally invalid
|
|
805
988
|
* (the implementation names the specific codes). A missing or unopenable path instead rejects with
|
|
806
989
|
* the underlying filesystem error.
|
|
807
990
|
*/
|
|
@@ -811,7 +994,7 @@ interface FormatAdapter {
|
|
|
811
994
|
*
|
|
812
995
|
* @param resource - The resource to serialize.
|
|
813
996
|
* @param filePath - The destination file.
|
|
814
|
-
* @throws
|
|
997
|
+
* @throws `AdapterError` if the resource cannot be represented in the format; rejects with the
|
|
815
998
|
* underlying filesystem error on a write failure.
|
|
816
999
|
*/
|
|
817
1000
|
write(resource: LocaleResource, filePath: string): Promise<void>;
|
|
@@ -823,8 +1006,9 @@ interface FormatAdapter {
|
|
|
823
1006
|
*/
|
|
824
1007
|
extractPlaceholders(value: string): readonly string[];
|
|
825
1008
|
/**
|
|
826
|
-
* Validate a single value against the format's message syntax before it is written.
|
|
827
|
-
*
|
|
1009
|
+
* Validate a single value against the format's message syntax before it is written. Only the
|
|
1010
|
+
* ICU-message formats carry a real check today (next-intl and ARB); every other adapter has no
|
|
1011
|
+
* message syntax to violate and returns true for any value.
|
|
828
1012
|
*
|
|
829
1013
|
* @param value - The candidate translated value to validate.
|
|
830
1014
|
* @returns True when the value is valid for the format's message syntax. Does not throw; an
|
|
@@ -836,7 +1020,7 @@ interface FormatAdapter {
|
|
|
836
1020
|
* When present, callers use it instead of independently extracting each side's placeholders with
|
|
837
1021
|
* {@link extractPlaceholders} and diffing the flat lists, since flattening a plural/select value loses
|
|
838
1022
|
* which branch a placeholder came from. Absent for every non-ICU adapter, which is compared via
|
|
839
|
-
* `extractPlaceholders` plus
|
|
1023
|
+
* `extractPlaceholders` plus a flat multiset check as before.
|
|
840
1024
|
*
|
|
841
1025
|
* @param sourceValue - The source value.
|
|
842
1026
|
* @param targetValue - The translated value to check against it.
|
|
@@ -845,34 +1029,56 @@ interface FormatAdapter {
|
|
|
845
1029
|
comparePlaceholders?(sourceValue: string, targetValue: string): PlaceholderIntegrityResult;
|
|
846
1030
|
}
|
|
847
1031
|
|
|
848
|
-
/**
|
|
1032
|
+
/**
|
|
1033
|
+
* Outcome of resolving an adapter for a file. Structured, never thrown: an unresolvable file is a
|
|
1034
|
+
* status the caller branches on, not an exception.
|
|
1035
|
+
*
|
|
1036
|
+
* `resolved` carries the single {@link FormatAdapter} to use. `no-match` means nothing claimed the
|
|
1037
|
+
* file, and reports the formats that were tried (every registered format for a detection attempt, or
|
|
1038
|
+
* just the one requested when an explicit format was given). `ambiguous` means more than one adapter
|
|
1039
|
+
* claimed the file, which is routine since every JSON adapter claims `.json`; it reports the competing
|
|
1040
|
+
* candidates instead of guessing, and the caller resolves it by naming a format explicitly.
|
|
1041
|
+
*/
|
|
849
1042
|
type AdapterResolution = {
|
|
1043
|
+
/** Exactly one adapter claimed the file. */
|
|
850
1044
|
readonly status: "resolved";
|
|
1045
|
+
/** The adapter to read and write the file with. */
|
|
851
1046
|
readonly adapter: FormatAdapter;
|
|
852
1047
|
} | {
|
|
1048
|
+
/** No registered adapter claimed the file. */
|
|
853
1049
|
readonly status: "no-match";
|
|
1050
|
+
/** The file that could not be matched. */
|
|
854
1051
|
readonly filePath: string;
|
|
1052
|
+
/**
|
|
1053
|
+
* The formats that were tried: every registered format for a detection attempt, or just the
|
|
1054
|
+
* one requested when an explicit format was given.
|
|
1055
|
+
*/
|
|
855
1056
|
readonly triedFormats: readonly SupportedFormat[];
|
|
856
1057
|
} | {
|
|
1058
|
+
/** More than one adapter claimed the file, so detection cannot choose between them. */
|
|
857
1059
|
readonly status: "ambiguous";
|
|
1060
|
+
/** The file that several adapters claimed. */
|
|
858
1061
|
readonly filePath: string;
|
|
1062
|
+
/** The competing formats. Resolve the ambiguity by naming one of them explicitly. */
|
|
859
1063
|
readonly candidates: readonly SupportedFormat[];
|
|
860
1064
|
};
|
|
861
1065
|
/** Options for {@link AdapterRegistry.resolve}: select a format explicitly, or aid detection. */
|
|
862
1066
|
interface ResolveOptions {
|
|
863
|
-
/** A content sample to aid detection. */
|
|
1067
|
+
/** A leading content sample to aid detection. Ignored when `format` is given. */
|
|
864
1068
|
readonly sample?: string;
|
|
865
1069
|
/** Bypass detection and select this format explicitly. */
|
|
866
1070
|
readonly format?: SupportedFormat;
|
|
867
1071
|
}
|
|
868
1072
|
/**
|
|
869
|
-
* Holds the registered adapters and resolves one for a file. Open for extension:
|
|
870
|
-
*
|
|
1073
|
+
* Holds the registered adapters and resolves one for a file. Open for extension: adapters attach
|
|
1074
|
+
* through {@link AdapterRegistry.register} without changing resolution logic, and resolution never
|
|
1075
|
+
* throws.
|
|
871
1076
|
*/
|
|
872
1077
|
declare class AdapterRegistry {
|
|
873
1078
|
private readonly adapters;
|
|
874
1079
|
/**
|
|
875
|
-
* Register an adapter.
|
|
1080
|
+
* Register an adapter. Registration order is the order detection consults them, and a format may be
|
|
1081
|
+
* registered more than once, in which case an explicit-format lookup takes the first.
|
|
876
1082
|
*
|
|
877
1083
|
* @param adapter - The adapter to add.
|
|
878
1084
|
* @returns This registry, for chaining.
|
|
@@ -880,11 +1086,6 @@ declare class AdapterRegistry {
|
|
|
880
1086
|
register(adapter: FormatAdapter): this;
|
|
881
1087
|
private formats;
|
|
882
1088
|
private resolveByFormat;
|
|
883
|
-
/**
|
|
884
|
-
* Resolve by asking every registered adapter's `canHandle`. Several adapters can claim one file
|
|
885
|
-
* (all JSON adapters claim `.json`), so more than one match is reported as `ambiguous` rather
|
|
886
|
-
* than guessed at.
|
|
887
|
-
*/
|
|
888
1089
|
private resolveByDetection;
|
|
889
1090
|
/**
|
|
890
1091
|
* Resolve the adapter for a file, by explicit format when given, otherwise by detection.
|
|
@@ -897,1447 +1098,1571 @@ declare class AdapterRegistry {
|
|
|
897
1098
|
resolve(filePath: string, options?: ResolveOptions): AdapterResolution;
|
|
898
1099
|
}
|
|
899
1100
|
|
|
900
|
-
/**
|
|
1101
|
+
/** One locale's counts in a {@link CheckSummary}. */
|
|
901
1102
|
interface LocaleCheckSummary {
|
|
902
|
-
/** The target locale
|
|
1103
|
+
/** The target locale these counts describe. */
|
|
903
1104
|
readonly locale: string;
|
|
904
|
-
/**
|
|
1105
|
+
/** Number of source keys with no translation in this locale yet. */
|
|
905
1106
|
readonly missing: number;
|
|
906
|
-
/**
|
|
1107
|
+
/** Number of keys whose source text changed since the locale was last translated. */
|
|
907
1108
|
readonly stale: number;
|
|
908
|
-
/**
|
|
1109
|
+
/** Number of keys whose translation still matches the source recorded in the lock-file. */
|
|
909
1110
|
readonly upToDate: number;
|
|
910
|
-
/** True when nothing
|
|
1111
|
+
/** True when this locale has nothing missing and nothing stale. */
|
|
911
1112
|
readonly inSync: boolean;
|
|
912
1113
|
}
|
|
913
|
-
/** The
|
|
1114
|
+
/** The result of {@link check}: per-locale counts plus one project-wide verdict. */
|
|
914
1115
|
interface CheckSummary {
|
|
915
|
-
/** True only when every
|
|
1116
|
+
/** True only when every reported locale is in sync. This is the value a CI gate should assert on. */
|
|
916
1117
|
readonly inSync: boolean;
|
|
917
|
-
/**
|
|
1118
|
+
/** Per-locale counts, in configured target order. */
|
|
918
1119
|
readonly locales: readonly LocaleCheckSummary[];
|
|
919
1120
|
}
|
|
920
|
-
/** Input for {@link check}
|
|
1121
|
+
/** Input for {@link check}. */
|
|
921
1122
|
interface CheckInput {
|
|
922
|
-
/** The
|
|
1123
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
923
1124
|
readonly config: VerbatraConfig;
|
|
924
|
-
/** Directory the
|
|
1125
|
+
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
925
1126
|
readonly cwd?: string;
|
|
926
|
-
/**
|
|
1127
|
+
/** Restrict the report to these target locales. Defaults to every configured target locale. */
|
|
927
1128
|
readonly locales?: readonly string[];
|
|
928
1129
|
}
|
|
929
|
-
/**
|
|
1130
|
+
/** Injectable dependencies for {@link check}. Every field has a working default. */
|
|
930
1131
|
interface CheckDeps {
|
|
1132
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
931
1133
|
readonly adapterRegistry?: AdapterRegistry;
|
|
1134
|
+
/** File-system port. Defaults to the real file system. */
|
|
932
1135
|
readonly fs?: SdkFs;
|
|
933
1136
|
}
|
|
934
1137
|
/**
|
|
935
|
-
* Reports
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
* true only when every checked locale is. Orphaned keys and integrity are not reported, since they
|
|
939
|
-
* concern a write; use {@link diff} for the key lists.
|
|
1138
|
+
* Reports whether every target locale is up to date, without writing anything and without calling
|
|
1139
|
+
* the provider. This is the CI gate: run it on a pull request and fail the build when
|
|
1140
|
+
* {@link CheckSummary.inSync} is false.
|
|
940
1141
|
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
*
|
|
1142
|
+
* Staleness is judged against the lock-file baseline, not against the target file's mere existence,
|
|
1143
|
+
* so a key whose source text changed after it was translated counts as stale even though a
|
|
1144
|
+
* translation is present. A locale file that does not exist yet is treated as empty rather than as
|
|
1145
|
+
* an error, so a newly added locale reports every key as missing.
|
|
1146
|
+
*
|
|
1147
|
+
* Use {@link diff} instead when you need the key names rather than the counts.
|
|
1148
|
+
*
|
|
1149
|
+
* Note that a malformed target locale file surfaces the adapter's own parse error rather than a
|
|
1150
|
+
* wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
|
|
1151
|
+
* should be ready for an unrecognized error from a target file.
|
|
1152
|
+
*
|
|
1153
|
+
* @param input - The config and the optional locale filter.
|
|
1154
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
1155
|
+
* @returns Per-locale counts and the project-wide in-sync verdict.
|
|
1156
|
+
*
|
|
1157
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
1158
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
1159
|
+
* cannot be combined, or a configured locale has no valid path spelling under that style.
|
|
1160
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
1161
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
1162
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
1163
|
+
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
1164
|
+
* unsupported version.
|
|
1165
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE`: a requested locale is not a configured target locale.
|
|
947
1166
|
*/
|
|
948
1167
|
declare function check(input: CheckInput, deps?: CheckDeps): Promise<CheckSummary>;
|
|
949
1168
|
|
|
950
|
-
/**
|
|
1169
|
+
/** One locale's pending work in a {@link DiffSummary}, as key names rather than counts. */
|
|
951
1170
|
interface LocaleDiff {
|
|
952
|
-
/** The target locale this entry
|
|
1171
|
+
/** The target locale this entry describes. */
|
|
953
1172
|
readonly locale: string;
|
|
954
|
-
/** Keys present in source but absent from
|
|
1173
|
+
/** Keys present in the source but absent from this locale. */
|
|
955
1174
|
readonly missing: readonly string[];
|
|
956
|
-
/** Keys whose source changed since
|
|
1175
|
+
/** Keys whose source text changed since this locale was last translated. */
|
|
957
1176
|
readonly changed: readonly string[];
|
|
958
|
-
/**
|
|
1177
|
+
/**
|
|
1178
|
+
* Keys present in this locale but no longer in the source. They are reported, never removed,
|
|
1179
|
+
* unless a run is asked to prune.
|
|
1180
|
+
*/
|
|
959
1181
|
readonly orphaned: readonly string[];
|
|
960
|
-
/**
|
|
1182
|
+
/**
|
|
1183
|
+
* True when this locale has missing or changed keys. Orphaned keys alone do not count as pending,
|
|
1184
|
+
* because they need no translation work.
|
|
1185
|
+
*/
|
|
961
1186
|
readonly hasPendingChanges: boolean;
|
|
962
1187
|
}
|
|
963
|
-
/** The
|
|
1188
|
+
/** The result of {@link diff}: per-locale key lists plus one project-wide verdict. */
|
|
964
1189
|
interface DiffSummary {
|
|
965
|
-
/** True when any
|
|
1190
|
+
/** True when any locale has missing or changed keys. */
|
|
966
1191
|
readonly hasPendingChanges: boolean;
|
|
967
|
-
/**
|
|
1192
|
+
/** Per-locale key lists, in configured target order. */
|
|
968
1193
|
readonly locales: readonly LocaleDiff[];
|
|
969
1194
|
}
|
|
970
|
-
/** Input for {@link diff}
|
|
1195
|
+
/** Input for {@link diff}. */
|
|
971
1196
|
interface DiffInput {
|
|
972
|
-
/** The
|
|
1197
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
973
1198
|
readonly config: VerbatraConfig;
|
|
974
|
-
/** Directory the
|
|
1199
|
+
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
975
1200
|
readonly cwd?: string;
|
|
976
|
-
/**
|
|
1201
|
+
/** Restrict the report to these target locales. Defaults to every configured target locale. */
|
|
977
1202
|
readonly locales?: readonly string[];
|
|
978
1203
|
}
|
|
979
|
-
/**
|
|
1204
|
+
/** Injectable dependencies for {@link diff}. Every field has a working default. */
|
|
980
1205
|
interface DiffDeps {
|
|
1206
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
981
1207
|
readonly adapterRegistry?: AdapterRegistry;
|
|
1208
|
+
/** File-system port. Defaults to the real file system. */
|
|
982
1209
|
readonly fs?: SdkFs;
|
|
983
1210
|
}
|
|
984
1211
|
/**
|
|
985
|
-
* Reports the
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
* The
|
|
1212
|
+
* Reports the per-locale drift between the source and each target locale as key names, without
|
|
1213
|
+
* writing anything and without calling the provider. It answers "what exactly would a run change",
|
|
1214
|
+
* where {@link check} answers "is anything pending at all".
|
|
1215
|
+
*
|
|
1216
|
+
* The comparison runs against the lock-file baseline, so `changed` means the source text moved
|
|
1217
|
+
* since the key was last translated rather than merely that the two strings differ. Orphaned keys
|
|
1218
|
+
* are reported but never removed here; pruning happens only in {@link translate}.
|
|
1219
|
+
*
|
|
1220
|
+
* Note that a malformed target locale file surfaces the adapter's own parse error rather than a
|
|
1221
|
+
* wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
|
|
1222
|
+
* should be ready for an unrecognized error from a target file.
|
|
990
1223
|
*
|
|
991
|
-
* @param input - The
|
|
992
|
-
* @param deps - Optional
|
|
993
|
-
* @returns
|
|
994
|
-
*
|
|
995
|
-
*
|
|
996
|
-
*
|
|
1224
|
+
* @param input - The config and the optional locale filter.
|
|
1225
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
1226
|
+
* @returns Per-locale missing, changed, and orphaned key lists.
|
|
1227
|
+
*
|
|
1228
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
1229
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
1230
|
+
* cannot be combined, or a configured locale has no valid path spelling under that style.
|
|
1231
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
1232
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
1233
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
1234
|
+
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
1235
|
+
* unsupported version.
|
|
1236
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE`: a requested locale is not a configured target locale.
|
|
997
1237
|
*/
|
|
998
1238
|
declare function diff(input: DiffInput, deps?: DiffDeps): Promise<DiffSummary>;
|
|
999
1239
|
|
|
1000
|
-
/**
|
|
1240
|
+
/**
|
|
1241
|
+
* Why a candidate translation was refused before it could be written. The same gate guards
|
|
1242
|
+
* {@link editEntry} and {@link retranslateEntry}, so a hand-typed value and a provider-produced one
|
|
1243
|
+
* are held to identical standards.
|
|
1244
|
+
*
|
|
1245
|
+
* A rejected value is never written to the locale file and never recorded in the lock-file, so the
|
|
1246
|
+
* previous translation stays intact.
|
|
1247
|
+
*
|
|
1248
|
+
* - `placeholder`: the candidate does not carry the same placeholders as the source, so
|
|
1249
|
+
* interpolation would break at runtime.
|
|
1250
|
+
* - `icu`: the candidate is not a valid ICU message under the configured format's adapter.
|
|
1251
|
+
* - `degenerate`: the candidate collapsed into runaway output rather than a translation. Two shapes
|
|
1252
|
+
* are detected: the candidate is at least twelve times the length of a source of meaningful
|
|
1253
|
+
* length, or a short unit repeats consecutively enough to dominate the value. An untranslated
|
|
1254
|
+
* echo of the source is not degenerate by this rule; it surfaces as the `EQUALS_SOURCE` review
|
|
1255
|
+
* reason instead, which flags rather than refuses.
|
|
1256
|
+
* - `empty`: the source has text but the candidate is blank, which would silently erase a string.
|
|
1257
|
+
*/
|
|
1001
1258
|
type IntegrityGateReason = "placeholder" | "icu" | "degenerate" | "empty";
|
|
1002
1259
|
|
|
1003
|
-
/** Input for {@link editEntry}
|
|
1260
|
+
/** Input for {@link editEntry}. */
|
|
1004
1261
|
interface EditEntryInput {
|
|
1005
|
-
/** The
|
|
1262
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
1006
1263
|
readonly config: VerbatraConfig;
|
|
1007
|
-
/** Directory the
|
|
1264
|
+
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
1008
1265
|
readonly cwd?: string;
|
|
1009
|
-
/** The target locale to write
|
|
1266
|
+
/** The target locale to write to. Must be a configured target locale. */
|
|
1010
1267
|
readonly locale: string;
|
|
1011
|
-
/** The
|
|
1268
|
+
/** The key to edit. Must exist in the source resource. */
|
|
1012
1269
|
readonly key: string;
|
|
1013
|
-
/** The
|
|
1270
|
+
/** The new translation. It is accepted only if it passes the integrity gate. */
|
|
1014
1271
|
readonly value: string;
|
|
1015
1272
|
}
|
|
1016
|
-
/**
|
|
1273
|
+
/** Injectable dependencies for {@link editEntry}. Every field has a working default. */
|
|
1017
1274
|
interface EditEntryDeps {
|
|
1275
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
1018
1276
|
readonly adapterRegistry?: AdapterRegistry;
|
|
1277
|
+
/** File-system port. Defaults to the real file system. */
|
|
1019
1278
|
readonly fs?: SdkFs;
|
|
1020
1279
|
}
|
|
1021
1280
|
/**
|
|
1022
|
-
* The
|
|
1023
|
-
*
|
|
1024
|
-
*
|
|
1025
|
-
* touches a provider, so there is no provider-derived review signal to report.
|
|
1281
|
+
* The outcome of {@link editEntry}. A rejection is data rather than an exception, because a value
|
|
1282
|
+
* that fails the integrity gate is an expected editing outcome that a UI needs to explain, not a
|
|
1283
|
+
* failure of the call.
|
|
1026
1284
|
*/
|
|
1027
1285
|
type EditEntryResult = {
|
|
1286
|
+
/** The value passed the integrity gate and was written. */
|
|
1028
1287
|
readonly accepted: true;
|
|
1288
|
+
/** The value now stored for the key. */
|
|
1029
1289
|
readonly value: string;
|
|
1030
1290
|
} | {
|
|
1291
|
+
/** The value was refused; nothing was written and the previous translation is intact. */
|
|
1031
1292
|
readonly accepted: false;
|
|
1293
|
+
/** Which integrity rule the value broke. */
|
|
1032
1294
|
readonly reason: IntegrityGateReason;
|
|
1295
|
+
/** The rejected value, echoed back so a UI can show what was refused. */
|
|
1033
1296
|
readonly value: string;
|
|
1034
1297
|
};
|
|
1035
1298
|
/**
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
1044
|
-
* the edit
|
|
1045
|
-
*
|
|
1046
|
-
*
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
1055
|
-
*
|
|
1056
|
-
*
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1059
|
-
* @param input - The validated config, the target locale, the source key, and the replacement value.
|
|
1060
|
-
* @param deps - Optional composition seams (registry, file system) for tests.
|
|
1061
|
-
* @returns The two-armed {@link EditEntryResult}.
|
|
1299
|
+
* Saves a manually edited translation for a single key. This is the write path behind a review UI:
|
|
1300
|
+
* a human corrects one string and it is persisted without re-running the whole project.
|
|
1301
|
+
*
|
|
1302
|
+
* The edit is held to the same integrity gate as a provider translation, so a value that drops a
|
|
1303
|
+
* placeholder, breaks ICU syntax, runs away into a hugely oversized or repetition-dominated value,
|
|
1304
|
+
* or blanks a non-empty string is refused. A refusal comes back as `accepted: false` rather than as
|
|
1305
|
+
* a thrown error, and nothing is written.
|
|
1306
|
+
*
|
|
1307
|
+
* The locale's write lock is taken for the whole read-modify-write, so an edit cannot interleave
|
|
1308
|
+
* with a concurrent {@link translate} run on the same locale. The gate runs inside that lock, so a
|
|
1309
|
+
* refused edit takes and releases the lock as well and can therefore fail with `LOCK_CONTENDED`
|
|
1310
|
+
* even though it would have written nothing. An accepted edit then updates the lock-file baseline
|
|
1311
|
+
* and feeds the translation memory, which means a later run treats the key as up to date and
|
|
1312
|
+
* reuses the edited text rather than paying the provider to translate it again.
|
|
1313
|
+
*
|
|
1314
|
+
* Note that a malformed target locale file surfaces the adapter's own parse error rather than a
|
|
1315
|
+
* wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
|
|
1316
|
+
* should be ready for an unrecognized error from a target file.
|
|
1317
|
+
*
|
|
1318
|
+
* @param input - The config, locale, key, and new value.
|
|
1319
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
1320
|
+
* @returns Whether the value was accepted, and on rejection the reason.
|
|
1321
|
+
*
|
|
1062
1322
|
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
1063
|
-
* @throws {@link SdkError} `UNKNOWN_LOCALE`:
|
|
1064
|
-
* @throws {@link SdkError} `
|
|
1065
|
-
*
|
|
1066
|
-
* @throws {@link SdkError} `
|
|
1323
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE`: the requested locale is not a configured target locale.
|
|
1324
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
1325
|
+
* cannot be combined, or the locale has no valid path spelling under that style.
|
|
1326
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
1327
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
1328
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
1329
|
+
* @throws {@link SdkError} `UNKNOWN_KEY`: the key is not present in the source resource.
|
|
1330
|
+
* @throws {@link SdkError} `LOCK_CONTENDED`: the locale's write lock could not be acquired before
|
|
1331
|
+
* the timeout elapsed.
|
|
1067
1332
|
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
1068
|
-
*
|
|
1069
|
-
* @throws {@link SdkError} `LOCK_CONTENDED`: this locale's write lock could not be acquired before
|
|
1070
|
-
* its timeout, because another process is holding it.
|
|
1333
|
+
* unsupported version.
|
|
1071
1334
|
*/
|
|
1072
1335
|
declare function editEntry(input: EditEntryInput, deps?: EditEntryDeps): Promise<EditEntryResult>;
|
|
1073
1336
|
|
|
1074
|
-
/**
|
|
1075
|
-
* One key's placeholder and ICU integrity result for one target locale. Only ever computed for a
|
|
1076
|
-
* "changed" key (a current source and a current target value both exist). `hasPlaceholders` is
|
|
1077
|
-
* false when the source value carries no placeholders, in which case `matches` is trivially true
|
|
1078
|
-
* and carries no signal on its own. `icuValid` is computed unconditionally, independent of
|
|
1079
|
-
* `matches`: unlike `gateCandidateValue`, which stops at the first failing check, this is an
|
|
1080
|
-
* information report and stays accurate even when the key already fails on placeholders; it is
|
|
1081
|
-
* always true for a non-ICU format. Carries no source or target string value: only the boolean
|
|
1082
|
-
* results and, on a placeholder mismatch, the specific tokens involved.
|
|
1083
|
-
*/
|
|
1337
|
+
/** One key's placeholder and ICU verdict in a {@link LocaleKeyIntegrity} report. */
|
|
1084
1338
|
interface KeyIntegrityEntry {
|
|
1339
|
+
/** The key this verdict describes. */
|
|
1085
1340
|
readonly key: string;
|
|
1341
|
+
/** Whether the source text contains any placeholders at all. When false, `matches` is trivially true. */
|
|
1086
1342
|
readonly hasPlaceholders: boolean;
|
|
1343
|
+
/** True when the translation carries exactly the source's placeholders. */
|
|
1087
1344
|
readonly matches: boolean;
|
|
1345
|
+
/** Placeholders present in the source but absent from the translation. */
|
|
1088
1346
|
readonly missing: readonly string[];
|
|
1347
|
+
/** Placeholders present in the translation but not in the source. */
|
|
1089
1348
|
readonly extra: readonly string[];
|
|
1349
|
+
/** True when the translation parses as a valid ICU message under the configured format. */
|
|
1090
1350
|
readonly icuValid: boolean;
|
|
1091
1351
|
}
|
|
1092
|
-
/** One
|
|
1352
|
+
/** One locale's per-key integrity verdicts. */
|
|
1093
1353
|
interface LocaleKeyIntegrity {
|
|
1354
|
+
/** The target locale these verdicts describe. */
|
|
1094
1355
|
readonly locale: string;
|
|
1356
|
+
/** Verdicts for the changed keys that exist in both the source and this locale. */
|
|
1095
1357
|
readonly entries: readonly KeyIntegrityEntry[];
|
|
1096
1358
|
}
|
|
1097
|
-
/** Input for {@link keyIntegrity}
|
|
1359
|
+
/** Input for {@link keyIntegrity}. */
|
|
1098
1360
|
interface KeyIntegrityInput {
|
|
1099
|
-
/** The
|
|
1361
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
1100
1362
|
readonly config: VerbatraConfig;
|
|
1101
|
-
/** Directory the
|
|
1363
|
+
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
1102
1364
|
readonly cwd?: string;
|
|
1103
|
-
/**
|
|
1365
|
+
/** Restrict the report to these target locales. Defaults to every configured target locale. */
|
|
1104
1366
|
readonly locales?: readonly string[];
|
|
1105
|
-
/** Restrict the
|
|
1367
|
+
/** Restrict the report to these keys. Defaults to every key the diff reports as changed. */
|
|
1106
1368
|
readonly keys?: readonly string[];
|
|
1107
1369
|
}
|
|
1108
|
-
/**
|
|
1370
|
+
/** Injectable dependencies for {@link keyIntegrity}. Every field has a working default. */
|
|
1109
1371
|
interface KeyIntegrityDeps {
|
|
1372
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
1110
1373
|
readonly adapterRegistry?: AdapterRegistry;
|
|
1374
|
+
/** File-system port. Defaults to the real file system. */
|
|
1111
1375
|
readonly fs?: SdkFs;
|
|
1112
1376
|
}
|
|
1113
1377
|
/**
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1116
|
-
*
|
|
1117
|
-
*
|
|
1118
|
-
*
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1378
|
+
* Reports, per changed key, whether the existing translation still carries the source's
|
|
1379
|
+
* placeholders and still parses as valid ICU. It writes nothing and calls no provider.
|
|
1380
|
+
*
|
|
1381
|
+
* The scope is deliberately the changed keys rather than every key: a key whose source text has not
|
|
1382
|
+
* moved was already gated when it was written, so re-reporting it would bury the keys that a source
|
|
1383
|
+
* edit may have just invalidated. Only keys present in both the source and the target are judged,
|
|
1384
|
+
* since a missing translation has no placeholders to compare.
|
|
1385
|
+
*
|
|
1386
|
+
* This is the read-only counterpart to the gate that {@link editEntry} and
|
|
1387
|
+
* {@link retranslateEntry} enforce at write time, and the data behind a review dashboard's
|
|
1388
|
+
* per-key integrity indicator.
|
|
1123
1389
|
*
|
|
1124
|
-
*
|
|
1125
|
-
*
|
|
1390
|
+
* Note that a malformed target locale file surfaces the adapter's own parse error rather than a
|
|
1391
|
+
* wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
|
|
1392
|
+
* should be ready for an unrecognized error from a target file.
|
|
1126
1393
|
*
|
|
1127
|
-
* @param input - The
|
|
1128
|
-
* @param deps - Optional
|
|
1129
|
-
* @returns One entry per
|
|
1130
|
-
*
|
|
1131
|
-
* @throws {@link SdkError} `UNKNOWN_FORMAT
|
|
1132
|
-
*
|
|
1394
|
+
* @param input - The config and the optional locale and key filters.
|
|
1395
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
1396
|
+
* @returns One entry per requested locale, each holding its per-key verdicts.
|
|
1397
|
+
*
|
|
1398
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
1399
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
1400
|
+
* cannot be combined, or a configured locale has no valid path spelling under that style.
|
|
1401
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
1402
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
1403
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
1404
|
+
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
1405
|
+
* unsupported version.
|
|
1406
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE`: a requested locale is not a configured target locale.
|
|
1133
1407
|
*/
|
|
1134
1408
|
declare function keyIntegrity(input: KeyIntegrityInput, deps?: KeyIntegrityDeps): Promise<readonly LocaleKeyIntegrity[]>;
|
|
1135
1409
|
|
|
1136
|
-
/** Input for {@link keyValue}
|
|
1410
|
+
/** Input for {@link keyValue}. */
|
|
1137
1411
|
interface KeyValueInput {
|
|
1138
|
-
/** The
|
|
1412
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
1139
1413
|
readonly config: VerbatraConfig;
|
|
1140
|
-
/** Directory the
|
|
1414
|
+
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
1141
1415
|
readonly cwd?: string;
|
|
1142
|
-
/** The target locale to read the
|
|
1416
|
+
/** The target locale to read the translation from. Must be a configured target locale. */
|
|
1143
1417
|
readonly locale: string;
|
|
1144
|
-
/** The
|
|
1418
|
+
/** The key to read. Must exist in the source resource. */
|
|
1145
1419
|
readonly key: string;
|
|
1146
1420
|
}
|
|
1147
|
-
/**
|
|
1421
|
+
/** Injectable dependencies for {@link keyValue}. Every field has a working default. */
|
|
1148
1422
|
interface KeyValueDeps {
|
|
1423
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
1149
1424
|
readonly adapterRegistry?: AdapterRegistry;
|
|
1425
|
+
/** File-system port. Defaults to the real file system. */
|
|
1150
1426
|
readonly fs?: SdkFs;
|
|
1151
1427
|
}
|
|
1152
|
-
/**
|
|
1153
|
-
* The current source and target values for one key/locale pair. `target` is absent exactly when
|
|
1154
|
-
* the key does not yet exist in that target locale; a present-but-empty-string target is a real
|
|
1155
|
-
* value and is returned as such.
|
|
1156
|
-
*/
|
|
1428
|
+
/** One key's current source and target text, as returned by {@link keyValue}. */
|
|
1157
1429
|
interface KeyValueResult {
|
|
1430
|
+
/** The key's text in the source locale. Always present, since a missing key is an error. */
|
|
1158
1431
|
readonly source: string;
|
|
1432
|
+
/** The key's text in the requested target locale, or absent when it has not been translated yet. */
|
|
1159
1433
|
readonly target?: string;
|
|
1160
1434
|
}
|
|
1161
1435
|
/**
|
|
1162
|
-
* Reads
|
|
1163
|
-
*
|
|
1164
|
-
*
|
|
1436
|
+
* Reads one key's current source and target text. It writes nothing and calls no provider, and is
|
|
1437
|
+
* the read half of the single-key editing flow that {@link editEntry} and
|
|
1438
|
+
* {@link retranslateEntry} complete.
|
|
1439
|
+
*
|
|
1440
|
+
* The two sides are asymmetric on purpose: a key absent from the source is an error, because it
|
|
1441
|
+
* cannot be edited or retranslated, while a key absent from the target is normal and simply comes
|
|
1442
|
+
* back with no `target`.
|
|
1443
|
+
*
|
|
1444
|
+
* Note that a malformed target locale file surfaces the adapter's own parse error rather than a
|
|
1445
|
+
* wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
|
|
1446
|
+
* should be ready for an unrecognized error from a target file.
|
|
1165
1447
|
*
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1168
|
-
*
|
|
1448
|
+
* @param input - The config, locale, and key to read.
|
|
1449
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
1450
|
+
* @returns The key's source text and, when present, its current translation.
|
|
1169
1451
|
*
|
|
1170
|
-
* @param input - The validated config, the target locale, and the source key to read.
|
|
1171
|
-
* @param deps - Optional composition seams (registry, file system) for tests.
|
|
1172
|
-
* @returns The current source value and, when present, the current target value.
|
|
1173
1452
|
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
1174
|
-
* @throws {@link SdkError} `UNKNOWN_LOCALE`:
|
|
1175
|
-
* @throws {@link SdkError} `
|
|
1176
|
-
*
|
|
1177
|
-
* @throws {@link SdkError} `
|
|
1453
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE`: the requested locale is not a configured target locale.
|
|
1454
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
1455
|
+
* cannot be combined, or the locale has no valid path spelling under that style.
|
|
1456
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
1457
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
1458
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
1459
|
+
* @throws {@link SdkError} `UNKNOWN_KEY`: the key is not present in the source resource.
|
|
1178
1460
|
*/
|
|
1179
1461
|
declare function keyValue(input: KeyValueInput, deps?: KeyValueDeps): Promise<KeyValueResult>;
|
|
1180
1462
|
|
|
1181
1463
|
/**
|
|
1182
|
-
* One locale file reduced to a
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1464
|
+
* One locale file reduced to a per-key content hash. Holding hashes rather than values keeps a
|
|
1465
|
+
* long-lived watcher's memory flat in the size of the translations and makes comparison a cheap
|
|
1466
|
+
* equality check.
|
|
1185
1467
|
*/
|
|
1186
1468
|
interface LocaleFileSnapshot {
|
|
1187
|
-
/** The locale this snapshot was
|
|
1469
|
+
/** The locale this snapshot was taken for. */
|
|
1188
1470
|
readonly locale: string;
|
|
1189
|
-
/** Content hash per key,
|
|
1471
|
+
/** Content hash per key, in the order the adapter read them. */
|
|
1190
1472
|
readonly hashes: ReadonlyMap<string, string>;
|
|
1191
1473
|
}
|
|
1192
|
-
/** Input for {@link readLocaleFileSnapshot}
|
|
1474
|
+
/** Input for {@link readLocaleFileSnapshot}. */
|
|
1193
1475
|
interface ReadLocaleFileSnapshotInput {
|
|
1194
|
-
/** The
|
|
1476
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
1195
1477
|
readonly config: VerbatraConfig;
|
|
1196
|
-
/**
|
|
1478
|
+
/**
|
|
1479
|
+
* The locale file to snapshot. Unlike most entry points this is not checked against the
|
|
1480
|
+
* configured target locales, so the source locale can be snapshotted too.
|
|
1481
|
+
*/
|
|
1197
1482
|
readonly locale: string;
|
|
1198
|
-
/** Directory the
|
|
1483
|
+
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
1199
1484
|
readonly cwd?: string;
|
|
1200
1485
|
}
|
|
1201
|
-
/**
|
|
1486
|
+
/** Injectable dependencies for {@link readLocaleFileSnapshot}. Every field has a working default. */
|
|
1202
1487
|
interface ReadLocaleFileSnapshotDeps {
|
|
1488
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
1203
1489
|
readonly adapterRegistry?: AdapterRegistry;
|
|
1490
|
+
/** File-system port. Defaults to the real file system. */
|
|
1204
1491
|
readonly fs?: SdkFs;
|
|
1205
1492
|
}
|
|
1206
1493
|
/**
|
|
1207
|
-
* Reads one locale file
|
|
1208
|
-
*
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1494
|
+
* Reads one locale file as a per-key content hash. Together with {@link diffLocaleSnapshots} this
|
|
1495
|
+
* is the building block for a live-refresh watcher: hold a snapshot, re-read on a file event, and
|
|
1496
|
+
* compare the two to learn what actually changed.
|
|
1497
|
+
*
|
|
1498
|
+
* A locale file that does not exist yet reads as an empty snapshot rather than an error, so a
|
|
1499
|
+
* watcher can start before the first translation run has written anything.
|
|
1500
|
+
*
|
|
1501
|
+
* Note that this entry point does not validate the locale against the configured targets, and that
|
|
1502
|
+
* a malformed target file surfaces the adapter's own parse error rather than a wrapped
|
|
1503
|
+
* {@link SdkError}, because no source-file contract is being asserted here.
|
|
1211
1504
|
*
|
|
1212
|
-
* @param input - The
|
|
1213
|
-
* @param deps - Optional
|
|
1214
|
-
* @returns The locale's per-key content
|
|
1215
|
-
*
|
|
1216
|
-
*
|
|
1505
|
+
* @param input - The config and the locale to snapshot.
|
|
1506
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
1507
|
+
* @returns The locale's per-key content hashes.
|
|
1508
|
+
*
|
|
1509
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
1510
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
1511
|
+
* cannot be combined, or a configured locale has no valid path spelling under that style.
|
|
1512
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
1217
1513
|
*/
|
|
1218
1514
|
declare function readLocaleFileSnapshot(input: ReadLocaleFileSnapshotInput, deps?: ReadLocaleFileSnapshotDeps): Promise<LocaleFileSnapshot>;
|
|
1219
|
-
/**
|
|
1515
|
+
/** How many keys were added, changed, and removed between two {@link LocaleFileSnapshot}s. */
|
|
1220
1516
|
interface LocaleSnapshotDelta {
|
|
1517
|
+
/** Keys present in the current snapshot but not the previous one. */
|
|
1221
1518
|
readonly added: number;
|
|
1519
|
+
/** Keys present in both snapshots whose content hash differs. */
|
|
1222
1520
|
readonly changed: number;
|
|
1521
|
+
/** Keys present in the previous snapshot but not the current one. */
|
|
1223
1522
|
readonly removed: number;
|
|
1224
1523
|
}
|
|
1225
1524
|
/**
|
|
1226
|
-
* Compares two
|
|
1227
|
-
*
|
|
1228
|
-
* core's `diffResources`: that function's source/target/baseline shape exists for cross-resource
|
|
1229
|
-
* comparison, and forcing a same-file, two-points-in-time comparison through it would need a
|
|
1230
|
-
* confusing relabeling (missing becomes removed, orphaned becomes added).
|
|
1525
|
+
* Compares two {@link LocaleFileSnapshot}s and counts what moved. It is pure, synchronous, and
|
|
1526
|
+
* touches no file system, so a watcher can call it on every file event without cost.
|
|
1231
1527
|
*
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
1234
|
-
*
|
|
1528
|
+
* The two snapshots are compared by key and content hash alone; neither the locale names nor the
|
|
1529
|
+
* order are consulted, so passing snapshots of two different locales compares them rather than
|
|
1530
|
+
* failing.
|
|
1531
|
+
*
|
|
1532
|
+
* @param previous - The earlier snapshot.
|
|
1533
|
+
* @param current - The later snapshot.
|
|
1534
|
+
* @returns The added, changed, and removed key counts.
|
|
1235
1535
|
*/
|
|
1236
1536
|
declare function diffLocaleSnapshots(previous: LocaleFileSnapshot, current: LocaleFileSnapshot): LocaleSnapshotDelta;
|
|
1237
1537
|
|
|
1238
|
-
/**
|
|
1538
|
+
/** One locale's lock-file baseline and the drift measured against it. */
|
|
1239
1539
|
interface LockLocaleState {
|
|
1240
|
-
/** The target locale this entry
|
|
1540
|
+
/** The target locale this entry describes. */
|
|
1241
1541
|
readonly locale: string;
|
|
1242
|
-
/**
|
|
1542
|
+
/** How many keys the lock-file records a baseline hash for in this locale. */
|
|
1243
1543
|
readonly keyCount: number;
|
|
1244
|
-
/**
|
|
1544
|
+
/** Number of source keys with no translation in this locale yet. */
|
|
1245
1545
|
readonly missing: number;
|
|
1246
|
-
/**
|
|
1546
|
+
/** Number of keys whose source text changed since the recorded baseline. */
|
|
1247
1547
|
readonly stale: number;
|
|
1248
|
-
/**
|
|
1548
|
+
/** Number of keys whose translation still matches the recorded baseline. */
|
|
1249
1549
|
readonly upToDate: number;
|
|
1250
1550
|
}
|
|
1251
1551
|
/**
|
|
1252
|
-
* The
|
|
1253
|
-
*
|
|
1254
|
-
* "no lock-file yet" and "an empty but present lock-file" stay distinguishable.
|
|
1552
|
+
* The result of {@link lockState}. The absence of a lock-file is a first-class state rather than an
|
|
1553
|
+
* error, because a project that has never been translated legitimately has none.
|
|
1255
1554
|
*/
|
|
1256
1555
|
type LockStateResult = {
|
|
1556
|
+
/** No lock-file exists yet, so there is no baseline to report against. */
|
|
1257
1557
|
readonly exists: false;
|
|
1258
1558
|
} | {
|
|
1559
|
+
/** A lock-file exists and was read successfully. */
|
|
1259
1560
|
readonly exists: true;
|
|
1561
|
+
/** The lock-file's schema version. */
|
|
1260
1562
|
readonly version: number;
|
|
1563
|
+
/** Per-locale baseline sizes and drift, in configured target order. */
|
|
1261
1564
|
readonly locales: readonly LockLocaleState[];
|
|
1262
1565
|
};
|
|
1263
|
-
/** Input for {@link lockState}
|
|
1566
|
+
/** Input for {@link lockState}. */
|
|
1264
1567
|
interface LockStateInput {
|
|
1265
|
-
/** The
|
|
1568
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
1266
1569
|
readonly config: VerbatraConfig;
|
|
1267
|
-
/** Directory the
|
|
1570
|
+
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
1268
1571
|
readonly cwd?: string;
|
|
1269
|
-
/**
|
|
1572
|
+
/** Restrict the report to these target locales. Defaults to every configured target locale. */
|
|
1270
1573
|
readonly locales?: readonly string[];
|
|
1271
1574
|
}
|
|
1272
|
-
/**
|
|
1575
|
+
/** Injectable dependencies for {@link lockState}. Every field has a working default. */
|
|
1273
1576
|
interface LockStateDeps {
|
|
1577
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
1274
1578
|
readonly adapterRegistry?: AdapterRegistry;
|
|
1579
|
+
/** File-system port. Defaults to the real file system. */
|
|
1275
1580
|
readonly fs?: SdkFs;
|
|
1276
1581
|
}
|
|
1277
1582
|
/**
|
|
1278
|
-
*
|
|
1279
|
-
* baseline
|
|
1280
|
-
*
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1583
|
+
* Reports the lock-file's existence, version, and the per-locale drift measured against its
|
|
1584
|
+
* baseline. It writes nothing and calls no provider.
|
|
1585
|
+
*
|
|
1586
|
+
* Where {@link check} answers "is the project in sync", this answers "what does the lock-file
|
|
1587
|
+
* actually record", which is what a diagnostic view needs when the two disagree: a locale with
|
|
1588
|
+
* translations present but a `keyCount` of zero, for instance, means the files were written outside
|
|
1589
|
+
* verbatra and have no baseline.
|
|
1590
|
+
*
|
|
1591
|
+
* When no lock-file exists the call returns `exists: false` rather than throwing, and does no
|
|
1592
|
+
* further reading.
|
|
1593
|
+
*
|
|
1594
|
+
* Note that a malformed target locale file surfaces the adapter's own parse error rather than a
|
|
1595
|
+
* wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
|
|
1596
|
+
* should be ready for an unrecognized error from a target file.
|
|
1597
|
+
*
|
|
1598
|
+
* @param input - The config and the optional locale filter.
|
|
1599
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
1600
|
+
* @returns The lock-file's version and per-locale baseline state, or `exists: false`.
|
|
1283
1601
|
*
|
|
1284
|
-
* @
|
|
1285
|
-
* @
|
|
1286
|
-
*
|
|
1287
|
-
* @throws {@link SdkError} `
|
|
1288
|
-
*
|
|
1289
|
-
*
|
|
1602
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE`: a requested locale is not a configured target locale.
|
|
1603
|
+
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
1604
|
+
* unsupported version.
|
|
1605
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
1606
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
1607
|
+
* cannot be combined, or a configured locale has no valid path spelling under that style.
|
|
1608
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
1609
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
1610
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
1290
1611
|
*/
|
|
1291
1612
|
declare function lockState(input: LockStateInput, deps?: LockStateDeps): Promise<LockStateResult>;
|
|
1292
1613
|
|
|
1293
|
-
/**
|
|
1614
|
+
/**
|
|
1615
|
+
* Builds a {@link TranslationProvider} from the config's `provider` block. Passed as
|
|
1616
|
+
* `deps.createProvider` to {@link translate}, {@link watch}, and {@link retranslateEntry}, it is the
|
|
1617
|
+
* seam for injecting a stub in a test or a provider the SDK does not ship.
|
|
1618
|
+
*
|
|
1619
|
+
* The default implementation dispatches on the provider ID and reads the API key from the
|
|
1620
|
+
* environment. A factory that throws is reported as `PROVIDER_CONSTRUCTION_FAILED`.
|
|
1621
|
+
*/
|
|
1294
1622
|
type CreateProvider = (config: ProviderConfig) => TranslationProvider;
|
|
1295
1623
|
|
|
1296
|
-
/** Input for {@link retranslateEntry}
|
|
1624
|
+
/** Input for {@link retranslateEntry}. */
|
|
1297
1625
|
interface RetranslateEntryInput {
|
|
1298
|
-
/** The
|
|
1626
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
1299
1627
|
readonly config: VerbatraConfig;
|
|
1300
|
-
/** Directory the
|
|
1628
|
+
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
1301
1629
|
readonly cwd?: string;
|
|
1302
|
-
/** The target locale to retranslate
|
|
1630
|
+
/** The target locale to retranslate into. Must be a configured target locale. */
|
|
1303
1631
|
readonly locale: string;
|
|
1304
|
-
/** The
|
|
1632
|
+
/** The key to retranslate. Must exist in the source resource. */
|
|
1305
1633
|
readonly key: string;
|
|
1306
1634
|
}
|
|
1307
|
-
/**
|
|
1635
|
+
/** Injectable dependencies for {@link retranslateEntry}. Every field has a working default. */
|
|
1308
1636
|
interface RetranslateEntryDeps {
|
|
1637
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
1309
1638
|
readonly adapterRegistry?: AdapterRegistry;
|
|
1639
|
+
/** Provider factory. Defaults to constructing the provider named in the config. */
|
|
1310
1640
|
readonly createProvider?: CreateProvider;
|
|
1641
|
+
/** File-system port. Defaults to the real file system. */
|
|
1311
1642
|
readonly fs?: SdkFs;
|
|
1312
1643
|
}
|
|
1313
1644
|
/**
|
|
1314
|
-
* The
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1645
|
+
* The outcome of {@link retranslateEntry}. As with {@link EditEntryResult}, a value refused by the
|
|
1646
|
+
* integrity gate is reported as data rather than thrown, because it is an expected outcome of
|
|
1647
|
+
* asking a provider for a fresh translation.
|
|
1317
1648
|
*/
|
|
1318
1649
|
type RetranslateEntryResult = {
|
|
1650
|
+
/** The provider's value passed the integrity gate and was written. */
|
|
1319
1651
|
readonly accepted: true;
|
|
1652
|
+
/** The newly translated value now stored for the key. */
|
|
1320
1653
|
readonly value: string;
|
|
1654
|
+
/**
|
|
1655
|
+
* Quality signals the provider layer raised for this value, such as a length-ratio outlier or
|
|
1656
|
+
* a value identical to the source. Empty when nothing was flagged. The value is written
|
|
1657
|
+
* either way; these are advisory.
|
|
1658
|
+
*/
|
|
1321
1659
|
readonly reviewReasons: readonly ReviewReasonCode[];
|
|
1322
1660
|
} | {
|
|
1661
|
+
/** The value was refused; nothing was written and the previous translation is intact. */
|
|
1323
1662
|
readonly accepted: false;
|
|
1663
|
+
/** Which integrity rule the provider's value broke. */
|
|
1324
1664
|
readonly reason: IntegrityGateReason;
|
|
1665
|
+
/** The rejected value, echoed back so a UI can show what was refused. */
|
|
1325
1666
|
readonly value: string;
|
|
1326
1667
|
};
|
|
1327
1668
|
/**
|
|
1328
|
-
*
|
|
1329
|
-
*
|
|
1330
|
-
*
|
|
1331
|
-
*
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
*
|
|
1335
|
-
*
|
|
1336
|
-
*
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1340
|
-
*
|
|
1341
|
-
*
|
|
1342
|
-
*
|
|
1343
|
-
*
|
|
1344
|
-
*
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1351
|
-
* (`LOCK_FILE_INVALID` for a corrupt lock-file, or `LOCK_CONTENDED` if its own internal lock-file
|
|
1352
|
-
* guard cannot be acquired before its timeout), the target file already carries the new value
|
|
1353
|
-
* while the lock still records the old hash, and the thrown error does not imply nothing was
|
|
1354
|
-
* saved. The same ordering, and the same partial-write shape on that rare path, also exists in
|
|
1355
|
-
* `translate()` and `importWorkbook()`.
|
|
1356
|
-
*
|
|
1357
|
-
* @param input - The validated config, the target locale, and the source key to retranslate.
|
|
1358
|
-
* @param deps - Optional composition seams (registry, provider builder, file system) for tests.
|
|
1359
|
-
* @returns The two-armed {@link RetranslateEntryResult}.
|
|
1669
|
+
* Re-runs the configured provider for a single key and saves the result. This is the paid
|
|
1670
|
+
* counterpart to {@link editEntry}: it calls the provider and therefore spends tokens, which is why
|
|
1671
|
+
* a UI should gate it behind an explicit user action.
|
|
1672
|
+
*
|
|
1673
|
+
* The returned value goes through the same integrity gate as a full run, so a translation that
|
|
1674
|
+
* loses a placeholder or breaks ICU syntax is refused and nothing is written. Provider quality
|
|
1675
|
+
* signals are surfaced on an accepted result as `reviewReasons` rather than blocking the write.
|
|
1676
|
+
*
|
|
1677
|
+
* The locale's write lock is taken before the provider is called and held across it, covering the
|
|
1678
|
+
* whole read-translate-write cycle, so a slow provider keeps the lock for the length of its call
|
|
1679
|
+
* and a concurrent {@link translate} run on that locale waits. The lock is held for a refused
|
|
1680
|
+
* translation too, since the gate runs inside it. An accepted value then updates the lock-file
|
|
1681
|
+
* baseline and feeds the translation memory, so a later {@link translate} run sees the key as up
|
|
1682
|
+
* to date.
|
|
1683
|
+
*
|
|
1684
|
+
* Note that a malformed target locale file surfaces the adapter's own parse error rather than a
|
|
1685
|
+
* wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
|
|
1686
|
+
* should be ready for an unrecognized error from a target file.
|
|
1687
|
+
*
|
|
1688
|
+
* @param input - The config, locale, and key to retranslate.
|
|
1689
|
+
* @param deps - Optional adapter registry, provider factory, and file-system overrides.
|
|
1690
|
+
* @returns Whether the new value was accepted, with review reasons or the rejection reason.
|
|
1691
|
+
*
|
|
1360
1692
|
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
1361
|
-
* @throws {@link SdkError} `UNKNOWN_LOCALE`:
|
|
1362
|
-
* @throws {@link SdkError} `
|
|
1363
|
-
*
|
|
1364
|
-
* @throws {@link SdkError} `
|
|
1365
|
-
* @throws {@link SdkError} `
|
|
1366
|
-
*
|
|
1367
|
-
* @throws {@link
|
|
1368
|
-
*
|
|
1693
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE`: the requested locale is not a configured target locale.
|
|
1694
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
1695
|
+
* cannot be combined, or the locale has no valid path spelling under that style.
|
|
1696
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
1697
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
1698
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
1699
|
+
* @throws {@link SdkError} `UNKNOWN_KEY`: the key is not present in the source resource.
|
|
1700
|
+
* @throws {@link SdkError} `PROVIDER_CONSTRUCTION_FAILED`: the provider could not be constructed,
|
|
1701
|
+
* most often because its API key environment variable is unset.
|
|
1702
|
+
* @throws {@link SdkError} `LOCK_CONTENDED`: the locale's write lock could not be acquired before
|
|
1703
|
+
* the timeout elapsed.
|
|
1369
1704
|
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
1370
|
-
*
|
|
1371
|
-
* @throws
|
|
1372
|
-
*
|
|
1705
|
+
* unsupported version.
|
|
1706
|
+
* @throws `ProviderError` `INVALID_RESPONSE`: the provider returned no value for the key. Provider
|
|
1707
|
+
* transport and rate-limit failures propagate as `ProviderError` too, since a single-key call has
|
|
1708
|
+
* no per-locale summary to record them on.
|
|
1373
1709
|
*/
|
|
1374
1710
|
declare function retranslateEntry(input: RetranslateEntryInput, deps?: RetranslateEntryDeps): Promise<RetranslateEntryResult>;
|
|
1375
1711
|
|
|
1376
|
-
/**
|
|
1712
|
+
/**
|
|
1713
|
+
* Conditions the SDK itself reports on a locale, as opposed to the {@link ProviderNotice} codes a
|
|
1714
|
+
* provider raises. Each marks a run that completed but did something a caller may want to know
|
|
1715
|
+
* about.
|
|
1716
|
+
*
|
|
1717
|
+
* - `PLURAL_CATEGORIES_INCOMPLETE`: plural generation could not produce every category the target
|
|
1718
|
+
* language requires, so the entry is written with the categories that were produced.
|
|
1719
|
+
* - `SUB_BATCH_FAILED`: one sub-batch of a locale failed while others succeeded. The locale is
|
|
1720
|
+
* reported as `partial` rather than failed.
|
|
1721
|
+
* - `BLANK_ROW_BASELINE_RETAINED`: an imported handoff row was blank, so the existing translation
|
|
1722
|
+
* and its lock-file baseline were kept rather than being erased.
|
|
1723
|
+
* - `BUDGET_TOKENS_EXCEEDED`: the configured token budget was passed. Under `warn` the run
|
|
1724
|
+
* continues; under `stop` the remaining keys are withheld.
|
|
1725
|
+
* - `CACHE_VERSION_UNRECOGNIZED`: the translation memory is at a version this release does not
|
|
1726
|
+
* understand, so it was ignored rather than trusted.
|
|
1727
|
+
*/
|
|
1377
1728
|
type SdkNoticeCode = "PLURAL_CATEGORIES_INCOMPLETE" | "SUB_BATCH_FAILED" | "BLANK_ROW_BASELINE_RETAINED" | "BUDGET_TOKENS_EXCEEDED" | "CACHE_VERSION_UNRECOGNIZED";
|
|
1378
|
-
/**
|
|
1729
|
+
/**
|
|
1730
|
+
* Token usage as reported by the provider. Absent when the provider does not report usage, which is
|
|
1731
|
+
* the case for machine-translation APIs such as DeepL that do not bill in tokens.
|
|
1732
|
+
*/
|
|
1379
1733
|
interface UsageSummary {
|
|
1734
|
+
/** Tokens consumed by the prompts sent to the provider. */
|
|
1380
1735
|
readonly inputTokens: number;
|
|
1736
|
+
/** Tokens consumed by the provider's responses. */
|
|
1381
1737
|
readonly outputTokens: number;
|
|
1382
1738
|
}
|
|
1383
|
-
/** The configured token-budget behavior: warn and continue, or stop making further provider calls. */
|
|
1384
|
-
type BudgetBehavior = "warn" | "stop";
|
|
1385
1739
|
/**
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
1388
|
-
* provider such as DeepL, or a dry-run): the guardrail is honestly inert, `tokensUsed` stays `0`, and
|
|
1389
|
-
* `exceeded` stays `false`, never a false trip.
|
|
1740
|
+
* What a run does when it passes its token budget: `warn` finishes the work and reports the
|
|
1741
|
+
* overrun, `stop` withholds the remaining keys.
|
|
1390
1742
|
*/
|
|
1743
|
+
type BudgetBehavior = "warn" | "stop";
|
|
1744
|
+
/** The token budget in force for a run, and how much of it was actually consumed. */
|
|
1391
1745
|
interface RunBudget {
|
|
1392
|
-
/** The configured ceiling. */
|
|
1746
|
+
/** The configured ceiling, in tokens. */
|
|
1393
1747
|
readonly maxTokens: number;
|
|
1394
|
-
/**
|
|
1748
|
+
/** Whether passing the ceiling warns or stops the run. */
|
|
1395
1749
|
readonly behavior: BudgetBehavior;
|
|
1396
|
-
/**
|
|
1750
|
+
/**
|
|
1751
|
+
* Whether the configured provider reports usage at all. When false the budget cannot be enforced,
|
|
1752
|
+
* because there is nothing to count.
|
|
1753
|
+
*/
|
|
1397
1754
|
readonly supported: boolean;
|
|
1398
|
-
/**
|
|
1755
|
+
/** Tokens consumed across the whole run so far. */
|
|
1399
1756
|
readonly tokensUsed: number;
|
|
1400
|
-
/**
|
|
1757
|
+
/** True once `tokensUsed` passed `maxTokens`. */
|
|
1401
1758
|
readonly exceeded: boolean;
|
|
1402
1759
|
}
|
|
1403
|
-
/**
|
|
1404
|
-
* A notice raised by the SDK itself (not a provider), structurally identical to a {@link ProviderNotice}
|
|
1405
|
-
* so both share the {@link LocaleSummary.notices} channel. Carries only a stable code and a static,
|
|
1406
|
-
* secret-free message; never a key value or translatable content.
|
|
1407
|
-
*/
|
|
1760
|
+
/** A condition the SDK reported on a locale. See {@link SdkNoticeCode}. */
|
|
1408
1761
|
interface SdkNotice {
|
|
1409
|
-
/** The stable
|
|
1762
|
+
/** The stable notice code. Branch on this, not on the message. */
|
|
1410
1763
|
readonly code: SdkNoticeCode;
|
|
1411
|
-
/** A
|
|
1764
|
+
/** A human-readable description of the condition. */
|
|
1412
1765
|
readonly message: string;
|
|
1413
1766
|
}
|
|
1414
|
-
/**
|
|
1767
|
+
/**
|
|
1768
|
+
* Any notice attached to a locale: either one the provider raised, such as a downgraded formality
|
|
1769
|
+
* or an ignored glossary, or one the SDK raised. Discriminate on `code`.
|
|
1770
|
+
*/
|
|
1415
1771
|
type LocaleNotice = ProviderNotice | SdkNotice;
|
|
1416
|
-
/**
|
|
1772
|
+
/**
|
|
1773
|
+
* A translated key the provider layer flagged as worth a human look. The translation was still
|
|
1774
|
+
* written; these are advisory quality signals, not rejections.
|
|
1775
|
+
*/
|
|
1417
1776
|
interface NeedsReviewEntry {
|
|
1418
|
-
/** The flagged
|
|
1777
|
+
/** The key that was flagged. */
|
|
1419
1778
|
readonly key: string;
|
|
1420
|
-
/**
|
|
1779
|
+
/** Why it was flagged. A key can carry more than one reason. */
|
|
1421
1780
|
readonly reasons: readonly ReviewReasonCode[];
|
|
1422
1781
|
}
|
|
1423
|
-
/**
|
|
1424
|
-
* One row a workbook import could not read: the 1-based row number, the file line for a delimited
|
|
1425
|
-
* import, and the header label of the offending column. Carries no cell content, so untrusted workbook
|
|
1426
|
-
* text never reaches the summary.
|
|
1427
|
-
*/
|
|
1782
|
+
/** A row of an imported handoff that could not be read. Reported rather than aborting the import. */
|
|
1428
1783
|
interface MalformedRowReport {
|
|
1429
|
-
/** The 1-based
|
|
1784
|
+
/** The row's 1-based index within its sheet or file. */
|
|
1430
1785
|
readonly row: number;
|
|
1431
1786
|
/**
|
|
1432
|
-
* The 1-based
|
|
1433
|
-
*
|
|
1434
|
-
* both numbers are reported.
|
|
1787
|
+
* The 1-based physical line in the source file, for delimited formats where a quoted value can
|
|
1788
|
+
* span several lines. Absent for `.xlsx`, which has rows but no lines.
|
|
1435
1789
|
*/
|
|
1436
1790
|
readonly line?: number;
|
|
1437
|
-
/** The
|
|
1791
|
+
/** The column whose value was missing or unusable. */
|
|
1438
1792
|
readonly column: string;
|
|
1439
1793
|
}
|
|
1440
1794
|
/**
|
|
1441
|
-
*
|
|
1442
|
-
*
|
|
1795
|
+
* A key that appeared more than once in an imported handoff. The first occurrence wins and the rest
|
|
1796
|
+
* are reported here, so a translator who duplicated a row learns which value was actually used.
|
|
1443
1797
|
*/
|
|
1444
1798
|
interface DuplicateKeyReport {
|
|
1445
|
-
/** The key that appeared more than once
|
|
1799
|
+
/** The key that appeared more than once. */
|
|
1446
1800
|
readonly key: string;
|
|
1447
|
-
/** The 1-based
|
|
1801
|
+
/** The 1-based row index of the ignored occurrence. */
|
|
1448
1802
|
readonly row: number;
|
|
1449
|
-
/**
|
|
1450
|
-
* The 1-based file line this later occurrence starts on. Present only for a delimited (csv or tsv)
|
|
1451
|
-
* import.
|
|
1452
|
-
*
|
|
1453
|
-
* A delimited record holding a quoted line break covers one spreadsheet row but several editor
|
|
1454
|
-
* lines, so `row` is the number a translator sees in a spreadsheet and `line` the one they see in a
|
|
1455
|
-
* text editor. Both are reported so the position holds whichever tool they opened the file in.
|
|
1456
|
-
*/
|
|
1803
|
+
/** The 1-based physical line of the ignored occurrence, for delimited formats. Absent for `.xlsx`. */
|
|
1457
1804
|
readonly line?: number;
|
|
1458
1805
|
}
|
|
1459
|
-
/**
|
|
1806
|
+
/**
|
|
1807
|
+
* Everything that happened for one locale during a run. The key lists are disjoint accounts of what
|
|
1808
|
+
* became of each key, so a caller can reconstruct the whole run without re-reading any file.
|
|
1809
|
+
*
|
|
1810
|
+
* A locale that failed outright still appears here with `status: "failed"` and an `error`, rather
|
|
1811
|
+
* than the whole run throwing. That is the central contract of {@link translate}: one unreachable
|
|
1812
|
+
* provider or one unwritable file does not discard the locales that succeeded.
|
|
1813
|
+
*/
|
|
1460
1814
|
interface LocaleSummary {
|
|
1461
|
-
/** The target locale this summary
|
|
1815
|
+
/** The target locale this summary describes. */
|
|
1462
1816
|
readonly locale: string;
|
|
1463
1817
|
/**
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1466
|
-
* at least one key was accepted and written (a {@link LocaleSummary.translated} key or a
|
|
1467
|
-
* {@link LocaleSummary.generated} plural form) but at least one was withheld (any of
|
|
1468
|
-
* {@link LocaleSummary.integrityMismatches}, {@link LocaleSummary.providerFailures}, or
|
|
1469
|
-
* {@link LocaleSummary.budgetWithheld} non-empty). `"failed"` when the locale had candidate keys
|
|
1470
|
-
* but accepted nothing at all (neither translated nor generated) while withholding at least one.
|
|
1471
|
-
* Withheld keys keep their prior lock hash and retry next run.
|
|
1472
|
-
*
|
|
1473
|
-
* A workbook import's structural findings, {@link LocaleSummary.unfilled},
|
|
1474
|
-
* {@link LocaleSummary.malformedRows}, and {@link LocaleSummary.duplicateKeys}, deliberately do NOT
|
|
1475
|
-
* feed this status: they are surfaced in their own lists (and the CLI) but are not withholdings of
|
|
1476
|
-
* an attempted translation, so a locale that dropped a malformed row, collapsed a duplicate key, or
|
|
1477
|
-
* left a row unfilled while accepting its other rows can still be `"succeeded"`.
|
|
1478
|
-
*
|
|
1479
|
-
* This is a settled decision, not an omission. Recording the reasoning here so the question stops
|
|
1480
|
-
* recurring:
|
|
1481
|
-
*
|
|
1482
|
-
* 1. Two questions, two commands. `import`'s exit code answers "did the import apply what the
|
|
1483
|
-
* translator gave me". `check` and `diff` answer "is this project fully translated", and both
|
|
1484
|
-
* already exit non-zero or report pending in every one of these cases. Folding pending human
|
|
1485
|
-
* work into the import exit code duplicates an existing gate rather than adding one.
|
|
1486
|
-
* 2. It would break the staged handoff. With `de` and `fr` configured and only `de` filled in,
|
|
1487
|
-
* failing on unfilled work makes `fr` fail and takes the whole import with it, even though `de`
|
|
1488
|
-
* imported perfectly. Locale-at-a-time handoff is this feature's most common workflow.
|
|
1489
|
-
* 3. {@link LocaleSummary.malformedRows} structurally cannot signal lost work. The Status column is
|
|
1490
|
-
* the only reachable rejection in the workbook reader; every other field is an unconstrained
|
|
1491
|
-
* string or a tolerant parse. So a row is malformed on its Status cell alone, entirely
|
|
1492
|
-
* independently of whether its Translation cell was filled. An untouched sheet with a mangled
|
|
1493
|
-
* Status column (spreadsheet autocorrect, a CAT-tool round trip) is all-malformed with zero lost
|
|
1494
|
-
* work. A bucket that cannot distinguish dropped work from absent work cannot honestly drive a
|
|
1495
|
-
* failure status.
|
|
1496
|
-
* 4. {@link LocaleSummary.duplicateKeys} never could: the first occurrence wins and the intent is
|
|
1497
|
-
* honoured.
|
|
1818
|
+
* `succeeded` when everything asked for was done, `partial` when some keys were translated and
|
|
1819
|
+
* others were not, and `failed` when the locale produced no usable result.
|
|
1498
1820
|
*/
|
|
1499
1821
|
readonly status: "succeeded" | "partial" | "failed";
|
|
1500
|
-
/**
|
|
1501
|
-
* Keys translated and written this run. In dry-run, the keys that would be translated
|
|
1502
|
-
* (the provider is not called and nothing is written).
|
|
1503
|
-
*/
|
|
1822
|
+
/** Keys newly translated by the provider in this run. */
|
|
1504
1823
|
readonly translated: readonly string[];
|
|
1505
|
-
/** Keys already up to date,
|
|
1824
|
+
/** Keys already up to date against the lock-file baseline, so no provider call was made. */
|
|
1506
1825
|
readonly unchanged: readonly string[];
|
|
1507
|
-
/**
|
|
1826
|
+
/** Keys present in this locale but no longer in the source. Reported, and removed only when pruning. */
|
|
1508
1827
|
readonly orphaned: readonly string[];
|
|
1509
|
-
/**
|
|
1510
|
-
* Orphaned keys actually removed this run because pruning was on. In a dry-run with pruning on, the keys
|
|
1511
|
-
* that would be removed. Empty when pruning is off (the orphans then survive and are reported in
|
|
1512
|
-
* `orphaned` only). A subset of `orphaned`; never includes a source-present key.
|
|
1513
|
-
*/
|
|
1828
|
+
/** Orphaned keys that were actually removed, which happens only when the run was asked to prune. */
|
|
1514
1829
|
readonly pruned: readonly string[];
|
|
1515
|
-
/** Source keys flagged invalid-ICU that were skipped for translation this run. */
|
|
1516
|
-
readonly invalidIcuSource: readonly string[];
|
|
1517
1830
|
/**
|
|
1518
|
-
* Keys
|
|
1519
|
-
*
|
|
1520
|
-
* the integrity gate, so no provider request was made for them. They are written and lock-advanced
|
|
1521
|
-
* exactly like a translated key, but kept distinct from {@link LocaleSummary.translated} because they
|
|
1522
|
-
* cost no provider usage. Always present, empty by default; empty in a dry-run and when the cache is
|
|
1523
|
-
* bypassed. Sorted by key.
|
|
1831
|
+
* Keys whose source text is not valid ICU. They are skipped rather than sent to the provider,
|
|
1832
|
+
* because a broken source message cannot yield a sound translation.
|
|
1524
1833
|
*/
|
|
1834
|
+
readonly invalidIcuSource: readonly string[];
|
|
1835
|
+
/** Keys served from the translation memory instead of the provider, and so not paid for. */
|
|
1525
1836
|
readonly cacheHits: readonly string[];
|
|
1526
1837
|
/**
|
|
1527
|
-
*
|
|
1528
|
-
*
|
|
1529
|
-
* for that case.
|
|
1838
|
+
* Keys whose translation was refused by the integrity gate, for instance because it dropped a
|
|
1839
|
+
* placeholder. The previous translation, if any, is left untouched.
|
|
1530
1840
|
*/
|
|
1531
1841
|
readonly integrityMismatches: readonly string[];
|
|
1532
|
-
/**
|
|
1533
|
-
* Keys withheld because nothing was translated for them this run, as distinct from a translation
|
|
1534
|
-
* that came back and failed the placeholder-integrity check. This covers two causes: the provider
|
|
1535
|
-
* call itself failed (for example a revoked API key, a rate limit, or a network timeout), in which
|
|
1536
|
-
* case the corresponding {@link LocaleSummary.notices} entry carries the secret-free failure code
|
|
1537
|
-
* and message; or the call succeeded but the key was still missing or duplicated in the response
|
|
1538
|
-
* after the shared LLM layer's bounded reconcile repair round, in which case no notice is added.
|
|
1539
|
-
* Empty in a dry-run.
|
|
1540
|
-
*/
|
|
1842
|
+
/** Keys the provider failed to translate, for instance because their sub-batch errored. */
|
|
1541
1843
|
readonly providerFailures: readonly string[];
|
|
1542
|
-
/**
|
|
1543
|
-
* Plural-category keys verbatra synthesized this run (for example a Polish `items_few` the source
|
|
1544
|
-
* never supplied), kept distinct from {@link translated}. Empty unless plural generation was enabled
|
|
1545
|
-
* and acted, and empty in a dry-run.
|
|
1546
|
-
*/
|
|
1844
|
+
/** Keys whose plural categories were generated for the target language rather than translated one by one. */
|
|
1547
1845
|
readonly generated: readonly string[];
|
|
1548
|
-
/**
|
|
1549
|
-
* Candidate keys never sent to the provider because a configured `maxTokens` budget already tripped
|
|
1550
|
-
* in `"stop"` mode: keys remaining in the current locale once the ceiling was crossed, and every
|
|
1551
|
-
* candidate key of a later, fully-skipped locale. Always present, empty by default, same convention as
|
|
1552
|
-
* {@link LocaleSummary.integrityMismatches} and {@link LocaleSummary.providerFailures}. Each key keeps
|
|
1553
|
-
* its prior lock hash and is picked up again next run, exactly like a `providerFailures` key.
|
|
1554
|
-
*/
|
|
1846
|
+
/** Keys not translated because the token budget was exhausted under `stop` behavior. */
|
|
1555
1847
|
readonly budgetWithheld: readonly string[];
|
|
1556
|
-
/**
|
|
1557
|
-
* Summed token usage across every provider call for this locale (main translation plus plural
|
|
1558
|
-
* generation). Absent when no call in this locale reported usage: a dry-run, a token-less provider
|
|
1559
|
-
* (DeepL), or a locale with nothing to translate. Never a fabricated `{ inputTokens: 0, outputTokens: 0 }`.
|
|
1560
|
-
*/
|
|
1848
|
+
/** Token usage for this locale. Absent when the provider does not report usage. */
|
|
1561
1849
|
readonly usage?: UsageSummary;
|
|
1562
|
-
/**
|
|
1563
|
-
* Notices for this locale: provider notices (e.g. DeepL graceful-degradation) and SDK notices
|
|
1564
|
-
* (e.g. a target language needing more CLDR plural categories than the source supplies). Empty when
|
|
1565
|
-
* nothing was degraded or flagged.
|
|
1566
|
-
*/
|
|
1850
|
+
/** Provider and SDK notices raised while running this locale. Always present, possibly empty. */
|
|
1567
1851
|
readonly notices: readonly LocaleNotice[];
|
|
1568
|
-
/**
|
|
1569
|
-
* Keys accepted and written this run that the review heuristics flagged for a second look, sorted
|
|
1570
|
-
* by key. Distinct from the placeholder/ICU integrity gate: a review flag is advisory only and never
|
|
1571
|
-
* withholds a key, so this never double-reports a key already surfaced through
|
|
1572
|
-
* {@link LocaleSummary.integrityMismatches} or {@link LocaleSummary.providerFailures}. Empty when
|
|
1573
|
-
* nothing was flagged, always empty for a workbook import (which never calls a provider or recomputes
|
|
1574
|
-
* flags on its own path).
|
|
1575
|
-
*/
|
|
1852
|
+
/** Translated keys flagged as worth a human look. The translations were still written. */
|
|
1576
1853
|
readonly needsReview: readonly NeedsReviewEntry[];
|
|
1577
|
-
/**
|
|
1578
|
-
* Keys the translator left blank that still need a translation, drifted or not, sorted by key.
|
|
1579
|
-
* Pending work made visible: the row is skipped (nothing is written and the prior lock baseline is
|
|
1580
|
-
* kept), but the key is surfaced so an unfinished import is never silently reported as done. Only a
|
|
1581
|
-
* workbook import populates this; the provider path, which never leaves a candidate key unfilled by
|
|
1582
|
-
* a person, always leaves it empty.
|
|
1583
|
-
*
|
|
1584
|
-
* Membership is decided by the import-time diff, not by the `status` the row was exported with: a
|
|
1585
|
-
* never-translated key exports as `new`, and a first handoff is all-new, which is the most common
|
|
1586
|
-
* unfilled case there is. A row exported as `changed` whose key no longer needs work by import time
|
|
1587
|
-
* is correspondingly excluded.
|
|
1588
|
-
*/
|
|
1854
|
+
/** Keys left with no translation after the run, whatever the cause. */
|
|
1589
1855
|
readonly unfilled: readonly string[];
|
|
1590
|
-
/**
|
|
1591
|
-
* Rows a workbook import could not read for this locale, each by worksheet row number and offending
|
|
1592
|
-
* column, in row order. The sheet's other rows still import; a malformed row is reported, not
|
|
1593
|
-
* written. Only a workbook import populates this; the provider path always leaves it empty.
|
|
1594
|
-
*/
|
|
1856
|
+
/** Unreadable rows from an imported handoff. Always empty for a {@link translate} run. */
|
|
1595
1857
|
readonly malformedRows: readonly MalformedRowReport[];
|
|
1596
|
-
/**
|
|
1597
|
-
* Duplicate-key conflicts a workbook import found in this locale's sheet, in row order. The rule is
|
|
1598
|
-
* first occurrence wins: the first row for a key is judged and its later occurrences are reported
|
|
1599
|
-
* here and otherwise ignored. Only a workbook import populates this; the provider path always leaves
|
|
1600
|
-
* it empty.
|
|
1601
|
-
*/
|
|
1858
|
+
/** Repeated keys from an imported handoff. Always empty for a {@link translate} run. */
|
|
1602
1859
|
readonly duplicateKeys: readonly DuplicateKeyReport[];
|
|
1603
1860
|
/**
|
|
1604
|
-
*
|
|
1605
|
-
*
|
|
1606
|
-
*
|
|
1607
|
-
* `
|
|
1608
|
-
*
|
|
1609
|
-
* `
|
|
1610
|
-
* it as a
|
|
1861
|
+
* Why this locale failed, when the failure came from a thrown error. The `code` is the failure's
|
|
1862
|
+
* own code where it has one, and `LOCALE_FAILED` otherwise. Never carries a secret.
|
|
1863
|
+
*
|
|
1864
|
+
* Absent unless `status` is `failed`, but a `failed` locale does not always carry it: a locale
|
|
1865
|
+
* whose every key was withheld by the integrity gate, a provider failure, or the token budget is
|
|
1866
|
+
* `failed` with nothing thrown, so this stays undefined and the withheld keys are the account of
|
|
1867
|
+
* what went wrong. Treat it as an optional detail on a failure, never as the failure test.
|
|
1611
1868
|
*/
|
|
1612
1869
|
readonly error?: {
|
|
1870
|
+
/** The failure's own code where it had one, and `LOCALE_FAILED` otherwise. */
|
|
1613
1871
|
readonly code: string;
|
|
1872
|
+
/** A human-readable description of the failure. Never contains a secret. */
|
|
1614
1873
|
readonly message: string;
|
|
1615
1874
|
};
|
|
1616
1875
|
}
|
|
1617
|
-
/**
|
|
1876
|
+
/**
|
|
1877
|
+
* The result of a whole run, returned by {@link translate}, {@link watch}, and
|
|
1878
|
+
* {@link importWorkbook}.
|
|
1879
|
+
*
|
|
1880
|
+
* Per-locale outcomes are data, not exceptions: inspect `failed` and `partial` to decide an exit
|
|
1881
|
+
* code rather than relying on the call to throw. Only whole-run failures, such as an invalid config
|
|
1882
|
+
* or an unreadable source file, throw an {@link SdkError}.
|
|
1883
|
+
*/
|
|
1618
1884
|
interface RunSummary {
|
|
1619
|
-
/**
|
|
1885
|
+
/** True when the run computed everything but wrote nothing and called no provider. */
|
|
1620
1886
|
readonly dryRun: boolean;
|
|
1621
|
-
/**
|
|
1887
|
+
/** The full per-locale account, in configured target order. */
|
|
1622
1888
|
readonly locales: readonly LocaleSummary[];
|
|
1623
|
-
/**
|
|
1889
|
+
/** Names of the locales whose status is `succeeded`. */
|
|
1624
1890
|
readonly succeeded: readonly string[];
|
|
1625
|
-
/**
|
|
1626
|
-
* Locales that wrote at least one translation but withheld at least one candidate key (status
|
|
1627
|
-
* `"partial"`). A partial locale still exits the CLI `0`: it made progress, and its withheld keys
|
|
1628
|
-
* retry next run. Never overlaps `succeeded` or `failed`.
|
|
1629
|
-
*/
|
|
1891
|
+
/** Names of the locales whose status is `partial`. */
|
|
1630
1892
|
readonly partial: readonly string[];
|
|
1631
|
-
/**
|
|
1893
|
+
/** Names of the locales whose status is `failed`. Check this before treating a run as clean. */
|
|
1632
1894
|
readonly failed: readonly string[];
|
|
1633
|
-
/**
|
|
1634
|
-
* Summed token usage across every locale's {@link LocaleSummary.usage}. Absent when no locale in the
|
|
1635
|
-
* run reported usage (a dry-run, or an all-token-less-provider run).
|
|
1636
|
-
*/
|
|
1895
|
+
/** Token usage summed across every locale. Absent when the provider does not report usage. */
|
|
1637
1896
|
readonly usage?: UsageSummary;
|
|
1638
|
-
/** The
|
|
1897
|
+
/** The token budget in force, present only when the config set one. */
|
|
1639
1898
|
readonly budget?: RunBudget;
|
|
1640
1899
|
}
|
|
1641
1900
|
|
|
1642
1901
|
/**
|
|
1643
|
-
* One
|
|
1644
|
-
*
|
|
1645
|
-
*
|
|
1902
|
+
* One locale's entry in a persisted {@link RunStatusFile}. It is a deliberately narrow projection
|
|
1903
|
+
* of {@link LocaleSummary}: only the parts a later session still needs, namely the outcome, the
|
|
1904
|
+
* review flags, and the cost.
|
|
1646
1905
|
*/
|
|
1647
1906
|
interface RunStatusLocale {
|
|
1907
|
+
/** The target locale this entry describes. */
|
|
1648
1908
|
readonly locale: string;
|
|
1909
|
+
/** How the locale finished in the recorded run. */
|
|
1649
1910
|
readonly status: "succeeded" | "partial" | "failed";
|
|
1650
|
-
/**
|
|
1911
|
+
/** Keys the run flagged as worth a human look. */
|
|
1651
1912
|
readonly needsReview: readonly NeedsReviewEntry[];
|
|
1652
|
-
/**
|
|
1913
|
+
/** Token usage for this locale. Absent when the provider does not report usage. */
|
|
1653
1914
|
readonly usage?: UsageSummary;
|
|
1654
1915
|
}
|
|
1655
1916
|
/**
|
|
1656
|
-
* The
|
|
1657
|
-
*
|
|
1658
|
-
*
|
|
1659
|
-
*
|
|
1917
|
+
* The snapshot a completed non-dry-run writes to `.verbatra-local/run-status.json`, read back by
|
|
1918
|
+
* {@link runStatus}. It lets a tool started after the run, such as a dashboard opened once
|
|
1919
|
+
* translation finished, still show what was flagged and what it cost.
|
|
1920
|
+
*
|
|
1921
|
+
* It is local state, not project state: unlike the lock-file it is not meant to be committed, and
|
|
1922
|
+
* losing it costs nothing beyond that history.
|
|
1660
1923
|
*/
|
|
1661
1924
|
interface RunStatusFile {
|
|
1925
|
+
/** The status-file schema version. A file at an unrecognized version is ignored rather than trusted. */
|
|
1662
1926
|
readonly version: number;
|
|
1663
|
-
/**
|
|
1927
|
+
/** When the run finished, as an ISO 8601 timestamp. */
|
|
1664
1928
|
readonly generatedAt: string;
|
|
1665
|
-
/**
|
|
1929
|
+
/** Token usage summed across every locale. Absent when the provider does not report usage. */
|
|
1666
1930
|
readonly usage?: UsageSummary;
|
|
1667
|
-
/** The
|
|
1931
|
+
/** The token budget in force during the run, present only when one was configured. */
|
|
1668
1932
|
readonly budget?: RunBudget;
|
|
1933
|
+
/** Per-locale outcomes from the recorded run. */
|
|
1669
1934
|
readonly locales: readonly RunStatusLocale[];
|
|
1670
1935
|
}
|
|
1671
1936
|
|
|
1672
|
-
/** Input for {@link runStatus}
|
|
1937
|
+
/** Input for {@link runStatus}. */
|
|
1673
1938
|
interface RunStatusInput {
|
|
1674
|
-
/** Directory the
|
|
1939
|
+
/** Directory holding the `.verbatra-local` status directory. Defaults to the process working directory. */
|
|
1675
1940
|
readonly cwd?: string;
|
|
1676
1941
|
}
|
|
1677
|
-
/**
|
|
1942
|
+
/** Injectable dependencies for {@link runStatus}. Every field has a working default. */
|
|
1678
1943
|
interface RunStatusDeps {
|
|
1944
|
+
/** File-system port. Defaults to the real file system. */
|
|
1679
1945
|
readonly fs?: SdkFs;
|
|
1680
1946
|
}
|
|
1681
1947
|
/**
|
|
1682
|
-
* The
|
|
1683
|
-
*
|
|
1684
|
-
* {@link runStatus} folds into it: never a distinct error, so a caller does not need to special-case
|
|
1685
|
-
* "first run" apart from "corrupt file" apart from "unrecognized version".
|
|
1948
|
+
* The result of {@link runStatus}. Having no readable status file is a normal state, not an error:
|
|
1949
|
+
* it simply means no non-dry-run has completed in this directory yet.
|
|
1686
1950
|
*/
|
|
1687
1951
|
type RunStatusResult = {
|
|
1952
|
+
/** No usable status file was found, so there is nothing to report. */
|
|
1688
1953
|
readonly available: false;
|
|
1689
1954
|
} | ({
|
|
1955
|
+
/** A status file was found and parsed; the {@link RunStatusFile} fields are spread alongside. */
|
|
1690
1956
|
readonly available: true;
|
|
1691
1957
|
} & RunStatusFile);
|
|
1692
1958
|
/**
|
|
1693
|
-
*
|
|
1694
|
-
* `.verbatra-local/run-status.json
|
|
1695
|
-
*
|
|
1696
|
-
*
|
|
1697
|
-
*
|
|
1959
|
+
* Reads the review-flag and token-usage snapshot that the last non-dry-run {@link translate} or
|
|
1960
|
+
* {@link watch} left behind in `.verbatra-local/run-status.json`. It writes nothing and calls no
|
|
1961
|
+
* provider.
|
|
1962
|
+
*
|
|
1963
|
+
* This exists so a tool started after a run, such as a dashboard opened once translation finished,
|
|
1964
|
+
* can still show which keys were flagged for review and what the run cost, without re-running
|
|
1965
|
+
* anything.
|
|
1966
|
+
*
|
|
1967
|
+
* The read is deliberately total: a missing, oversized, unparseable, schema-invalid, or
|
|
1968
|
+
* wrong-version file all report `available: false` rather than throwing, because stale local status
|
|
1969
|
+
* should never break the tool reading it. This call throws nothing.
|
|
1698
1970
|
*
|
|
1699
|
-
* @param input -
|
|
1700
|
-
* @param deps - Optional
|
|
1701
|
-
* @returns
|
|
1971
|
+
* @param input - The optional working directory.
|
|
1972
|
+
* @param deps - Optional file-system override.
|
|
1973
|
+
* @returns The persisted run status, or `available: false` when none is usable.
|
|
1702
1974
|
*/
|
|
1703
1975
|
declare function runStatus(input?: RunStatusInput, deps?: RunStatusDeps): Promise<RunStatusResult>;
|
|
1704
1976
|
|
|
1705
|
-
/**
|
|
1977
|
+
/**
|
|
1978
|
+
* Whatever could be read about the process currently holding a write lock. Both fields are optional
|
|
1979
|
+
* because a lock file left behind by a killed process may be truncated or unreadable, and reporting
|
|
1980
|
+
* a partial holder is more useful than reporting none.
|
|
1981
|
+
*/
|
|
1706
1982
|
interface LockHolder {
|
|
1707
|
-
/** The
|
|
1983
|
+
/** The process ID recorded when the lock was taken. */
|
|
1708
1984
|
readonly pid?: number;
|
|
1709
|
-
/**
|
|
1985
|
+
/** When the lock was taken, as an ISO 8601 timestamp. */
|
|
1710
1986
|
readonly acquiredAt?: string;
|
|
1711
1987
|
}
|
|
1712
|
-
/**
|
|
1988
|
+
/**
|
|
1989
|
+
* Reported while a run waits for another process to release a locale's write lock. It exists so a
|
|
1990
|
+
* CLI or UI can explain a stall rather than appearing to hang, and can name the lock file a user
|
|
1991
|
+
* may need to delete after a crash.
|
|
1992
|
+
*/
|
|
1713
1993
|
interface LockWaitEvent {
|
|
1714
|
-
/**
|
|
1994
|
+
/** Absolute path of the lock file being waited on. */
|
|
1715
1995
|
readonly lockPath: string;
|
|
1716
|
-
/**
|
|
1996
|
+
/** How long this acquisition has been waiting, in milliseconds. */
|
|
1717
1997
|
readonly elapsedMs: number;
|
|
1718
|
-
/**
|
|
1719
|
-
* Diagnostics about the holding process, present only when the lock file could be read and parsed.
|
|
1720
|
-
* Purely for messaging; never consulted for any acquire decision.
|
|
1721
|
-
*/
|
|
1998
|
+
/** What is known about the holding process, when anything could be read. */
|
|
1722
1999
|
readonly holder?: LockHolder;
|
|
1723
2000
|
}
|
|
1724
|
-
/**
|
|
2001
|
+
/**
|
|
2002
|
+
* Called on each poll while waiting for a locale's write lock. Passed as `onLockWait` to
|
|
2003
|
+
* {@link translate} and {@link watch}.
|
|
2004
|
+
*/
|
|
1725
2005
|
type LockWaitListener = (event: LockWaitEvent) => void;
|
|
1726
2006
|
|
|
1727
2007
|
/**
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
* nothing to any stream, so a caller (the CLI) is free to render these to stderr and keep stdout a
|
|
1731
|
-
* clean summary or NDJSON stream. Discriminated on `type`, mirroring the lock-wait event contract.
|
|
2008
|
+
* Any event a run emits while it works. Discriminate on `type`. Progress is reported rather than
|
|
2009
|
+
* printed so that a CLI, a dashboard, and a CI log can each render it in their own way.
|
|
1732
2010
|
*/
|
|
1733
2011
|
type ProgressEvent = LocaleStartedEvent | SubBatchProgressEvent | LocaleFinishedEvent | RunFinishedEvent;
|
|
1734
|
-
/**
|
|
2012
|
+
/** Emitted when a locale's work begins. */
|
|
1735
2013
|
interface LocaleStartedEvent {
|
|
2014
|
+
/** Discriminant for {@link ProgressEvent}. */
|
|
1736
2015
|
readonly type: "locale-started";
|
|
1737
|
-
/** The
|
|
2016
|
+
/** The locale starting. */
|
|
1738
2017
|
readonly locale: string;
|
|
1739
|
-
/**
|
|
2018
|
+
/**
|
|
2019
|
+
* This locale's 0-based position among the run's locales. With concurrency above 1 several
|
|
2020
|
+
* locales are in flight at once, so these indices do not arrive in order.
|
|
2021
|
+
*/
|
|
1740
2022
|
readonly localeIndex: number;
|
|
1741
|
-
/**
|
|
2023
|
+
/** How many locales the run covers in total. */
|
|
1742
2024
|
readonly totalLocales: number;
|
|
1743
2025
|
}
|
|
1744
2026
|
/**
|
|
1745
|
-
*
|
|
1746
|
-
*
|
|
1747
|
-
*
|
|
1748
|
-
*
|
|
1749
|
-
*
|
|
2027
|
+
* Emitted as each sub-batch of a locale is reached, immediately before the provider call for it is
|
|
2028
|
+
* attempted. Large locales are split into batches, so this is the finer-grained signal to drive a
|
|
2029
|
+
* progress bar with.
|
|
2030
|
+
*
|
|
2031
|
+
* It announces an attempt rather than confirming a send: a batch withheld because the token budget
|
|
2032
|
+
* has already stopped the run still emits, and no provider call follows it. That is deliberate, so
|
|
2033
|
+
* that `batchIndex` always advances to `totalBatches` and a progress bar reaches its end instead of
|
|
2034
|
+
* stalling at the point the budget tripped.
|
|
1750
2035
|
*/
|
|
1751
2036
|
interface SubBatchProgressEvent {
|
|
2037
|
+
/** Discriminant for {@link ProgressEvent}. */
|
|
1752
2038
|
readonly type: "sub-batch";
|
|
1753
|
-
/** The locale
|
|
2039
|
+
/** The locale this batch belongs to. */
|
|
1754
2040
|
readonly locale: string;
|
|
1755
|
-
/**
|
|
2041
|
+
/** This batch's 1-based position within the locale, counting up to `totalBatches`. */
|
|
1756
2042
|
readonly batchIndex: number;
|
|
1757
|
-
/**
|
|
2043
|
+
/** How many batches the locale was split into. */
|
|
1758
2044
|
readonly totalBatches: number;
|
|
1759
2045
|
}
|
|
1760
|
-
/**
|
|
2046
|
+
/** Emitted when a locale's work ends, whether it succeeded, partially succeeded, or failed. */
|
|
1761
2047
|
interface LocaleFinishedEvent {
|
|
2048
|
+
/** Discriminant for {@link ProgressEvent}. */
|
|
1762
2049
|
readonly type: "locale-finished";
|
|
1763
|
-
/** The
|
|
2050
|
+
/** The locale that finished. */
|
|
1764
2051
|
readonly locale: string;
|
|
1765
|
-
/**
|
|
2052
|
+
/** How many keys were newly translated for this locale. */
|
|
1766
2053
|
readonly translated: number;
|
|
1767
|
-
/**
|
|
1768
|
-
* Zero-based index of this locale in the run's target-locale order, carrying the same value as the
|
|
1769
|
-
* matching {@link LocaleStartedEvent}. This is a correlation key, so a consumer can pair a finish
|
|
1770
|
-
* with its start without matching on the locale name.
|
|
1771
|
-
*
|
|
1772
|
-
* It is deliberately NOT a progress counter. The worker pool claims indices up front, so this is a
|
|
1773
|
-
* claim ordinal rather than a completion ordinal: under concurrency the locales finish in a
|
|
1774
|
-
* different order, and rendering `localeIndex + 1` of `totalLocales` on a finish produces a
|
|
1775
|
-
* counter that jumps around and never reaches its own total in order. Do not reintroduce one.
|
|
1776
|
-
*/
|
|
2054
|
+
/** This locale's 0-based position among the run's locales. */
|
|
1777
2055
|
readonly localeIndex: number;
|
|
1778
|
-
/**
|
|
2056
|
+
/** How many locales the run covers in total. */
|
|
1779
2057
|
readonly totalLocales: number;
|
|
1780
2058
|
}
|
|
1781
|
-
/**
|
|
2059
|
+
/** Emitted once, after every locale has finished. */
|
|
1782
2060
|
interface RunFinishedEvent {
|
|
2061
|
+
/** Discriminant for {@link ProgressEvent}. */
|
|
1783
2062
|
readonly type: "run-finished";
|
|
1784
|
-
/**
|
|
1785
|
-
* Number of locales the run processed (including failed locales: a per-locale failure is isolated
|
|
1786
|
-
* as a summary rather than thrown, so it is still counted here).
|
|
1787
|
-
*/
|
|
2063
|
+
/** How many locales completed, including those that failed. */
|
|
1788
2064
|
readonly localesCompleted: number;
|
|
1789
2065
|
}
|
|
1790
2066
|
/**
|
|
1791
|
-
* Called
|
|
1792
|
-
*
|
|
1793
|
-
* a run with no listener attached.
|
|
2067
|
+
* Called for each {@link ProgressEvent} a run emits. Passed as `onProgress` to {@link translate}
|
|
2068
|
+
* and {@link watch}.
|
|
1794
2069
|
*/
|
|
1795
2070
|
type ProgressListener = (event: ProgressEvent) => void;
|
|
1796
2071
|
|
|
1797
|
-
/**
|
|
2072
|
+
/** Input for {@link translate}. Only `config` is required; every other field has a default. */
|
|
1798
2073
|
interface TranslateInput {
|
|
1799
|
-
/** The
|
|
2074
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
1800
2075
|
readonly config: VerbatraConfig;
|
|
1801
|
-
/** Directory the
|
|
2076
|
+
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
1802
2077
|
readonly cwd?: string;
|
|
1803
|
-
/**
|
|
2078
|
+
/**
|
|
2079
|
+
* Compute the whole run but write nothing and call no provider. The returned
|
|
2080
|
+
* {@link RunSummary} reports what would have happened, which makes this safe to run in CI to
|
|
2081
|
+
* preview work without spending anything. Defaults to false.
|
|
2082
|
+
*/
|
|
1804
2083
|
readonly dryRun?: boolean;
|
|
1805
2084
|
/**
|
|
1806
|
-
*
|
|
1807
|
-
*
|
|
1808
|
-
* the config's `prune` applies. Only `diff.orphaned` keys are ever removed; no other key is touched.
|
|
2085
|
+
* Remove keys that no longer exist in the source. Defaults to the config's `prune`, then to
|
|
2086
|
+
* false, so orphaned keys are reported but kept unless removal is asked for explicitly.
|
|
1809
2087
|
*/
|
|
1810
2088
|
readonly prune?: boolean;
|
|
1811
2089
|
/**
|
|
1812
|
-
*
|
|
1813
|
-
*
|
|
1814
|
-
* default. When set, takes precedence over the config's `generatePlurals` option for this run; when
|
|
1815
|
-
* unset, the config's `generatePlurals` applies.
|
|
2090
|
+
* Generate the plural categories a target language requires rather than translating each
|
|
2091
|
+
* category separately. Defaults to the config's `generatePlurals`, then to false.
|
|
1816
2092
|
*/
|
|
1817
2093
|
readonly generatePlurals?: boolean;
|
|
1818
2094
|
/**
|
|
1819
|
-
* Called while
|
|
1820
|
-
*
|
|
1821
|
-
* with the growing elapsed time. Never called for an uncontended run. The SDK writes no output; this
|
|
1822
|
-
* is the only wait-progress signal (the CLI renders its "still waiting" line from it).
|
|
2095
|
+
* Called while waiting on another process's write lock, so a CLI can explain a stall instead of
|
|
2096
|
+
* appearing to hang.
|
|
1823
2097
|
*/
|
|
1824
2098
|
readonly onLockWait?: LockWaitListener;
|
|
1825
|
-
/**
|
|
1826
|
-
* Called as the run advances: once per locale before it starts and once after it finishes, once
|
|
1827
|
-
* per provider sub-batch within a locale, and once when the whole locale loop ends. Never fires a
|
|
1828
|
-
* sub-batch event on a dry-run (no provider call is made). The SDK writes no output; this is the
|
|
1829
|
-
* only progress signal (the CLI renders it to stderr, keeping stdout byte-identical).
|
|
1830
|
-
*
|
|
1831
|
-
* Pairing is not guaranteed when the run throws: a whole-run failure (for example a corrupt
|
|
1832
|
-
* lock-file surfacing as `LOCK_FILE_INVALID`, which re-throws instead of being isolated per locale)
|
|
1833
|
-
* can emit a `locale-started` event with no matching `locale-finished`, and no `run-finished` at
|
|
1834
|
-
* all. A per-locale failure that is isolated (not re-thrown) still emits both, and is counted in
|
|
1835
|
-
* `run-finished`.
|
|
1836
|
-
*/
|
|
2099
|
+
/** Called as locales and sub-batches start and finish, for progress reporting. */
|
|
1837
2100
|
readonly onProgress?: ProgressListener;
|
|
1838
|
-
/**
|
|
1839
|
-
* Override how long a locale's write lock keeps retrying before failing with `LOCK_CONTENDED`, in
|
|
1840
|
-
* milliseconds. Defaults to the lock's own 10-minute default when unset (surfaced to the CLI as
|
|
1841
|
-
* `--lock-timeout`).
|
|
1842
|
-
*/
|
|
2101
|
+
/** How long to wait for a locale's write lock before failing with `LOCK_CONTENDED`. */
|
|
1843
2102
|
readonly lockAcquireTimeoutMs?: number;
|
|
1844
2103
|
/**
|
|
1845
|
-
* How many
|
|
1846
|
-
*
|
|
1847
|
-
*
|
|
1848
|
-
* `CONCURRENCY_INVALID` error. On a live run, a value greater than 1 is rejected with a whole-run
|
|
1849
|
-
* `CONCURRENCY_BUDGET_CONFLICT` error when the config sets a `maxTokens` budget, because
|
|
1850
|
-
* concurrency would make the budget's stop guarantee nondeterministic; a dry run is exempt. The
|
|
1851
|
-
* per-locale write locks already isolate concurrent locales on disk, so no extra locking is added.
|
|
2104
|
+
* How many locales to run at once. Must be an integer of at least 1; defaults to 1. On a live
|
|
2105
|
+
* run it cannot be combined with a configured token budget, because concurrent locales would
|
|
2106
|
+
* overshoot the budget nondeterministically.
|
|
1852
2107
|
*/
|
|
1853
2108
|
readonly concurrency?: number;
|
|
1854
2109
|
/**
|
|
1855
|
-
*
|
|
1856
|
-
*
|
|
1857
|
-
* cache write are skipped: the run makes exactly the provider calls it would with no cache present,
|
|
1858
|
-
* and leaves any existing cache file untouched. Ignored on a dry-run, which never reads or writes the
|
|
1859
|
-
* cache regardless. See the cache module for the fingerprint and degrade-to-empty contract.
|
|
2110
|
+
* Consult and update the translation memory. Defaults to true. Turning it off forces every key
|
|
2111
|
+
* through the provider, which is what to do when you want to re-pay for a fresh translation.
|
|
1860
2112
|
*/
|
|
1861
2113
|
readonly cache?: boolean;
|
|
1862
2114
|
}
|
|
1863
|
-
/**
|
|
2115
|
+
/** Injectable dependencies for {@link translate}. Every field has a working default. */
|
|
1864
2116
|
interface TranslateDeps {
|
|
1865
|
-
/**
|
|
2117
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
1866
2118
|
readonly adapterRegistry?: AdapterRegistry;
|
|
1867
|
-
/** Provider
|
|
2119
|
+
/** Provider factory. Defaults to constructing the provider named in the config. */
|
|
1868
2120
|
readonly createProvider?: CreateProvider;
|
|
1869
|
-
/** File
|
|
2121
|
+
/** File-system port. Defaults to the real file system. */
|
|
1870
2122
|
readonly fs?: SdkFs;
|
|
1871
2123
|
}
|
|
1872
2124
|
/**
|
|
1873
|
-
*
|
|
1874
|
-
*
|
|
1875
|
-
*
|
|
1876
|
-
*
|
|
1877
|
-
*
|
|
1878
|
-
*
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
1881
|
-
*
|
|
1882
|
-
*
|
|
1883
|
-
*
|
|
1884
|
-
*
|
|
1885
|
-
*
|
|
1886
|
-
* A
|
|
1887
|
-
*
|
|
1888
|
-
*
|
|
1889
|
-
*
|
|
1890
|
-
*
|
|
1891
|
-
*
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
*
|
|
1895
|
-
* the
|
|
1896
|
-
*
|
|
1897
|
-
* @param input - The
|
|
1898
|
-
* @param deps - Optional
|
|
1899
|
-
* @returns
|
|
2125
|
+
* Runs the one-shot translation flow over every configured target locale: read the source, diff
|
|
2126
|
+
* each locale against the lock-file baseline, translate what is missing or stale, verify placeholder
|
|
2127
|
+
* and ICU integrity, write the locale files, and update the lock-file and translation memory.
|
|
2128
|
+
*
|
|
2129
|
+
* Failure handling is the contract worth understanding. Whole-run problems, such as an unreadable
|
|
2130
|
+
* source file or a provider that cannot be constructed, throw an {@link SdkError} before any locale
|
|
2131
|
+
* runs. Once locales are running, a per-locale failure is recorded on that locale's
|
|
2132
|
+
* {@link LocaleSummary} and the other locales continue, so one unreachable provider or one
|
|
2133
|
+
* unwritable file never discards work that succeeded. A locale whose write lock stays contended is
|
|
2134
|
+
* one of these per-locale failures: it is recorded with code `LOCK_CONTENDED` on that locale's
|
|
2135
|
+
* summary rather than thrown. Callers should therefore inspect {@link RunSummary.failed} rather
|
|
2136
|
+
* than relying on a thrown error to detect trouble.
|
|
2137
|
+
*
|
|
2138
|
+
* A corrupt lock-file is the one exception. On a live run each locale reads the lock-file inside
|
|
2139
|
+
* its own write lock, so `LOCK_FILE_INVALID` escapes as a thrown error even after earlier locales
|
|
2140
|
+
* have already translated and written. No further locale is started, and the locales in flight
|
|
2141
|
+
* finish and release their write locks before the call rejects.
|
|
2142
|
+
*
|
|
2143
|
+
* Each locale takes its own write lock for the read-modify-write, so concurrent runs and
|
|
2144
|
+
* single-key edits cannot interleave on one file. Translations that fail the integrity gate are
|
|
2145
|
+
* refused rather than written, leaving the previous value intact.
|
|
2146
|
+
*
|
|
2147
|
+
* Set `dryRun` to compute the whole plan without writing or spending anything.
|
|
2148
|
+
*
|
|
2149
|
+
* @param input - The config and the per-run options.
|
|
2150
|
+
* @param deps - Optional adapter registry, provider factory, and file-system overrides.
|
|
2151
|
+
* @returns The per-locale account of the run, including usage and budget.
|
|
2152
|
+
*
|
|
1900
2153
|
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
1901
|
-
* @throws {@link SdkError} `
|
|
1902
|
-
*
|
|
1903
|
-
*
|
|
2154
|
+
* @throws {@link SdkError} `CONCURRENCY_INVALID`: `concurrency` is not an integer of at least 1.
|
|
2155
|
+
* @throws {@link SdkError} `CONCURRENCY_BUDGET_CONFLICT`: a live run combined a `concurrency` above
|
|
2156
|
+
* 1 with a configured token budget. A dry run is exempt.
|
|
2157
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
2158
|
+
* cannot be combined, or a configured locale has no valid path spelling under that style.
|
|
2159
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
1904
2160
|
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
1905
|
-
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
1908
|
-
*
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
1912
|
-
*
|
|
2161
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
2162
|
+
* @throws {@link SdkError} `PROVIDER_CONSTRUCTION_FAILED`: the provider could not be constructed,
|
|
2163
|
+
* most often because its API key environment variable is unset. Not thrown on a dry run, which
|
|
2164
|
+
* never constructs a provider.
|
|
2165
|
+
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
2166
|
+
* unsupported version. A dry run reads it once before any locale runs; a live run reads it per
|
|
2167
|
+
* locale, so this can abort the run after other locales have already been written.
|
|
2168
|
+
*
|
|
1913
2169
|
* @example
|
|
1914
2170
|
* ```ts
|
|
1915
2171
|
* import { loadConfig, translate } from "@verbatra/sdk";
|
|
1916
2172
|
*
|
|
1917
|
-
* // The provider reads its API key from the environment (e.g. ANTHROPIC_API_KEY); no key is passed here.
|
|
1918
2173
|
* const config = await loadConfig();
|
|
1919
|
-
* const summary = await translate({
|
|
2174
|
+
* const summary = await translate({
|
|
2175
|
+
* config,
|
|
2176
|
+
* onProgress: (event) => {
|
|
2177
|
+
* if (event.type === "locale-finished") {
|
|
2178
|
+
* console.log(`${event.locale}: ${event.translated} keys`);
|
|
2179
|
+
* }
|
|
2180
|
+
* },
|
|
2181
|
+
* });
|
|
1920
2182
|
*
|
|
1921
|
-
*
|
|
1922
|
-
*
|
|
1923
|
-
*
|
|
1924
|
-
* console.error(`${locale.locale}: ${locale.error?.code} ${locale.error?.message}`);
|
|
1925
|
-
* } else {
|
|
1926
|
-
* console.log(`${locale.locale}: ${locale.translated.length} translated, ${locale.notices.length} notices`);
|
|
2183
|
+
* if (summary.failed.length > 0) {
|
|
2184
|
+
* for (const locale of summary.locales.filter((entry) => entry.status === "failed")) {
|
|
2185
|
+
* console.error(`${locale.locale}: ${locale.error?.message}`);
|
|
1927
2186
|
* }
|
|
2187
|
+
* process.exitCode = 1;
|
|
1928
2188
|
* }
|
|
1929
|
-
*
|
|
1930
|
-
* // Preview only: no provider call, no writes.
|
|
1931
|
-
* const preview = await translate({ config, dryRun: true });
|
|
1932
2189
|
* ```
|
|
1933
2190
|
*/
|
|
1934
2191
|
declare function translate(input: TranslateInput, deps?: TranslateDeps): Promise<RunSummary>;
|
|
1935
2192
|
|
|
1936
2193
|
/**
|
|
1937
2194
|
* The two delimiter-separated interchange formats. They are one implementation with one parameter, the
|
|
1938
|
-
* field delimiter: `tsv` is not a second format, it is `csv` with a tab.
|
|
2195
|
+
* field delimiter: `tsv` is not a second format, it is `csv` with a tab. The member is also the file
|
|
2196
|
+
* extension an export writes and an import reads back.
|
|
1939
2197
|
*/
|
|
1940
2198
|
type DelimitedFormat = "csv" | "tsv";
|
|
1941
2199
|
|
|
1942
2200
|
/**
|
|
1943
|
-
* The
|
|
1944
|
-
*
|
|
1945
|
-
*
|
|
2201
|
+
* The file shape of a translator handoff: a single styled `.xlsx` workbook with one sheet per
|
|
2202
|
+
* locale, or one plain `.csv` or `.tsv` file per locale.
|
|
2203
|
+
*
|
|
2204
|
+
* The workbook is the friendlier artifact to hand a human translator, while the delimited forms are
|
|
2205
|
+
* easier to diff, review, and feed to another tool. {@link exportWorkbook} and
|
|
2206
|
+
* {@link importWorkbook} both accept either.
|
|
1946
2207
|
*/
|
|
1947
2208
|
type ExchangeFormat = "xlsx" | DelimitedFormat;
|
|
2209
|
+
/**
|
|
2210
|
+
* Every {@link ExchangeFormat} at runtime, for a tool that has to validate a `--format` argument or
|
|
2211
|
+
* offer the choices to a user. The order is the order to present them in: the workbook first, then
|
|
2212
|
+
* the delimited forms.
|
|
2213
|
+
*
|
|
2214
|
+
* It is derived from a record keyed by {@link ExchangeFormat} itself, so a format added to the type
|
|
2215
|
+
* without being added here fails to compile. A consumer that checks membership against this list can
|
|
2216
|
+
* therefore never silently reject a format the SDK accepts.
|
|
2217
|
+
*/
|
|
2218
|
+
declare const EXCHANGE_FORMATS: readonly ExchangeFormat[];
|
|
2219
|
+
/** The {@link ExchangeFormat} {@link exportWorkbook} and {@link importWorkbook} use when the caller names none. */
|
|
2220
|
+
declare const DEFAULT_EXCHANGE_FORMAT: ExchangeFormat;
|
|
1948
2221
|
|
|
1949
|
-
/** Default
|
|
2222
|
+
/** Default output path for an `.xlsx` handoff, used when {@link ExportWorkbookInput.out} is omitted. */
|
|
1950
2223
|
declare const DEFAULT_WORKBOOK_PATH = "verbatra-translations.xlsx";
|
|
1951
2224
|
/**
|
|
1952
|
-
* Default
|
|
1953
|
-
*
|
|
2225
|
+
* Default output directory for a delimited handoff. It carries no extension because it names a
|
|
2226
|
+
* directory, not a file: the export creates it and writes one `<locale>.<format>` file inside it
|
|
2227
|
+
* per exported locale, such as `de.csv`.
|
|
1954
2228
|
*/
|
|
1955
2229
|
declare const DEFAULT_DELIMITED_PATH = "verbatra-translations";
|
|
1956
|
-
/** Input for {@link exportWorkbook}
|
|
2230
|
+
/** Input for {@link exportWorkbook}. */
|
|
1957
2231
|
interface ExportWorkbookInput {
|
|
1958
|
-
/** The
|
|
2232
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
1959
2233
|
readonly config: VerbatraConfig;
|
|
1960
|
-
/** Directory the
|
|
2234
|
+
/** Directory the `files.pattern` and `out` are resolved against. Defaults to the process working directory. */
|
|
1961
2235
|
readonly cwd?: string;
|
|
1962
2236
|
/**
|
|
1963
|
-
*
|
|
1964
|
-
* {@link DEFAULT_DELIMITED_PATH} for
|
|
1965
|
-
* directory the per-locale files are written into for `csv` and `tsv` (created if missing).
|
|
2237
|
+
* Where to write the handoff. Defaults to {@link DEFAULT_WORKBOOK_PATH} for `xlsx` and to
|
|
2238
|
+
* {@link DEFAULT_DELIMITED_PATH} for the delimited formats.
|
|
1966
2239
|
*/
|
|
1967
2240
|
readonly out?: string;
|
|
1968
|
-
/**
|
|
2241
|
+
/** Restrict the export to these target locales. Defaults to every configured target locale. */
|
|
1969
2242
|
readonly locales?: readonly string[];
|
|
1970
|
-
/**
|
|
2243
|
+
/**
|
|
2244
|
+
* Include keys that are already up to date, not just the missing and stale ones. Useful when a
|
|
2245
|
+
* translator needs the surrounding context to translate consistently. Defaults to false.
|
|
2246
|
+
*/
|
|
1971
2247
|
readonly includeUnchanged?: boolean;
|
|
1972
|
-
/**
|
|
2248
|
+
/** The handoff shape to write. Defaults to `xlsx`. */
|
|
1973
2249
|
readonly format?: ExchangeFormat;
|
|
1974
2250
|
}
|
|
1975
|
-
/**
|
|
2251
|
+
/** Injectable dependencies for {@link exportWorkbook}. Every field has a working default. */
|
|
1976
2252
|
interface ExportWorkbookDeps {
|
|
2253
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
1977
2254
|
readonly adapterRegistry?: AdapterRegistry;
|
|
2255
|
+
/** File-system port. Defaults to the real file system. */
|
|
1978
2256
|
readonly fs?: SdkFs;
|
|
1979
2257
|
}
|
|
1980
|
-
/**
|
|
2258
|
+
/** What {@link exportWorkbook} wrote. */
|
|
1981
2259
|
interface ExportWorkbookResult {
|
|
1982
|
-
/**
|
|
1983
|
-
* The absolute path written to: the workbook file for `xlsx`, the directory holding one
|
|
1984
|
-
* `<locale>.csv` or `<locale>.tsv` per exported locale for the delimited formats.
|
|
1985
|
-
*/
|
|
2260
|
+
/** The absolute path written, or the shared base path when one file per locale was written. */
|
|
1986
2261
|
readonly path: string;
|
|
1987
|
-
/**
|
|
2262
|
+
/** Row counts per exported locale. */
|
|
1988
2263
|
readonly locales: readonly {
|
|
2264
|
+
/** The exported target locale. */
|
|
1989
2265
|
readonly locale: string;
|
|
2266
|
+
/** How many translatable rows that locale contributed. */
|
|
1990
2267
|
readonly rows: number;
|
|
1991
2268
|
}[];
|
|
1992
2269
|
}
|
|
1993
2270
|
/**
|
|
1994
|
-
*
|
|
1995
|
-
* `.
|
|
1996
|
-
*
|
|
1997
|
-
*
|
|
2271
|
+
* Writes the strings awaiting translation to a handoff a human translator can work in: a styled
|
|
2272
|
+
* `.xlsx` workbook with one sheet per locale, or one `.csv` or `.tsv` file per locale.
|
|
2273
|
+
*
|
|
2274
|
+
* By default only missing and stale keys are exported, which is what makes the handoff a work list
|
|
2275
|
+
* rather than a dump of the whole project. Each row carries the source text alongside any existing
|
|
2276
|
+
* translation and a review status, so the translator sees what changed and why a string was
|
|
2277
|
+
* flagged.
|
|
1998
2278
|
*
|
|
1999
|
-
*
|
|
2000
|
-
*
|
|
2001
|
-
*
|
|
2002
|
-
* on import; it is compared against the live source and the row is withheld as drift.
|
|
2279
|
+
* This is the outbound half of the exchange; {@link importWorkbook} reads the filled handoff back
|
|
2280
|
+
* through the same diff, lock, and integrity checks. It writes only the handoff file and never
|
|
2281
|
+
* touches the locale files or the lock-file.
|
|
2003
2282
|
*
|
|
2004
|
-
*
|
|
2005
|
-
*
|
|
2006
|
-
*
|
|
2007
|
-
* them. The export deletes nothing, so an unrelated file in the output directory is never at risk.
|
|
2283
|
+
* Note that a malformed target locale file surfaces the adapter's own parse error rather than a
|
|
2284
|
+
* wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
|
|
2285
|
+
* should be ready for an unrecognized error from a target file.
|
|
2008
2286
|
*
|
|
2009
|
-
* @param input - The
|
|
2010
|
-
* @param deps - Optional
|
|
2011
|
-
* @returns
|
|
2012
|
-
*
|
|
2013
|
-
*
|
|
2014
|
-
*
|
|
2287
|
+
* @param input - The config, output path, locale filter, and handoff format.
|
|
2288
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
2289
|
+
* @returns The path written and the per-locale row counts.
|
|
2290
|
+
*
|
|
2291
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
2292
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
2293
|
+
* cannot be combined, or a configured locale has no valid path spelling under that style.
|
|
2294
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
2295
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
2296
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
2297
|
+
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
2298
|
+
* unsupported version.
|
|
2299
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE`: a requested locale is not a configured target locale.
|
|
2015
2300
|
*/
|
|
2016
2301
|
declare function exportWorkbook(input: ExportWorkbookInput, deps?: ExportWorkbookDeps): Promise<ExportWorkbookResult>;
|
|
2017
2302
|
|
|
2018
|
-
/** Input for {@link importWorkbook}
|
|
2303
|
+
/** Input for {@link importWorkbook}. */
|
|
2019
2304
|
interface ImportWorkbookInput {
|
|
2020
|
-
/** The
|
|
2305
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
2021
2306
|
readonly config: VerbatraConfig;
|
|
2022
2307
|
/**
|
|
2023
|
-
* Path to the filled handoff
|
|
2024
|
-
*
|
|
2308
|
+
* Path to the filled handoff. For a delimited import the path is tried as a single file first,
|
|
2309
|
+
* so one individual `<locale>.<format>` file can be imported on its own, with the locale taken
|
|
2310
|
+
* from its file name. If no file exists there, the path is treated as the directory the
|
|
2311
|
+
* per-locale files were written into and every configured target locale found inside is read.
|
|
2025
2312
|
*/
|
|
2026
2313
|
readonly workbook: string;
|
|
2027
|
-
/** Directory the
|
|
2314
|
+
/** Directory the `files.pattern` and `workbook` are resolved against. Defaults to the process working directory. */
|
|
2028
2315
|
readonly cwd?: string;
|
|
2029
|
-
/**
|
|
2316
|
+
/**
|
|
2317
|
+
* Read and validate the handoff but write nothing. The returned {@link RunSummary} reports what
|
|
2318
|
+
* would have been applied, which is the safe way to inspect a handoff before trusting it.
|
|
2319
|
+
* Defaults to false.
|
|
2320
|
+
*/
|
|
2030
2321
|
readonly dryRun?: boolean;
|
|
2031
|
-
/**
|
|
2322
|
+
/** The handoff shape to read. Defaults to `xlsx`. */
|
|
2032
2323
|
readonly format?: ExchangeFormat;
|
|
2033
2324
|
}
|
|
2034
|
-
/**
|
|
2325
|
+
/** Injectable dependencies for {@link importWorkbook}. Every field has a working default. */
|
|
2035
2326
|
interface ImportWorkbookDeps {
|
|
2327
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
2036
2328
|
readonly adapterRegistry?: AdapterRegistry;
|
|
2329
|
+
/** File-system port. Defaults to the real file system. */
|
|
2037
2330
|
readonly fs?: SdkFs;
|
|
2038
2331
|
}
|
|
2039
2332
|
/**
|
|
2040
|
-
*
|
|
2041
|
-
*
|
|
2042
|
-
*
|
|
2043
|
-
*
|
|
2333
|
+
* Reads a filled translator handoff back into the locale files. It is the inbound half of the
|
|
2334
|
+
* exchange that {@link exportWorkbook} starts, and it calls no provider: every value comes from the
|
|
2335
|
+
* handoff.
|
|
2336
|
+
*
|
|
2337
|
+
* Imported values are held to the same integrity gate as provider output, so a translator who drops
|
|
2338
|
+
* a placeholder or breaks ICU syntax has that row refused rather than written. Each locale takes
|
|
2339
|
+
* its write lock, and the lock-file and translation memory are updated exactly as in a
|
|
2340
|
+
* {@link translate} run, so an imported translation counts as up to date afterwards.
|
|
2044
2341
|
*
|
|
2045
|
-
*
|
|
2046
|
-
*
|
|
2047
|
-
*
|
|
2048
|
-
*
|
|
2049
|
-
*
|
|
2050
|
-
*
|
|
2051
|
-
*
|
|
2052
|
-
*
|
|
2053
|
-
* locale's `status: "failed"` (`HANDOFF_FILE_STALE`). Dry-run validates and reports without writing any
|
|
2054
|
-
* locale or lock file, and skips lock acquisition (there is nothing to protect).
|
|
2342
|
+
* Damage is contained rather than fatal: a blank row keeps the existing translation and its
|
|
2343
|
+
* baseline, an unreadable row is reported as a {@link MalformedRowReport}, and a repeated key is
|
|
2344
|
+
* reported as a {@link DuplicateKeyReport} with the first occurrence winning. All three surface on
|
|
2345
|
+
* the returned {@link RunSummary} rather than aborting the import. A sheet or file naming a locale
|
|
2346
|
+
* that is not configured is contained the same way: that locale fails with `CONFIG_INVALID` on its
|
|
2347
|
+
* own {@link LocaleSummary}, so nothing is written to an unmanaged path and the configured locales
|
|
2348
|
+
* still import. Once the handoff has been read, every per-locale failure is isolated this way, so
|
|
2349
|
+
* callers should inspect {@link RunSummary.failed} rather than relying on a thrown error.
|
|
2055
2350
|
*
|
|
2056
|
-
*
|
|
2057
|
-
*
|
|
2058
|
-
*
|
|
2351
|
+
* @param input - The config and the handoff path, format, and dry-run flag.
|
|
2352
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
2353
|
+
* @returns The per-locale account of what was applied.
|
|
2059
2354
|
*
|
|
2060
|
-
* @
|
|
2061
|
-
* @
|
|
2062
|
-
*
|
|
2063
|
-
* @throws {@link SdkError} `
|
|
2355
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
2356
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the handoff file was not found, or the source
|
|
2357
|
+
* locale file does not exist.
|
|
2358
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the handoff is oversized or could not be parsed, or
|
|
2359
|
+
* the source locale file could not be parsed.
|
|
2360
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
2361
|
+
* cannot be combined, or a configured locale has no valid path spelling under that style.
|
|
2362
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
2363
|
+
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
2364
|
+
* unsupported version. Read once before any locale is applied.
|
|
2064
2365
|
*/
|
|
2065
2366
|
declare function importWorkbook(input: ImportWorkbookInput, deps?: ImportWorkbookDeps): Promise<RunSummary>;
|
|
2066
2367
|
|
|
2067
|
-
/** The locale
|
|
2368
|
+
/** The locale spellings {@link LocaleStyle} is drawn from, in declaration order. */
|
|
2068
2369
|
declare const LOCALE_STYLES: readonly ["literal", "posix", "android"];
|
|
2069
2370
|
/**
|
|
2070
|
-
* How a
|
|
2371
|
+
* How a locale is spelled inside a file path, set through the config's `files.localeStyle`.
|
|
2071
2372
|
*
|
|
2072
|
-
* - `literal
|
|
2073
|
-
*
|
|
2074
|
-
*
|
|
2075
|
-
*
|
|
2076
|
-
*
|
|
2077
|
-
*
|
|
2078
|
-
*
|
|
2373
|
+
* - `literal`: the locale is written into the path exactly as configured, so `pt-BR` yields
|
|
2374
|
+
* `pt-BR`. This is the default and suits the JSON and YAML layouts most web projects use.
|
|
2375
|
+
* - `posix`: the POSIX spelling, so `pt-BR` yields `pt_BR`. Common for gettext-influenced and
|
|
2376
|
+
* Java-influenced layouts.
|
|
2377
|
+
* - `android`: the Android resource-qualifier spelling, so `pt-BR` yields `values-pt-rBR` and the
|
|
2378
|
+
* source locale yields the unqualified `values`. This style expands to a whole path segment, so
|
|
2379
|
+
* the `{locale}` token must stand alone between separators in the pattern.
|
|
2079
2380
|
*
|
|
2080
|
-
* A
|
|
2081
|
-
*
|
|
2082
|
-
*
|
|
2381
|
+
* A locale that has no valid spelling under the declared style, or that would expand to something
|
|
2382
|
+
* other than a single path segment, is rejected with `LOCALE_LAYOUT_INVALID` before any file is
|
|
2383
|
+
* read.
|
|
2083
2384
|
*/
|
|
2084
2385
|
type LocaleStyle = (typeof LOCALE_STYLES)[number];
|
|
2085
2386
|
|
|
2086
2387
|
/**
|
|
2087
|
-
* The
|
|
2088
|
-
*
|
|
2388
|
+
* The slice of a {@link VerbatraConfig} that determines locale-to-path mapping. It is narrower than
|
|
2389
|
+
* the full config on purpose, so a caller holding only these fields can build a resolver.
|
|
2089
2390
|
*/
|
|
2090
2391
|
interface LocalePathResolverConfig {
|
|
2392
|
+
/** The source locale, which some styles spell differently from the targets. */
|
|
2091
2393
|
readonly sourceLocale: string;
|
|
2394
|
+
/** Every target locale. All of them are mapped up front so collisions are caught immediately. */
|
|
2092
2395
|
readonly targetLocales: readonly string[];
|
|
2396
|
+
/** The file layout. */
|
|
2093
2397
|
readonly files: {
|
|
2398
|
+
/** The path pattern, which must contain the `{locale}` token. */
|
|
2094
2399
|
readonly pattern: string;
|
|
2400
|
+
/** How a locale is spelled inside the path. Defaults to `literal`. */
|
|
2095
2401
|
readonly localeStyle?: LocaleStyle | undefined;
|
|
2096
2402
|
};
|
|
2097
2403
|
}
|
|
2098
|
-
/**
|
|
2099
|
-
* The project's locale-to-path mapping, resolved once from the config and threaded through every
|
|
2100
|
-
* flow that needs a path. Holding one object rather than a `(pattern, style, sourceLocale)` triple
|
|
2101
|
-
* is what keeps a source-aware style from producing a wrong path in a flow that only threaded part
|
|
2102
|
-
* of it.
|
|
2103
|
-
*/
|
|
2404
|
+
/** The two-way mapping between locales and their absolute file paths. */
|
|
2104
2405
|
interface LocalePathResolver {
|
|
2105
2406
|
/**
|
|
2106
|
-
* The absolute
|
|
2107
|
-
*
|
|
2108
|
-
*
|
|
2109
|
-
* @throws SdkError `LOCALE_LAYOUT_INVALID` when the style cannot spell an unconfigured locale
|
|
2110
|
-
* passed in after construction.
|
|
2407
|
+
* The absolute path of a locale's file. Accepts any locale string, including ones outside the
|
|
2408
|
+
* configured set, so it can be used for exploratory reads.
|
|
2111
2409
|
*/
|
|
2112
2410
|
pathFor(locale: string): string;
|
|
2113
2411
|
/**
|
|
2114
|
-
*
|
|
2115
|
-
*
|
|
2116
|
-
*
|
|
2117
|
-
* created with; the match is exact, so a path differing only in case does not resolve even on a
|
|
2118
|
-
* case-insensitive file system.
|
|
2119
|
-
*
|
|
2120
|
-
* `undefined` is not an error. It means the path is not one of this project's locale files, which
|
|
2121
|
-
* is what a watcher needs to know in order to ignore it.
|
|
2412
|
+
* The configured locale owning an absolute path, or `undefined` when the path belongs to no
|
|
2413
|
+
* configured locale. The path is normalized before lookup, so a relative or non-canonical path
|
|
2414
|
+
* still resolves.
|
|
2122
2415
|
*/
|
|
2123
2416
|
localeFor(absolutePath: string): string | undefined;
|
|
2124
2417
|
}
|
|
2125
2418
|
/**
|
|
2126
|
-
*
|
|
2127
|
-
*
|
|
2128
|
-
*
|
|
2129
|
-
* and
|
|
2419
|
+
* Builds the project's locale-to-path mapping from the configured pattern, locales, and
|
|
2420
|
+
* {@link LocaleStyle}, in both directions.
|
|
2421
|
+
*
|
|
2422
|
+
* Every SDK flow resolves paths through this, and a consumer that watches or reports on locale
|
|
2423
|
+
* files should use it too rather than re-deriving the mapping: a watcher that guesses at paths will
|
|
2424
|
+
* disagree with the SDK the moment a non-literal locale style is configured.
|
|
2130
2425
|
*
|
|
2131
|
-
*
|
|
2132
|
-
*
|
|
2133
|
-
*
|
|
2426
|
+
* The whole configured locale set is mapped eagerly during construction, so an unusable layout or a
|
|
2427
|
+
* path collision is reported here, before any file is read and before any provider is called,
|
|
2428
|
+
* rather than partway through a run.
|
|
2134
2429
|
*
|
|
2135
|
-
* @param cwd - Directory the
|
|
2136
|
-
* @param config - The
|
|
2137
|
-
* @
|
|
2138
|
-
*
|
|
2139
|
-
* @throws SdkError `
|
|
2430
|
+
* @param cwd - Directory the pattern is resolved against.
|
|
2431
|
+
* @param config - The source locale, target locales, and file layout.
|
|
2432
|
+
* @returns A resolver mapping locales to paths and paths back to locales.
|
|
2433
|
+
*
|
|
2434
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the pattern lacks the `{locale}` token, a
|
|
2435
|
+
* segment style's token does not stand alone between separators, or a configured locale has no
|
|
2436
|
+
* valid single-segment spelling under the declared style.
|
|
2437
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same
|
|
2438
|
+
* absolute path.
|
|
2140
2439
|
*/
|
|
2141
2440
|
declare function createLocalePathResolver(cwd: string, config: LocalePathResolverConfig): LocalePathResolver;
|
|
2142
2441
|
|
|
2143
2442
|
/**
|
|
2144
|
-
* The
|
|
2145
|
-
*
|
|
2443
|
+
* The contents of `verbatra.lock.json` (see {@link LOCK_FILE_NAME}), the baseline that makes
|
|
2444
|
+
* staleness detectable. For each locale it records the hash of the source text each key was
|
|
2445
|
+
* translated from, so a later run can tell a translation that is merely present from one that is
|
|
2446
|
+
* still current.
|
|
2447
|
+
*
|
|
2448
|
+
* Commit it: without it, every run has to treat an existing translation as up to date and a source
|
|
2449
|
+
* edit would silently never be re-translated.
|
|
2146
2450
|
*/
|
|
2147
2451
|
interface LockFile {
|
|
2452
|
+
/** The lock-file schema version. An unrecognized version is rejected rather than guessed at. */
|
|
2148
2453
|
readonly version: number;
|
|
2454
|
+
/** Per locale, the source-content hash recorded for each translated key. */
|
|
2149
2455
|
readonly locales: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
2150
2456
|
}
|
|
2151
2457
|
|
|
2152
|
-
/** Input for {@link loadLockFile}
|
|
2458
|
+
/** Input for {@link loadLockFile}. */
|
|
2153
2459
|
interface LoadLockFileInput {
|
|
2154
|
-
/** Directory the lock-file
|
|
2460
|
+
/** Directory holding the lock-file. Defaults to the process working directory. */
|
|
2155
2461
|
readonly cwd?: string;
|
|
2156
2462
|
}
|
|
2157
|
-
/**
|
|
2463
|
+
/** Injectable dependencies for {@link loadLockFile}. Every field has a working default. */
|
|
2158
2464
|
interface LoadLockFileDeps {
|
|
2465
|
+
/** File-system port. Defaults to the real file system. */
|
|
2159
2466
|
readonly fs?: SdkFs;
|
|
2160
2467
|
}
|
|
2161
2468
|
/**
|
|
2162
|
-
*
|
|
2163
|
-
*
|
|
2164
|
-
* A missing lock-file degrades to an empty lock (first-run), the same behavior `translate` and
|
|
2165
|
-
* `watch` rely on internally. Callers that need to distinguish "no lock-file yet" from "an empty
|
|
2166
|
-
* but present lock-file" should use {@link lockState} instead, which probes the file's existence
|
|
2167
|
-
* explicitly.
|
|
2469
|
+
* Reads the project's lock-file. Use it when you need the recorded baseline hashes themselves;
|
|
2470
|
+
* {@link lockState} is the better choice when you want the drift those hashes imply.
|
|
2168
2471
|
*
|
|
2169
|
-
*
|
|
2170
|
-
*
|
|
2171
|
-
*
|
|
2172
|
-
*
|
|
2173
|
-
*
|
|
2472
|
+
* A project with no lock-file yet reads as an empty lock-file at the current version rather than an
|
|
2473
|
+
* error, so a first run and a lost file behave the same way. A lock-file that exists but cannot be
|
|
2474
|
+
* trusted is a different matter and throws, because silently treating a corrupt baseline as empty
|
|
2475
|
+
* would make every key look stale and trigger a full re-translation.
|
|
2476
|
+
*
|
|
2477
|
+
* @param input - The optional working directory.
|
|
2478
|
+
* @param deps - Optional file-system override.
|
|
2479
|
+
* @returns The lock-file's contents, or an empty lock-file when none exists.
|
|
2480
|
+
*
|
|
2481
|
+
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file exists but is oversized, not valid
|
|
2482
|
+
* JSON, structurally wrong, or at an unsupported version.
|
|
2174
2483
|
*/
|
|
2175
2484
|
declare function loadLockFile(input?: LoadLockFileInput, deps?: LoadLockFileDeps): Promise<LockFile>;
|
|
2176
2485
|
|
|
2177
|
-
/**
|
|
2486
|
+
/**
|
|
2487
|
+
* The file name of the project's lock-file, resolved against the run's working directory. Commit it
|
|
2488
|
+
* alongside the locale files: it is the baseline that lets verbatra tell a stale translation from a
|
|
2489
|
+
* current one. See {@link LockFile} for its contents.
|
|
2490
|
+
*/
|
|
2178
2491
|
declare const LOCK_FILE_NAME = "verbatra.lock.json";
|
|
2179
2492
|
|
|
2180
2493
|
/**
|
|
2181
|
-
*
|
|
2182
|
-
*
|
|
2183
|
-
*
|
|
2184
|
-
* apiKeyEnvVar/OPENAI_COMPATIBLE_API_KEY/placeholder fallback (see resolveOpenAiCompatibleKey in
|
|
2185
|
-
* @verbatra/ai-providers), so it does not fit the `providerEnv` table below and `init` scaffolding does
|
|
2186
|
-
* not offer it. This is the single source of truth for the exclusion; other packages import this type
|
|
2187
|
-
* rather than re-deriving `Exclude<ProviderId, "openai-compatible">` themselves.
|
|
2494
|
+
* A provider that project scaffolding can offer out of the box. It excludes `openai-compatible`,
|
|
2495
|
+
* which needs a `baseUrl` and a model name that only the user can supply, so there is nothing
|
|
2496
|
+
* sensible to prefill.
|
|
2188
2497
|
*/
|
|
2189
2498
|
type ScaffoldableProviderId = Exclude<ProviderId, "openai-compatible">;
|
|
2190
2499
|
/**
|
|
2191
|
-
*
|
|
2192
|
-
* provider
|
|
2500
|
+
* The facts a project generator needs to write a first config: which environment variable each
|
|
2501
|
+
* provider reads its API key from, a sensible starting model per provider, and the formats the SDK
|
|
2502
|
+
* can handle.
|
|
2503
|
+
*
|
|
2504
|
+
* It is exported so that the CLI's `init` command and any third-party generator prompt with the
|
|
2505
|
+
* same values the SDK actually enforces, rather than keeping a copy that drifts. Note the key names
|
|
2506
|
+
* only: no key value is present or reachable here.
|
|
2193
2507
|
*/
|
|
2194
2508
|
declare const scaffoldingMetadata: {
|
|
2195
|
-
/**
|
|
2509
|
+
/** The environment variable each scaffoldable provider reads its API key from. */
|
|
2196
2510
|
readonly providerEnv: {
|
|
2197
2511
|
readonly anthropic: "ANTHROPIC_API_KEY";
|
|
2198
2512
|
readonly openai: "OPENAI_API_KEY";
|
|
2199
2513
|
readonly gemini: "GEMINI_API_KEY";
|
|
2200
2514
|
readonly deepl: "DEEPL_API_KEY";
|
|
2201
2515
|
};
|
|
2202
|
-
/**
|
|
2516
|
+
/** A reasonable default model to prefill per language-model provider. DeepL has none, since it takes no model. */
|
|
2203
2517
|
readonly scaffoldModels: {
|
|
2204
2518
|
readonly anthropic: "claude-sonnet-4-6";
|
|
2205
2519
|
readonly openai: "gpt-5.4-mini";
|
|
2206
2520
|
readonly gemini: "gemini-2.5-flash";
|
|
2207
2521
|
};
|
|
2208
|
-
/**
|
|
2522
|
+
/**
|
|
2523
|
+
* The option key each language-model provider takes its output token limit under, since they do
|
|
2524
|
+
* not agree: Anthropic calls it `maxTokens` and the others `maxOutputTokens`. A generator that
|
|
2525
|
+
* prefills a token limit must read the key from here rather than assume one, because
|
|
2526
|
+
* {@link verbatraConfigSchema} validates each provider's options strictly and rejects the wrong
|
|
2527
|
+
* one. DeepL has no entry, since it takes no token limit.
|
|
2528
|
+
*/
|
|
2529
|
+
readonly providerTokenLimitKeys: {
|
|
2530
|
+
readonly anthropic: "maxTokens";
|
|
2531
|
+
readonly openai: "maxOutputTokens";
|
|
2532
|
+
readonly gemini: "maxOutputTokens";
|
|
2533
|
+
};
|
|
2534
|
+
/** Every i18n file format the SDK can read and write. */
|
|
2209
2535
|
readonly supportedFormats: readonly ["i18next-json", "vue-i18n-json", "next-intl-json", "ngx-translate-json", "xliff", "yaml", "arb", "properties"];
|
|
2210
2536
|
};
|
|
2211
2537
|
|
|
2212
|
-
/**
|
|
2538
|
+
/**
|
|
2539
|
+
* The minimal file-watching surface {@link watch} needs. Supplying your own through
|
|
2540
|
+
* `deps.createWatcher` lets an embedding application reuse a watcher it already runs, or drive the
|
|
2541
|
+
* flow deterministically in a test.
|
|
2542
|
+
*/
|
|
2213
2543
|
interface Watcher {
|
|
2214
|
-
/**
|
|
2544
|
+
/** Registers a listener called on each change to the watched paths. */
|
|
2215
2545
|
onChange(listener: () => void): void;
|
|
2216
|
-
/**
|
|
2546
|
+
/** Stops watching and releases the underlying resources. */
|
|
2217
2547
|
close(): Promise<void>;
|
|
2218
2548
|
}
|
|
2219
|
-
/** Builds a {@link Watcher}
|
|
2549
|
+
/** Builds a {@link Watcher} over the given absolute paths. Defaults to a chokidar-backed watcher. */
|
|
2220
2550
|
type CreateWatcher = (paths: readonly string[]) => Watcher;
|
|
2221
|
-
/**
|
|
2551
|
+
/** Runs one translation pass. Defaults to {@link translate}, and is the seam a test substitutes. */
|
|
2222
2552
|
type RunTranslate = (input: TranslateInput) => Promise<RunSummary>;
|
|
2223
|
-
/**
|
|
2553
|
+
/**
|
|
2554
|
+
* The outcome of a single watch-triggered run, handed to `onRun`. A failed run is reported here
|
|
2555
|
+
* rather than thrown, because a long-lived watcher must survive a bad run and keep watching.
|
|
2556
|
+
*/
|
|
2224
2557
|
type WatchRunResult = {
|
|
2558
|
+
/** The run completed. Inspect the summary for per-locale outcomes, which may still include failures. */
|
|
2225
2559
|
readonly status: "succeeded";
|
|
2560
|
+
/** The run's per-locale account. */
|
|
2226
2561
|
readonly summary: RunSummary;
|
|
2227
2562
|
} | {
|
|
2563
|
+
/** The run threw before producing a summary, for instance because the source became unreadable. */
|
|
2228
2564
|
readonly status: "failed";
|
|
2229
|
-
/**
|
|
2230
|
-
* A secret-free projection of the run's failure. `code` is a preserved string (the underlying
|
|
2231
|
-
* error's `code`, or `"WATCH_RUN_FAILED"` as a fallback), not an {@link SdkErrorCode}.
|
|
2232
|
-
*/
|
|
2565
|
+
/** The failure's code and message. Never carries a secret. */
|
|
2233
2566
|
readonly error: {
|
|
2567
|
+
/** The failure's own code where it had one, and `WATCH_RUN_FAILED` otherwise. */
|
|
2234
2568
|
readonly code: string;
|
|
2569
|
+
/** A human-readable description of the failure. Never contains a secret. */
|
|
2235
2570
|
readonly message: string;
|
|
2236
2571
|
};
|
|
2237
2572
|
};
|
|
2238
|
-
/**
|
|
2573
|
+
/** Input for {@link watch}. */
|
|
2239
2574
|
interface WatchInput {
|
|
2240
|
-
/** The
|
|
2575
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
2241
2576
|
readonly config: VerbatraConfig;
|
|
2242
|
-
/** Directory the
|
|
2577
|
+
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
2243
2578
|
readonly cwd?: string;
|
|
2244
|
-
/**
|
|
2579
|
+
/** How long to coalesce rapid source changes before running. Defaults to 300 milliseconds. */
|
|
2245
2580
|
readonly debounceMs?: number;
|
|
2246
|
-
/** Called once per run with its result. The SDK does no logging; this is the only output. */
|
|
2247
|
-
readonly onRun: (result: WatchRunResult) => void;
|
|
2248
2581
|
/**
|
|
2249
|
-
*
|
|
2250
|
-
*
|
|
2251
|
-
* one-shot run does.
|
|
2582
|
+
* Called after every run with its outcome. This is the only way to observe a watch session, since
|
|
2583
|
+
* {@link watch} itself resolves as soon as watching starts.
|
|
2252
2584
|
*/
|
|
2585
|
+
readonly onRun: (result: WatchRunResult) => void;
|
|
2586
|
+
/** Called while waiting on another process's write lock. */
|
|
2253
2587
|
readonly onLockWait?: LockWaitListener;
|
|
2254
|
-
/**
|
|
2255
|
-
* Passed through to every run's {@link TranslateInput.onProgress}: fires per locale and per provider
|
|
2256
|
-
* sub-batch as each run advances, so a watch caller can surface the same progress a one-shot run does.
|
|
2257
|
-
*/
|
|
2588
|
+
/** Called as locales and sub-batches start and finish, for progress reporting. */
|
|
2258
2589
|
readonly onProgress?: ProgressListener;
|
|
2259
|
-
/**
|
|
2590
|
+
/** How long to wait for a locale's write lock before that run fails. */
|
|
2260
2591
|
readonly lockAcquireTimeoutMs?: number;
|
|
2261
|
-
/**
|
|
2262
|
-
* Passed through to every run's {@link TranslateInput.concurrency}: how many locales each cycle may
|
|
2263
|
-
* run at once. Defaults to 1 (strictly serial). Because the per-cycle budget tracker is fresh, a
|
|
2264
|
-
* value greater than 1 cannot be honored alongside a `maxTokens` budget; {@link watch} refuses that
|
|
2265
|
-
* combination at startup rather than letting every cycle fail on it.
|
|
2266
|
-
*/
|
|
2592
|
+
/** How many locales to run at once. Validated once when watching starts, and applied to every run. */
|
|
2267
2593
|
readonly concurrency?: number;
|
|
2268
|
-
/**
|
|
2269
|
-
* Passed through to every run's {@link TranslateInput.cache}: when false, each run bypasses the
|
|
2270
|
-
* translation-memory cache (both read and write). On by default.
|
|
2271
|
-
*/
|
|
2594
|
+
/** Consult and update the translation memory. Defaults to true. */
|
|
2272
2595
|
readonly cache?: boolean;
|
|
2273
2596
|
}
|
|
2274
|
-
/**
|
|
2597
|
+
/** Injectable dependencies for {@link watch}. Every field has a working default. */
|
|
2275
2598
|
interface WatchDeps {
|
|
2276
|
-
/**
|
|
2599
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
2277
2600
|
readonly adapterRegistry?: AdapterRegistry;
|
|
2278
|
-
/** Provider
|
|
2601
|
+
/** Provider factory. Defaults to constructing the provider named in the config. */
|
|
2279
2602
|
readonly createProvider?: CreateProvider;
|
|
2280
|
-
/** File
|
|
2603
|
+
/** File-system port. Defaults to the real file system. */
|
|
2281
2604
|
readonly fs?: SdkFs;
|
|
2282
|
-
/**
|
|
2605
|
+
/** Watcher factory. Defaults to a chokidar-backed watcher over the source file. */
|
|
2283
2606
|
readonly createWatcher?: CreateWatcher;
|
|
2284
|
-
/**
|
|
2607
|
+
/** Translation runner. Defaults to {@link translate} wired with the other dependencies. */
|
|
2285
2608
|
readonly runTranslate?: RunTranslate;
|
|
2286
2609
|
}
|
|
2287
|
-
/**
|
|
2610
|
+
/** The handle returned by {@link watch}, used to end the session. */
|
|
2288
2611
|
interface WatchController {
|
|
2289
|
-
/**
|
|
2612
|
+
/** Stops watching and waits for any in-flight run to settle. Safe to call more than once. */
|
|
2290
2613
|
stop(): Promise<void>;
|
|
2291
2614
|
}
|
|
2292
2615
|
/**
|
|
2293
|
-
*
|
|
2294
|
-
*
|
|
2295
|
-
*
|
|
2296
|
-
*
|
|
2297
|
-
*
|
|
2298
|
-
*
|
|
2299
|
-
*
|
|
2300
|
-
*
|
|
2301
|
-
*
|
|
2302
|
-
*
|
|
2303
|
-
*
|
|
2304
|
-
*
|
|
2305
|
-
*
|
|
2306
|
-
*
|
|
2307
|
-
*
|
|
2308
|
-
*
|
|
2309
|
-
* @
|
|
2310
|
-
*
|
|
2311
|
-
* @
|
|
2312
|
-
* @
|
|
2313
|
-
*
|
|
2314
|
-
*
|
|
2315
|
-
*
|
|
2316
|
-
* @throws {@link SdkError} `
|
|
2616
|
+
* Watches the source locale file and re-runs {@link translate} on each debounced change. It is the
|
|
2617
|
+
* development-loop counterpart to a one-shot run: edit a string, and the target locales catch up
|
|
2618
|
+
* without a manual command.
|
|
2619
|
+
*
|
|
2620
|
+
* One run starts immediately once watching is established, without waiting for a change, so the
|
|
2621
|
+
* target locales are brought up to date the moment the session opens. Its outcome arrives through
|
|
2622
|
+
* `onRun` like any other, and it may still be in flight when the returned promise resolves.
|
|
2623
|
+
*
|
|
2624
|
+
* The returned promise resolves as soon as watching is established, not when translation finishes,
|
|
2625
|
+
* so every run outcome arrives through `onRun`. A run that throws is delivered as a failed
|
|
2626
|
+
* {@link WatchRunResult} rather than escaping the session, because a watcher that died on the first
|
|
2627
|
+
* bad save would be useless. Only startup problems, validated before any watching begins, throw.
|
|
2628
|
+
*
|
|
2629
|
+
* Rapid saves are coalesced by `debounceMs`, and runs never overlap, so an editor writing a file
|
|
2630
|
+
* several times in a moment produces one run rather than a queue of them.
|
|
2631
|
+
*
|
|
2632
|
+
* Call {@link WatchController.stop} to end the session.
|
|
2633
|
+
*
|
|
2634
|
+
* @param input - The config, the debounce window, and the run callback.
|
|
2635
|
+
* @param deps - Optional adapter registry, provider factory, file-system, watcher, and runner overrides.
|
|
2636
|
+
* @returns A controller that stops the session.
|
|
2637
|
+
*
|
|
2638
|
+
* @throws {@link SdkError} `CONCURRENCY_INVALID`: `concurrency` is not an integer of at least 1.
|
|
2639
|
+
* @throws {@link SdkError} `CONCURRENCY_BUDGET_CONFLICT`: `concurrency` above 1 was combined with a
|
|
2640
|
+
* configured token budget.
|
|
2641
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
2642
|
+
* cannot be combined, or a configured locale has no valid path spelling under that style.
|
|
2643
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
2644
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist when watching
|
|
2645
|
+
* starts.
|
|
2646
|
+
*
|
|
2317
2647
|
* @example
|
|
2318
2648
|
* ```ts
|
|
2319
2649
|
* import { loadConfig, watch } from "@verbatra/sdk";
|
|
2320
2650
|
*
|
|
2321
|
-
* // The provider reads its API key from the environment (e.g. ANTHROPIC_API_KEY); no key is passed here.
|
|
2322
2651
|
* const config = await loadConfig();
|
|
2323
2652
|
* const controller = await watch({
|
|
2324
2653
|
* config,
|
|
2325
2654
|
* onRun: (result) => {
|
|
2326
|
-
* if (result.status === "
|
|
2327
|
-
* console.
|
|
2328
|
-
*
|
|
2329
|
-
* // Surfaced, not thrown: code is a preserved string (WATCH_RUN_FAILED is only the fallback).
|
|
2330
|
-
* console.error(`run failed: ${result.error.code} ${result.error.message}`);
|
|
2655
|
+
* if (result.status === "failed") {
|
|
2656
|
+
* console.error(`run failed: ${result.error.message}`);
|
|
2657
|
+
* return;
|
|
2331
2658
|
* }
|
|
2659
|
+
* console.log(`translated ${result.summary.succeeded.length} locales`);
|
|
2332
2660
|
* },
|
|
2333
2661
|
* });
|
|
2334
2662
|
*
|
|
2335
|
-
*
|
|
2336
|
-
* process.on("SIGINT", () => {
|
|
2337
|
-
* void controller.stop();
|
|
2338
|
-
* });
|
|
2663
|
+
* process.on("SIGINT", () => void controller.stop());
|
|
2339
2664
|
* ```
|
|
2340
2665
|
*/
|
|
2341
2666
|
declare function watch(input: WatchInput, deps?: WatchDeps): Promise<WatchController>;
|
|
2342
2667
|
|
|
2343
|
-
export { type BudgetBehavior, CACHE_FILE_NAME, type CheckDeps, type CheckInput, type CheckSummary, type ConfigSource, type CreateProvider, type CreateWatcher, DEFAULT_DELIMITED_PATH, DEFAULT_WORKBOOK_PATH, type DiffDeps, type DiffInput, type DiffSummary, type DuplicateKeyReport, type EditEntryDeps, type EditEntryInput, type EditEntryResult, type ExchangeFormat, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type GlossaryProvenance, type ImportWorkbookDeps, type ImportWorkbookInput, type IntegrityGateReason, type KeyIntegrityDeps, type KeyIntegrityEntry, type KeyIntegrityInput, type KeyValueDeps, type KeyValueInput, type KeyValueResult, LOCK_FILE_NAME, type LoadConfigOptions, type LoadLockFileDeps, type LoadLockFileInput, type LoadedConfig, type LocaleCheckSummary, type LocaleDiff, type LocaleFileSnapshot, type LocaleFinishedEvent, type LocaleKeyIntegrity, type LocaleNotice, type LocalePathResolver, type LocalePathResolverConfig, type LocaleSnapshotDelta, type LocaleStartedEvent, type LocaleStyle, type LocaleSummary, type LockFile, type LockHolder, type LockLocaleState, type LockStateDeps, type LockStateInput, type LockStateResult, type LockWaitEvent, type LockWaitListener, type MalformedRowReport, type NeedsReviewEntry, type ProgressEvent, type ProgressListener, type ProviderConfig, type ProviderId, type ReadLocaleFileSnapshotDeps, type ReadLocaleFileSnapshotInput, type RetranslateEntryDeps, type RetranslateEntryInput, type RetranslateEntryResult, type ReviewReasonCode, type RunBudget, type RunFinishedEvent, type RunStatusDeps, type RunStatusFile, type RunStatusInput, type RunStatusLocale, type RunStatusResult, type RunSummary, type RunTranslate, type ScaffoldableProviderId, SdkError, type SdkErrorCode, type SdkFs, type SdkNotice, type SdkNoticeCode, type SubBatchProgressEvent, type SupportedFormat$1 as SupportedFormat, type TranslateDeps, type TranslateInput, type TranslationMemory, type UsageSummary, type VerbatraConfig, type VerbatraConfigInput, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, check, createLocalePathResolver, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate, verbatraConfigSchema, watch };
|
|
2668
|
+
export { type BudgetBehavior, CACHE_FILE_NAME, type CheckDeps, type CheckInput, type CheckSummary, type ConfigSource, type CreateProvider, type CreateWatcher, DEFAULT_DELIMITED_PATH, DEFAULT_EXCHANGE_FORMAT, DEFAULT_WORKBOOK_PATH, type DiffDeps, type DiffInput, type DiffSummary, type DuplicateKeyReport, EXCHANGE_FORMATS, type EditEntryDeps, type EditEntryInput, type EditEntryResult, type ExchangeFormat, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type GlossaryProvenance, type ImportWorkbookDeps, type ImportWorkbookInput, type IntegrityGateReason, type KeyIntegrityDeps, type KeyIntegrityEntry, type KeyIntegrityInput, type KeyValueDeps, type KeyValueInput, type KeyValueResult, LOCK_FILE_NAME, type LoadConfigOptions, type LoadLockFileDeps, type LoadLockFileInput, type LoadedConfig, type LocaleCheckSummary, type LocaleDiff, type LocaleFileSnapshot, type LocaleFinishedEvent, type LocaleKeyIntegrity, type LocaleNotice, type LocalePathResolver, type LocalePathResolverConfig, type LocaleSnapshotDelta, type LocaleStartedEvent, type LocaleStyle, type LocaleSummary, type LockFile, type LockHolder, type LockLocaleState, type LockStateDeps, type LockStateInput, type LockStateResult, type LockWaitEvent, type LockWaitListener, type MalformedRowReport, type NeedsReviewEntry, type ProgressEvent, type ProgressListener, type ProviderConfig, type ProviderId, type ReadLocaleFileSnapshotDeps, type ReadLocaleFileSnapshotInput, type RetranslateEntryDeps, type RetranslateEntryInput, type RetranslateEntryResult, type ReviewReasonCode, type RunBudget, type RunFinishedEvent, type RunStatusDeps, type RunStatusFile, type RunStatusInput, type RunStatusLocale, type RunStatusResult, type RunSummary, type RunTranslate, type ScaffoldableProviderId, SdkError, type SdkErrorCode, type SdkFs, type SdkNotice, type SdkNoticeCode, type SubBatchProgressEvent, type SupportedFormat$1 as SupportedFormat, type TranslateDeps, type TranslateInput, type TranslationMemory, type UsageSummary, type VerbatraConfig, type VerbatraConfigInput, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, check, createLocalePathResolver, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate, verbatraConfigSchema, watch };
|