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