@verbatra/sdk 0.10.0 → 0.11.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 +55 -111
- package/dist/config-schema.json +175 -14
- package/dist/index.cjs +11987 -3689
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2989 -733
- package/dist/index.d.ts +2989 -733
- package/dist/index.js +11948 -3669
- package/dist/index.js.map +1 -1
- package/package.json +15 -14
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import OpenAI from 'openai';
|
|
|
17
17
|
* (an i18next plural key suffix, a vue-i18n pipe-separated value, or an ICU plural), which the adapter
|
|
18
18
|
* decides and which feeds the content hash.
|
|
19
19
|
*/
|
|
20
|
-
declare const translationEntrySchema$
|
|
20
|
+
declare const translationEntrySchema$2: z.ZodObject<{
|
|
21
21
|
key: z.ZodString;
|
|
22
22
|
namespace: z.ZodString;
|
|
23
23
|
value: z.ZodString;
|
|
@@ -30,14 +30,14 @@ declare const translationEntrySchema$1: z.ZodObject<{
|
|
|
30
30
|
* The validated shape of one translation unit; the inferred type of {@link translationEntrySchema},
|
|
31
31
|
* made deeply readonly. See that schema for what each field carries.
|
|
32
32
|
*/
|
|
33
|
-
type TranslationEntry$
|
|
33
|
+
type TranslationEntry$2 = Readonly<z.infer<typeof translationEntrySchema$2>>;
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Result of comparing source placeholders against translated-output placeholders as MULTISETS
|
|
37
37
|
* (counts matter, so dropping one of two occurrences of the same token is still a miss). Always
|
|
38
38
|
* returned, never thrown: a mismatch is data the caller decides what to do about, not a failure.
|
|
39
39
|
*/
|
|
40
|
-
interface PlaceholderIntegrityResult$
|
|
40
|
+
interface PlaceholderIntegrityResult$2 {
|
|
41
41
|
/** True when nothing is missing or extra: the multisets match, regardless of order. */
|
|
42
42
|
readonly matches: boolean;
|
|
43
43
|
/** Occurrences present in source but absent from the translation, one per dropped occurrence. Sorted. */
|
|
@@ -74,7 +74,7 @@ type PlaceholderExtractor = (value: string) => readonly string[];
|
|
|
74
74
|
* defines one) so it matches the entries' format; ai-providers never derives it itself and never parses
|
|
75
75
|
* a specific format's message syntax.
|
|
76
76
|
*/
|
|
77
|
-
type PlaceholderComparator = (source: string, translated: string) => PlaceholderIntegrityResult$
|
|
77
|
+
type PlaceholderComparator = (source: string, translated: string) => PlaceholderIntegrityResult$2;
|
|
78
78
|
/**
|
|
79
79
|
* A batch translation request. Format- and provider-neutral: it carries no prompt,
|
|
80
80
|
* model, key, or other provider-specific field. The placeholder extractor is
|
|
@@ -86,11 +86,18 @@ interface TranslateRequest {
|
|
|
86
86
|
/** BCP-47 target locale to translate into (for example, "de"). */
|
|
87
87
|
readonly targetLocale: string;
|
|
88
88
|
/** The entries to translate; at least one is required. */
|
|
89
|
-
readonly entries: readonly TranslationEntry$
|
|
89
|
+
readonly entries: readonly TranslationEntry$2[];
|
|
90
90
|
/** Optional source-term to target-term map applied by glossary-capable providers. */
|
|
91
91
|
readonly glossary?: Readonly<Record<string, string>>;
|
|
92
92
|
/** Optional target {@link Tone}; machine-translation providers map it to formality. */
|
|
93
93
|
readonly tone?: Tone;
|
|
94
|
+
/**
|
|
95
|
+
* Optional per-key maximum length budget, measured in grapheme clusters. A key present in the map
|
|
96
|
+
* whose translation comes back longer than its budget is flagged `MAX_LENGTH_EXCEEDED`; a key
|
|
97
|
+
* absent from the map is never flagged. Advisory only: it never changes what is sent to the
|
|
98
|
+
* provider and never withholds a value.
|
|
99
|
+
*/
|
|
100
|
+
readonly maxLength?: ReadonlyMap<string, number>;
|
|
94
101
|
/** Mandatory placeholder extractor; the output integrity check runs against it. */
|
|
95
102
|
readonly extractPlaceholders: PlaceholderExtractor;
|
|
96
103
|
/**
|
|
@@ -141,24 +148,65 @@ interface ProviderNotice {
|
|
|
141
148
|
}
|
|
142
149
|
/**
|
|
143
150
|
* Stable codes for a derived, per-key "needs review" signal. This is verbatra's own computed
|
|
144
|
-
* assessment, never a raw model self-score:
|
|
145
|
-
* values, and `PROVIDER_DEGRADED`
|
|
151
|
+
* assessment, never a raw model self-score: five are recomputable from plain source and translated
|
|
152
|
+
* values, and `FUZZY_CACHE_REUSE` and `PROVIDER_DEGRADED` are layered on afterwards, the first by
|
|
153
|
+
* the SDK from where the value came, the second from the batch's notices.
|
|
146
154
|
*
|
|
147
155
|
* - `LENGTH_RATIO_OUTLIER`: the translated value's length is far shorter or longer than the source's.
|
|
148
|
-
* Only considered once the trimmed source is long enough for the ratio to mean anything.
|
|
156
|
+
* Only considered once the trimmed source is long enough for the ratio to mean anything. Relative
|
|
157
|
+
* to the source, and measured in UTF-16 code units; contrast `MAX_LENGTH_EXCEEDED`.
|
|
158
|
+
* - `MAX_LENGTH_EXCEEDED`: the translated value is longer than the absolute budget configured for
|
|
159
|
+
* its key. Measured in grapheme clusters (user-perceived characters), on the value exactly as
|
|
160
|
+
* written with no trimming, and the comparison is inclusive, so a value of exactly the budget is
|
|
161
|
+
* not flagged. A key with no configured budget is never flagged. Advisory only: an over-budget
|
|
162
|
+
* value is still written to the locale file and still recorded in the lock file, because a
|
|
163
|
+
* correct translation that overruns a layout budget is more useful than no translation at all.
|
|
149
164
|
* - `EQUALS_SOURCE`: the translated value equals the source value once both are trimmed, so a
|
|
150
165
|
* difference in leading or trailing whitespace alone still counts as equal. Also requires that
|
|
151
166
|
* the locales differ and that the value contains at least one letter (so a bare symbol or number
|
|
152
167
|
* is not flagged).
|
|
153
168
|
* - `GLOSSARY_TERM_MISSED`: a configured glossary source term appeared in the source but its target
|
|
154
|
-
* term did not appear in the translation.
|
|
169
|
+
* term did not appear in the translation. Matching is case-insensitive, and the two sides are
|
|
170
|
+
* held to deliberately different standards. The source side requires a whole-word occurrence, so
|
|
171
|
+
* the term "AI" is not found inside "Airport" and cannot raise an expectation the translator was
|
|
172
|
+
* never given; a source term whose edge character belongs to a script written without word
|
|
173
|
+
* separators (Han, kana, Thai and similar) has no boundary to anchor to and falls back to
|
|
174
|
+
* containment. The target side requires containment only, because a translated term legitimately
|
|
175
|
+
* fuses with the text around it: German compounds ("Benutzerkonto"), Korean particles ("계정을")
|
|
176
|
+
* and Japanese loanwords all carry the term with no boundary around it, and demanding one there
|
|
177
|
+
* would flag correct translations in most languages a glossary is used for.
|
|
155
178
|
* - `INTEGRITY_REORDERED`: the placeholder set matched but landed in a different order.
|
|
156
179
|
* - `PROVIDER_DEGRADED`: the batch this key came from carried a `FORMALITY_DOWNGRADED` or
|
|
157
180
|
* `GLOSSARY_IGNORED` notice, either of which can silently change wording. A
|
|
158
181
|
* `PLACEHOLDER_UNSUPPORTED` notice does not raise it, since the affected entries are withheld
|
|
159
182
|
* rather than degraded.
|
|
183
|
+
* - `FUZZY_CACHE_REUSE`: the value was not translated for this source text at all. It was reused
|
|
184
|
+
* from the translation memory for an earlier, near-identical source that has since been edited,
|
|
185
|
+
* so it is a translation of text that is no longer the source text. Similarity is measured in
|
|
186
|
+
* characters and meaning is not a function of character distance: a dropped negation, an
|
|
187
|
+
* inverted modal or a swapped proper noun are all small edits with large consequences, and no
|
|
188
|
+
* threshold separates them from a typo fix. Every such reuse therefore carries this reason, on
|
|
189
|
+
* every run, regardless of score.
|
|
190
|
+
*
|
|
191
|
+
* This tuple is the single source of truth for the set. {@link ReviewReasonCode} is derived from
|
|
192
|
+
* it, so build any runtime validator or exhaustive lookup from this value rather than retyping the
|
|
193
|
+
* members; a hand-copied list silently falls behind the next addition.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* import { REVIEW_REASON_CODES } from "@verbatra/sdk";
|
|
198
|
+
* import { z } from "zod";
|
|
199
|
+
*
|
|
200
|
+
* const reasonSchema = z.enum(REVIEW_REASON_CODES);
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
declare const REVIEW_REASON_CODES: readonly ["LENGTH_RATIO_OUTLIER", "MAX_LENGTH_EXCEEDED", "EQUALS_SOURCE", "GLOSSARY_TERM_MISSED", "INTEGRITY_REORDERED", "PROVIDER_DEGRADED", "FUZZY_CACHE_REUSE"];
|
|
204
|
+
/**
|
|
205
|
+
* One of {@link REVIEW_REASON_CODES}. The union is derived from that tuple rather than written out
|
|
206
|
+
* again, so a code can only be added in one place and every consumer that builds a runtime schema
|
|
207
|
+
* or an exhaustive map from the tuple stays in step automatically.
|
|
160
208
|
*/
|
|
161
|
-
type ReviewReasonCode =
|
|
209
|
+
type ReviewReasonCode = (typeof REVIEW_REASON_CODES)[number];
|
|
162
210
|
/** A key flagged for human review, carrying every reason code that applies. */
|
|
163
211
|
interface ReviewFlag {
|
|
164
212
|
/** Always "review": the flag exists only for flagged keys, so there is no "ok" member. */
|
|
@@ -175,7 +223,7 @@ interface TranslateResult {
|
|
|
175
223
|
*/
|
|
176
224
|
readonly values: ReadonlyMap<string, string>;
|
|
177
225
|
/** The placeholder-integrity outcome for each key (source vs translated placeholder sets). */
|
|
178
|
-
readonly integrity: ReadonlyMap<string, PlaceholderIntegrityResult$
|
|
226
|
+
readonly integrity: ReadonlyMap<string, PlaceholderIntegrityResult$2>;
|
|
179
227
|
/** Token usage when the provider reports it; absent for token-less providers. */
|
|
180
228
|
readonly usage?: Usage;
|
|
181
229
|
/**
|
|
@@ -260,21 +308,26 @@ type GeminiModel = "gemini-2.5-flash" | "gemini-2.5-pro" | "gemma-4-26b-a4b-it"
|
|
|
260
308
|
type OpenAiModel = OpenAI.ChatModel;
|
|
261
309
|
|
|
262
310
|
/**
|
|
263
|
-
* Zod schema accepting exactly one of the
|
|
311
|
+
* Zod schema accepting exactly one of the fourteen source formats verbatra can read and write. Each
|
|
264
312
|
* member names the ecosystem it round-trips, not just a file extension, because several of them share
|
|
265
313
|
* one: `i18next-json` is i18next's nested JSON with its plural key suffixes, `vue-i18n-json` is Vue
|
|
266
314
|
* I18n's JSON with its pipe-separated plural values, `next-intl-json` is next-intl's ICU-message JSON,
|
|
267
|
-
* and `ngx-translate-json` is ngx-translate's nested JSON. The remaining
|
|
315
|
+
* and `ngx-translate-json` is ngx-translate's nested JSON. The remaining ten are `xliff` (the XLIFF
|
|
268
316
|
* interchange XML), `yaml` (plain nested YAML), `arb` (Flutter's Application Resource Bundle),
|
|
269
317
|
* `properties` (Java and Spring `.properties` files), `apple-strings` (Apple's flat `.strings`
|
|
270
318
|
* localization format for iOS and macOS), `apple-xcstrings` (Apple's Xcode String Catalog, a single
|
|
271
319
|
* JSON document holding every locale of a `.xcstrings` catalogue together), `android-xml` (Android's
|
|
272
320
|
* `res/values/strings.xml` resource format, including `<plurals>` and `translatable="false"` entries),
|
|
273
|
-
*
|
|
274
|
-
* `msgid_plural`/`msgstr[n]` plural forms keyed by the file's own `Plural-Forms` index count)
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
321
|
+
* `gettext-po` (GNU gettext's `.po` and `.pot` catalogs, including `msgctxt` disambiguation and
|
|
322
|
+
* `msgid_plural`/`msgstr[n]` plural forms keyed by the file's own `Plural-Forms` index count),
|
|
323
|
+
* `ini` (classic INI configuration files, one level of `[section]` headers over `key=value` lines,
|
|
324
|
+
* whose entries are addressed as `section.key`), and `resx` (.NET's XML resource format; its `<data>`
|
|
325
|
+
* elements carrying a `type` or `mimetype` attribute, and its designer metadata names, are preserved
|
|
326
|
+
* untouched rather than translated).
|
|
327
|
+
*
|
|
328
|
+
* The set is closed: adding a built-in format means adding a member here and an adapter that claims
|
|
329
|
+
* it. A format shipped by a package outside verbatra is named by a `custom:` identifier instead and
|
|
330
|
+
* never joins this set; see `FormatId`.
|
|
278
331
|
*/
|
|
279
332
|
declare const supportedFormatSchema$1: z.ZodEnum<{
|
|
280
333
|
"i18next-json": "i18next-json";
|
|
@@ -289,198 +342,1236 @@ declare const supportedFormatSchema$1: z.ZodEnum<{
|
|
|
289
342
|
"apple-xcstrings": "apple-xcstrings";
|
|
290
343
|
"android-xml": "android-xml";
|
|
291
344
|
"gettext-po": "gettext-po";
|
|
345
|
+
ini: "ini";
|
|
346
|
+
resx: "resx";
|
|
292
347
|
}>;
|
|
293
|
-
/** One of the
|
|
348
|
+
/** One of the fourteen supported source formats; the inferred type of {@link supportedFormatSchema}. */
|
|
294
349
|
type SupportedFormat$1 = z.infer<typeof supportedFormatSchema$1>;
|
|
295
|
-
|
|
296
350
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
351
|
+
* A format identifier claimed by an adapter that ships outside verbatra: the reserved
|
|
352
|
+
* `custom:` prefix followed by a lowercase, hyphen-separated name, for example
|
|
353
|
+
* `custom:my-format`.
|
|
354
|
+
*
|
|
355
|
+
* No built-in format name contains a colon, so a third-party identifier can never shadow a
|
|
356
|
+
* built-in one.
|
|
300
357
|
*/
|
|
301
|
-
type
|
|
302
|
-
/** The file was read in full. */
|
|
303
|
-
readonly kind: "ok";
|
|
304
|
-
/** The file's content, decoded as UTF-8. */
|
|
305
|
-
readonly content: string;
|
|
306
|
-
} | {
|
|
307
|
-
/** No readable file exists at the path. */
|
|
308
|
-
readonly kind: "missing";
|
|
309
|
-
} | {
|
|
310
|
-
/** The file exists but exceeds the requested byte limit, so nothing was read. */
|
|
311
|
-
readonly kind: "too-large";
|
|
312
|
-
};
|
|
358
|
+
type CustomFormatId$1 = `custom:${string}`;
|
|
313
359
|
/**
|
|
314
|
-
*
|
|
315
|
-
*
|
|
360
|
+
* Narrow a format identifier to a third-party one.
|
|
361
|
+
*
|
|
362
|
+
* @param format - The identifier to classify.
|
|
363
|
+
* @returns True when the identifier is a well-formed `custom:` identifier. A built-in format
|
|
364
|
+
* name always returns false.
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```ts
|
|
368
|
+
* isCustomFormatId("custom:toml"); // true
|
|
369
|
+
* isCustomFormatId("i18next-json"); // false
|
|
370
|
+
* ```
|
|
316
371
|
*/
|
|
317
|
-
|
|
318
|
-
/** The file was read in full. */
|
|
319
|
-
readonly kind: "ok";
|
|
320
|
-
/** The file's raw bytes. */
|
|
321
|
-
readonly bytes: Uint8Array;
|
|
322
|
-
} | {
|
|
323
|
-
/** No readable file exists at the path. */
|
|
324
|
-
readonly kind: "missing";
|
|
325
|
-
} | {
|
|
326
|
-
/** The file exists but exceeds the requested byte limit, so nothing was read. */
|
|
327
|
-
readonly kind: "too-large";
|
|
328
|
-
};
|
|
372
|
+
declare function isCustomFormatId(format: string): format is CustomFormatId$1;
|
|
329
373
|
/**
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
* embedding application can back part of a project with something other than a local disk.
|
|
374
|
+
* Any format verbatra can be asked to handle: one of the built-in {@link SupportedFormat}
|
|
375
|
+
* members, or a third-party {@link CustomFormatId}.
|
|
333
376
|
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
|
|
338
|
-
|
|
377
|
+
* A built-in name is checked against the closed set at compile time. A `custom:` identifier is
|
|
378
|
+
* only checked for shape, because the adapter behind it lives outside this package; if no
|
|
379
|
+
* registered adapter claims it, the run fails with a structured error naming the format.
|
|
380
|
+
*/
|
|
381
|
+
type FormatId$1 = SupportedFormat$1 | CustomFormatId$1;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* A single, format-neutral translation unit. Placeholders are supplied already extracted; core never
|
|
385
|
+
* derives them from the value, so an adapter that reads a file is the one authority on what counts as
|
|
386
|
+
* a placeholder in that format.
|
|
339
387
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
388
|
+
* The fields are: `key`, the entry's identifier within its namespace, non-empty and already flattened
|
|
389
|
+
* to the dotted path an adapter produced; `namespace`, the namespace the entry belongs to, empty when
|
|
390
|
+
* the format has no namespacing; `value`, the translatable string itself, which is untrusted input and
|
|
391
|
+
* may be empty; `description` and `meaning`, both optional free-form context that a provider is told to
|
|
392
|
+
* use only for disambiguation and never to translate or echo back; `placeholders`, the format's
|
|
393
|
+
* placeholder tokens in document order, used for the output integrity check and to decide which entries
|
|
394
|
+
* a placeholder-blind provider must withhold; and `isPlural`, whether the entry carries plural forms
|
|
395
|
+
* (an i18next plural key suffix, a vue-i18n pipe-separated value, or an ICU plural), which the adapter
|
|
396
|
+
* decides and which feeds the content hash.
|
|
345
397
|
*/
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
398
|
+
declare const translationEntrySchema$1: z.ZodObject<{
|
|
399
|
+
key: z.ZodString;
|
|
400
|
+
namespace: z.ZodString;
|
|
401
|
+
value: z.ZodString;
|
|
402
|
+
description: z.ZodOptional<z.ZodString>;
|
|
403
|
+
meaning: z.ZodOptional<z.ZodString>;
|
|
404
|
+
placeholders: z.ZodReadonly<z.ZodArray<z.ZodString>>;
|
|
405
|
+
isPlural: z.ZodBoolean;
|
|
406
|
+
}, z.core.$strip>;
|
|
407
|
+
/**
|
|
408
|
+
* The validated shape of one translation unit; the inferred type of {@link translationEntrySchema},
|
|
409
|
+
* made deeply readonly. See that schema for what each field carries.
|
|
410
|
+
*/
|
|
411
|
+
type TranslationEntry$1 = Readonly<z.infer<typeof translationEntrySchema$1>>;
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* All translation entries for one locale and namespace, keyed by entry key. This is the neutral
|
|
415
|
+
* intermediate representation every adapter reads into and writes back out of, so nothing downstream
|
|
416
|
+
* of an adapter parses a format's own syntax.
|
|
417
|
+
*/
|
|
418
|
+
interface LocaleResource$1 {
|
|
419
|
+
/** The locale these entries belong to (for example, "en" or "de"). */
|
|
420
|
+
readonly locale: string;
|
|
421
|
+
/** The namespace these entries belong to; empty when the format has no namespacing. */
|
|
422
|
+
readonly namespace: string;
|
|
423
|
+
/** The source format the resource came from, retained for round-trip fidelity on write. */
|
|
424
|
+
readonly format: FormatId$1;
|
|
425
|
+
/** Entries addressable by key, in the order the source file defined them. */
|
|
426
|
+
readonly entries: ReadonlyMap<string, TranslationEntry$1>;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** One distinct translated value inside an {@link InconsistencyGroup}, with every key holding it. */
|
|
430
|
+
interface InconsistentTranslation {
|
|
357
431
|
/**
|
|
358
|
-
*
|
|
359
|
-
*
|
|
432
|
+
* The translated value in its compared form: Unicode NFC, line endings folded to `\n`, and
|
|
433
|
+
* leading and trailing whitespace trimmed. Internal whitespace and letter case are kept.
|
|
360
434
|
*/
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
435
|
+
readonly value: string;
|
|
436
|
+
/** Every key in this locale holding this value, sorted by UTF-16 code unit. */
|
|
437
|
+
readonly keys: readonly string[];
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* One source string that is translated more than one way within a single target locale. Keys are
|
|
441
|
+
* grouped only when their source value, description, meaning, plural flag, plural form, and context
|
|
442
|
+
* all agree, so a string that deliberately carries different disambiguation metadata is never
|
|
443
|
+
* reported.
|
|
444
|
+
*/
|
|
445
|
+
interface InconsistencyGroup {
|
|
446
|
+
/** The shared source value in its compared form (see {@link InconsistentTranslation.value}). */
|
|
447
|
+
readonly source: string;
|
|
448
|
+
/** The disambiguation context every key in the group shares, when the format has one. */
|
|
449
|
+
readonly context?: string;
|
|
450
|
+
/** The description every key in the group shares, when there is one. */
|
|
451
|
+
readonly description?: string;
|
|
452
|
+
/** The meaning every key in the group shares, when there is one. */
|
|
453
|
+
readonly meaning?: string;
|
|
454
|
+
/** Whether the grouped source entries carry plural forms. */
|
|
455
|
+
readonly isPlural: boolean;
|
|
456
|
+
/**
|
|
457
|
+
* The plural form every key in the group shares (a CLDR category such as `one`, or a format's own
|
|
458
|
+
* plural index), when the format encodes one per key. Different forms of one key are never grouped.
|
|
459
|
+
*/
|
|
460
|
+
readonly pluralForm?: string;
|
|
461
|
+
/** Every distinct translation, at least two, sorted by value in UTF-16 code unit order. */
|
|
462
|
+
readonly translations: readonly InconsistentTranslation[];
|
|
366
463
|
}
|
|
367
464
|
|
|
368
465
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
* Entries are keyed by configuration fingerprint, then locale, then source-content hash. The
|
|
374
|
-
* fingerprint layer means that changing the provider, model, tone, or glossary does not silently
|
|
375
|
-
* reuse translations produced under the old settings; those entries simply stop matching.
|
|
466
|
+
* Result of comparing source placeholders against translated-output placeholders as MULTISETS
|
|
467
|
+
* (counts matter, so dropping one of two occurrences of the same token is still a miss). Always
|
|
468
|
+
* returned, never thrown: a mismatch is data the caller decides what to do about, not a failure.
|
|
376
469
|
*/
|
|
377
|
-
interface
|
|
378
|
-
/**
|
|
379
|
-
readonly
|
|
380
|
-
/**
|
|
381
|
-
readonly
|
|
470
|
+
interface PlaceholderIntegrityResult$1 {
|
|
471
|
+
/** True when nothing is missing or extra: the multisets match, regardless of order. */
|
|
472
|
+
readonly matches: boolean;
|
|
473
|
+
/** Occurrences present in source but absent from the translation, one per dropped occurrence. Sorted. */
|
|
474
|
+
readonly missing: readonly string[];
|
|
475
|
+
/** Occurrences present in the translation but absent from source, one per surplus occurrence. Sorted. */
|
|
476
|
+
readonly extra: readonly string[];
|
|
477
|
+
/** Same multiset present in both, but in a different order. Never true alongside a mismatch. */
|
|
478
|
+
readonly reordered: boolean;
|
|
382
479
|
}
|
|
383
480
|
|
|
481
|
+
declare const SOURCE_FRAMEWORKS: readonly ["i18next"];
|
|
384
482
|
/**
|
|
385
|
-
* The
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
483
|
+
* The translation framework whose call sites a source scan looks for. The set is closed: a
|
|
484
|
+
* framework is only nameable here once an extractor ships for it, so a configured value that
|
|
485
|
+
* resolves to nothing cannot exist.
|
|
486
|
+
*
|
|
487
|
+
* `i18next` covers the `t(...)`, `$t(...)`, and `<object>.t(...)` shapes, each also through an
|
|
488
|
+
* optional call (`t?.(...)`) and an explicit type-argument list (`t<string>(...)`), with the key as
|
|
489
|
+
* the first argument and an optional default as either the second argument or a `defaultValue`
|
|
490
|
+
* field on the options object. A namespace-qualified key such as `t("common:nav.home")` is
|
|
491
|
+
* reported as a dynamic call site rather than extracted, because one configuration addresses one
|
|
492
|
+
* catalog file.
|
|
389
493
|
*/
|
|
390
|
-
|
|
494
|
+
type SourceFramework = (typeof SOURCE_FRAMEWORKS)[number];
|
|
391
495
|
|
|
496
|
+
/** One application source file, already read, as it is handed to a {@link SourceExtractor}. */
|
|
497
|
+
interface SourceFile {
|
|
498
|
+
/** The file's absolute path, used only for reporting. */
|
|
499
|
+
readonly path: string;
|
|
500
|
+
/** The file's full text. It is untrusted input and is never echoed into a result. */
|
|
501
|
+
readonly content: string;
|
|
502
|
+
}
|
|
503
|
+
/** One translation call site whose key argument was a static string. */
|
|
504
|
+
interface ExtractedCallSite {
|
|
505
|
+
/** The key exactly as the call site spelled it. */
|
|
506
|
+
readonly key: string;
|
|
507
|
+
/** The default value the call site supplied, if any. Absent when the call named only a key. */
|
|
508
|
+
readonly defaultValue?: string;
|
|
509
|
+
/** The one-based line the call site sits on. */
|
|
510
|
+
readonly line: number;
|
|
511
|
+
}
|
|
392
512
|
/**
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
* {@link ProviderConfig}.
|
|
513
|
+
* One translation call site whose key argument could not be resolved statically: a variable, a
|
|
514
|
+
* member expression, or a template literal carrying an expression. It is reported rather than
|
|
515
|
+
* guessed at, so no invented key ever reaches a catalog.
|
|
397
516
|
*/
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
maxOutputTokens: z.ZodNumber;
|
|
410
|
-
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
411
|
-
}, z.core.$strict>;
|
|
412
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
413
|
-
id: z.ZodLiteral<"gemini">;
|
|
414
|
-
options: z.ZodObject<{
|
|
415
|
-
model: z.ZodString;
|
|
416
|
-
maxOutputTokens: z.ZodNumber;
|
|
417
|
-
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
418
|
-
}, z.core.$strict>;
|
|
419
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
420
|
-
id: z.ZodLiteral<"deepl">;
|
|
421
|
-
options: z.ZodObject<{
|
|
422
|
-
glossaryId: z.ZodOptional<z.ZodString>;
|
|
423
|
-
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
424
|
-
}, z.core.$strict>;
|
|
425
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
426
|
-
id: z.ZodLiteral<"google-translate">;
|
|
427
|
-
options: z.ZodObject<{
|
|
428
|
-
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
429
|
-
}, z.core.$strict>;
|
|
430
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
431
|
-
id: z.ZodLiteral<"openai-compatible">;
|
|
432
|
-
options: z.ZodObject<{
|
|
433
|
-
baseUrl: z.ZodURL;
|
|
434
|
-
model: z.ZodString;
|
|
435
|
-
maxOutputTokens: z.ZodNumber;
|
|
436
|
-
apiKeyEnvVar: z.ZodOptional<z.ZodString>;
|
|
437
|
-
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
438
|
-
}, z.core.$strict>;
|
|
439
|
-
}, z.core.$strip>], "id">;
|
|
517
|
+
interface DynamicCallSite {
|
|
518
|
+
/** The one-based line the call site sits on. */
|
|
519
|
+
readonly line: number;
|
|
520
|
+
}
|
|
521
|
+
/** A catalog key a file names statically, wherever it names it. */
|
|
522
|
+
interface ReferencedKeySite {
|
|
523
|
+
/** The key as the source can reach it. */
|
|
524
|
+
readonly key: string;
|
|
525
|
+
/** The one-based line it is named on. */
|
|
526
|
+
readonly line: number;
|
|
527
|
+
}
|
|
440
528
|
/**
|
|
441
|
-
*
|
|
442
|
-
* carries exactly the options it supports and nothing else.
|
|
443
|
-
*
|
|
444
|
-
* No variant has a field for an API key. Keys are read from the environment by the provider itself
|
|
445
|
-
* (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `DEEPL_API_KEY`, or
|
|
446
|
-
* `GOOGLE_TRANSLATE_API_KEY`), which is what keeps them out of config files and out of version
|
|
447
|
-
* control. The `openai-compatible` variant may name a different environment variable through
|
|
448
|
-
* `apiKeyEnvVar`, but still never holds the key itself.
|
|
529
|
+
* A key argument with a static head and a dynamic rest, such as the template literal `nav.${page}`.
|
|
449
530
|
*/
|
|
450
|
-
|
|
531
|
+
interface KeyPrefixSite {
|
|
532
|
+
/** The static head every key this site can reach starts with. */
|
|
533
|
+
readonly prefix: string;
|
|
534
|
+
/** The one-based line the site sits on. */
|
|
535
|
+
readonly line: number;
|
|
536
|
+
}
|
|
451
537
|
/**
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
538
|
+
* Why a place in the source reaches keys a static reading cannot bound.
|
|
539
|
+
*
|
|
540
|
+
* - `dynamic-key-prefix`: a key prefix (a `keyPrefix` option or a `getFixedT` prefix argument) is
|
|
541
|
+
* not a static string.
|
|
542
|
+
* - `trans-without-key`: a `Trans` element carries no static `i18nKey`, so its key is its children.
|
|
543
|
+
* - `aliased-translate-function`: the translate function is assigned somewhere its calls cannot be
|
|
544
|
+
* followed, such as an object property.
|
|
545
|
+
* - `translate-function-escapes`: a translate function bound from a recognised source appears
|
|
546
|
+
* somewhere other than a direct call, its own binding, a local alias, or the `t` attribute of a
|
|
547
|
+
* `Trans` or `Translation` element (for example as an argument, a property, an export, or a
|
|
548
|
+
* ternary operand), so other code can call it with keys this file never names.
|
|
549
|
+
* - `unrecognised-translate-source`: a translate source (`useTranslation`, `withTranslation`,
|
|
550
|
+
* `getFixedT`, a `Translation` render prop, a member `t`, or a `t` import) is used in a shape the
|
|
551
|
+
* scan does not recognise, so the keys and prefix the resulting function uses are unknown.
|
|
455
552
|
*/
|
|
456
|
-
type
|
|
457
|
-
|
|
553
|
+
type UnresolvedKeySiteReason = "dynamic-key-prefix" | "trans-without-key" | "aliased-translate-function" | "translate-function-escapes" | "unrecognised-translate-source";
|
|
554
|
+
/** A place whose reachable keys cannot be bounded statically. */
|
|
555
|
+
interface UnresolvedKeySite {
|
|
556
|
+
/** Why the keys it reaches are unknown. */
|
|
557
|
+
readonly reason: UnresolvedKeySiteReason;
|
|
558
|
+
/** The one-based line it sits on. */
|
|
559
|
+
readonly line: number;
|
|
560
|
+
}
|
|
561
|
+
/** Everything one file tells a caller about which catalog keys it can reach. */
|
|
562
|
+
interface KeyUsage {
|
|
563
|
+
/** Every static key the file names, including the extracted calls, in line order. */
|
|
564
|
+
readonly references: readonly ReferencedKeySite[];
|
|
565
|
+
/** Key arguments with no static part at all. */
|
|
566
|
+
readonly dynamic: readonly DynamicCallSite[];
|
|
567
|
+
/** Key arguments with a static head. */
|
|
568
|
+
readonly prefixes: readonly KeyPrefixSite[];
|
|
569
|
+
/** Places whose reachable keys cannot be bounded. */
|
|
570
|
+
readonly unresolved: readonly UnresolvedKeySite[];
|
|
571
|
+
}
|
|
572
|
+
/** Everything one {@link SourceExtractor} found in one file. */
|
|
573
|
+
interface FileExtraction {
|
|
574
|
+
/** Call sites whose key was a static string. */
|
|
575
|
+
readonly calls: readonly ExtractedCallSite[];
|
|
576
|
+
/** Call sites whose key could not be resolved statically. */
|
|
577
|
+
readonly dynamic: readonly DynamicCallSite[];
|
|
578
|
+
/**
|
|
579
|
+
* What the file tells a caller asking which catalog keys the source can reach, beyond the calls
|
|
580
|
+
* above: static keys named some other way, template keys with a static head, and constructs
|
|
581
|
+
* whose keys cannot be bounded. Absent for an extractor that does not report it, in which case
|
|
582
|
+
* the calls and dynamic sites above are all that is known.
|
|
583
|
+
*/
|
|
584
|
+
readonly usage?: KeyUsage;
|
|
585
|
+
/**
|
|
586
|
+
* Whether the file could not be read to its end, so everything above it is partial. An
|
|
587
|
+
* unterminated block comment or template literal abandons the rest of the file. In a file read as
|
|
588
|
+
* markup (`.tsx`, `.jsx`, `.js`), so does a JSX element that never closes or is closed by an
|
|
589
|
+
* enclosing element's tag, and a quoted string in code that runs into the end of its line. The
|
|
590
|
+
* scan records it as an `unparseable` diagnostic and carries on, rather than reporting a short
|
|
591
|
+
* result as if it were the whole file.
|
|
592
|
+
*/
|
|
593
|
+
readonly truncated?: boolean;
|
|
594
|
+
}
|
|
458
595
|
/**
|
|
459
|
-
* The
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
* own tooling.
|
|
463
|
-
*
|
|
464
|
-
* The object is strict, so an unrecognized key is an error rather than being silently ignored: a
|
|
465
|
-
* typo in a config file is reported instead of quietly doing nothing. The one exception is the
|
|
466
|
-
* optional `$schema` key, accepted so a JSON or YAML config can point an editor at the JSON Schema
|
|
467
|
-
* document the package ships as `@verbatra/sdk/config-schema.json`.
|
|
596
|
+
* The contract every framework's source extractor implements: given one file's text, report the
|
|
597
|
+
* translation call sites it contains. One implementation per framework, selected by the configured
|
|
598
|
+
* `extract.framework`.
|
|
468
599
|
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
* per-field check on `files.pattern`.
|
|
600
|
+
* It is pure: it reads no file, makes no network call, and holds no state between files. Supplying
|
|
601
|
+
* your own as `deps.createExtractor` is how you teach `extract` a call shape verbatra does not
|
|
602
|
+
* ship yet.
|
|
473
603
|
*/
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
604
|
+
interface SourceExtractor {
|
|
605
|
+
/** The framework whose call shapes this extractor knows. */
|
|
606
|
+
readonly framework: SourceFramework;
|
|
607
|
+
/** The file extensions this extractor can read, each including its leading dot. */
|
|
608
|
+
readonly extensions: readonly string[];
|
|
609
|
+
/**
|
|
610
|
+
* Find every translation call site in one file.
|
|
611
|
+
*
|
|
612
|
+
* @param file - The file's path and full text.
|
|
613
|
+
* @returns The static and dynamic call sites found, in document order.
|
|
614
|
+
*/
|
|
615
|
+
extract(file: SourceFile): FileExtraction;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** Where something was found: a source file and a line inside it. Never a file's contents. */
|
|
619
|
+
interface SourceLocation {
|
|
620
|
+
/** The source file, relative to the run's working directory, with forward slashes. */
|
|
621
|
+
readonly file: string;
|
|
622
|
+
/** The one-based line number. */
|
|
623
|
+
readonly line: number;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* One key found at two or more call sites that disagree on its default value. Neither value is
|
|
627
|
+
* written: resolving it last-write-wins would make the catalog depend on directory traversal order,
|
|
628
|
+
* so the disagreement is reported for a person to settle at the call sites.
|
|
629
|
+
*/
|
|
630
|
+
interface KeyConflict {
|
|
631
|
+
/** The key the call sites disagree about. */
|
|
632
|
+
readonly key: string;
|
|
633
|
+
/** Every distinct default value found, in the order the scan met them. */
|
|
634
|
+
readonly values: readonly string[];
|
|
635
|
+
/** Where each of those values was found, index-aligned with `values`. */
|
|
636
|
+
readonly locations: readonly SourceLocation[];
|
|
637
|
+
}
|
|
638
|
+
type ScanDiagnosticReason = "unreadable" | "too-large" | "unparseable" | "unreadable-directory";
|
|
639
|
+
/** One file or directory the scan could not read, reported as data so one bad file never aborts a run. */
|
|
640
|
+
interface ScanDiagnostic {
|
|
641
|
+
/** The file or directory, relative to the run's working directory, with forward slashes. */
|
|
642
|
+
readonly file: string;
|
|
643
|
+
/** Why it was skipped. */
|
|
644
|
+
readonly reason: ScanDiagnosticReason;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* One hardcoded string literal that reads as user-facing text and never went through a translation
|
|
649
|
+
* call. It names where the literal sits and a bounded excerpt of it, never the surrounding source.
|
|
650
|
+
*/
|
|
651
|
+
interface LiteralFinding {
|
|
652
|
+
/** The source file, relative to the run's working directory, with forward slashes. */
|
|
653
|
+
readonly file: string;
|
|
654
|
+
/** The one-based line the literal starts on. */
|
|
655
|
+
readonly line: number;
|
|
656
|
+
/** The one-based column the literal starts at: its opening quote, or its first character of JSX text. */
|
|
657
|
+
readonly column: number;
|
|
658
|
+
/**
|
|
659
|
+
* The literal's text with whitespace collapsed, cut to at most 80 characters. In JSX text and a
|
|
660
|
+
* quoted JSX attribute value, character references such as `&` are decoded.
|
|
661
|
+
*/
|
|
662
|
+
readonly text: string;
|
|
663
|
+
/** Whether `text` was cut short. A cut excerpt ends in `...`. */
|
|
664
|
+
readonly truncated: boolean;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Why a literal that would have been reported was held back.
|
|
668
|
+
*
|
|
669
|
+
* - `directive`: a `verbatra-ignore-next-line` comment above it, or a `verbatra-ignore-line`
|
|
670
|
+
* comment on the same line. A next-line directive targets the next line that holds code, skipping
|
|
671
|
+
* blank and comment-only lines, and covers every literal that starts on that line plus every
|
|
672
|
+
* attribute of a JSX opening tag that starts there, even when the tag spans several lines. Text and
|
|
673
|
+
* nested elements on later lines are not covered.
|
|
674
|
+
* - `ignore-list`: its text is listed in `extract.literals.ignore`.
|
|
675
|
+
*/
|
|
676
|
+
type LiteralSuppressionReason = "directive" | "ignore-list";
|
|
677
|
+
/** A literal the scan found and did not report, kept so a suppression is visible rather than silent. */
|
|
678
|
+
interface SuppressedLiteral extends LiteralFinding {
|
|
679
|
+
/** Which suppression mechanism held it back. */
|
|
680
|
+
readonly reason: LiteralSuppressionReason;
|
|
681
|
+
}
|
|
682
|
+
/** Everything one untranslated-literal scan found across a project's source. */
|
|
683
|
+
interface LiteralScan {
|
|
684
|
+
/** How many source files were read and scanned. */
|
|
685
|
+
readonly scannedFiles: number;
|
|
686
|
+
/** Literals that look user-facing and were not suppressed, in file then document order. */
|
|
687
|
+
readonly findings: readonly LiteralFinding[];
|
|
688
|
+
/** Literals that were found but suppressed, each with the reason. */
|
|
689
|
+
readonly suppressed: readonly SuppressedLiteral[];
|
|
690
|
+
/**
|
|
691
|
+
* Files and directories the scan could not read to the end. A file listed here was not fully
|
|
692
|
+
* checked, so a scan with any diagnostic is never a clean one.
|
|
693
|
+
*/
|
|
694
|
+
readonly diagnostics: readonly ScanDiagnostic[];
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Zod schema accepting exactly one of the fourteen source formats verbatra can read and write. Each
|
|
699
|
+
* member names the ecosystem it round-trips, not just a file extension, because several of them share
|
|
700
|
+
* one: `i18next-json` is i18next's nested JSON with its plural key suffixes, `vue-i18n-json` is Vue
|
|
701
|
+
* I18n's JSON with its pipe-separated plural values, `next-intl-json` is next-intl's ICU-message JSON,
|
|
702
|
+
* and `ngx-translate-json` is ngx-translate's nested JSON. The remaining ten are `xliff` (the XLIFF
|
|
703
|
+
* interchange XML), `yaml` (plain nested YAML), `arb` (Flutter's Application Resource Bundle),
|
|
704
|
+
* `properties` (Java and Spring `.properties` files), `apple-strings` (Apple's flat `.strings`
|
|
705
|
+
* localization format for iOS and macOS), `apple-xcstrings` (Apple's Xcode String Catalog, a single
|
|
706
|
+
* JSON document holding every locale of a `.xcstrings` catalogue together), `android-xml` (Android's
|
|
707
|
+
* `res/values/strings.xml` resource format, including `<plurals>` and `translatable="false"` entries),
|
|
708
|
+
* `gettext-po` (GNU gettext's `.po` and `.pot` catalogs, including `msgctxt` disambiguation and
|
|
709
|
+
* `msgid_plural`/`msgstr[n]` plural forms keyed by the file's own `Plural-Forms` index count),
|
|
710
|
+
* `ini` (classic INI configuration files, one level of `[section]` headers over `key=value` lines,
|
|
711
|
+
* whose entries are addressed as `section.key`), and `resx` (.NET's XML resource format; its `<data>`
|
|
712
|
+
* elements carrying a `type` or `mimetype` attribute, and its designer metadata names, are preserved
|
|
713
|
+
* untouched rather than translated).
|
|
714
|
+
*
|
|
715
|
+
* The set is closed: adding a built-in format means adding a member here and an adapter that claims
|
|
716
|
+
* it. A format shipped by a package outside verbatra is named by a `custom:` identifier instead and
|
|
717
|
+
* never joins this set; see `FormatId`.
|
|
718
|
+
*/
|
|
719
|
+
declare const supportedFormatSchema: z.ZodEnum<{
|
|
720
|
+
"i18next-json": "i18next-json";
|
|
721
|
+
"vue-i18n-json": "vue-i18n-json";
|
|
722
|
+
"next-intl-json": "next-intl-json";
|
|
723
|
+
"ngx-translate-json": "ngx-translate-json";
|
|
724
|
+
xliff: "xliff";
|
|
725
|
+
yaml: "yaml";
|
|
726
|
+
arb: "arb";
|
|
727
|
+
properties: "properties";
|
|
728
|
+
"apple-strings": "apple-strings";
|
|
729
|
+
"apple-xcstrings": "apple-xcstrings";
|
|
730
|
+
"android-xml": "android-xml";
|
|
731
|
+
"gettext-po": "gettext-po";
|
|
732
|
+
ini: "ini";
|
|
733
|
+
resx: "resx";
|
|
734
|
+
}>;
|
|
735
|
+
/** One of the fourteen supported source formats; the inferred type of {@link supportedFormatSchema}. */
|
|
736
|
+
type SupportedFormat = z.infer<typeof supportedFormatSchema>;
|
|
737
|
+
/**
|
|
738
|
+
* A format identifier claimed by an adapter that ships outside verbatra: the reserved
|
|
739
|
+
* `custom:` prefix followed by a lowercase, hyphen-separated name, for example
|
|
740
|
+
* `custom:my-format`.
|
|
741
|
+
*
|
|
742
|
+
* No built-in format name contains a colon, so a third-party identifier can never shadow a
|
|
743
|
+
* built-in one.
|
|
744
|
+
*/
|
|
745
|
+
type CustomFormatId = `custom:${string}`;
|
|
746
|
+
/**
|
|
747
|
+
* Any format verbatra can be asked to handle: one of the built-in {@link SupportedFormat}
|
|
748
|
+
* members, or a third-party {@link CustomFormatId}.
|
|
749
|
+
*
|
|
750
|
+
* A built-in name is checked against the closed set at compile time. A `custom:` identifier is
|
|
751
|
+
* only checked for shape, because the adapter behind it lives outside this package; if no
|
|
752
|
+
* registered adapter claims it, the run fails with a structured error naming the format.
|
|
753
|
+
*/
|
|
754
|
+
type FormatId = SupportedFormat | CustomFormatId;
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* A single, format-neutral translation unit. Placeholders are supplied already extracted; core never
|
|
758
|
+
* derives them from the value, so an adapter that reads a file is the one authority on what counts as
|
|
759
|
+
* a placeholder in that format.
|
|
760
|
+
*
|
|
761
|
+
* The fields are: `key`, the entry's identifier within its namespace, non-empty and already flattened
|
|
762
|
+
* to the dotted path an adapter produced; `namespace`, the namespace the entry belongs to, empty when
|
|
763
|
+
* the format has no namespacing; `value`, the translatable string itself, which is untrusted input and
|
|
764
|
+
* may be empty; `description` and `meaning`, both optional free-form context that a provider is told to
|
|
765
|
+
* use only for disambiguation and never to translate or echo back; `placeholders`, the format's
|
|
766
|
+
* placeholder tokens in document order, used for the output integrity check and to decide which entries
|
|
767
|
+
* a placeholder-blind provider must withhold; and `isPlural`, whether the entry carries plural forms
|
|
768
|
+
* (an i18next plural key suffix, a vue-i18n pipe-separated value, or an ICU plural), which the adapter
|
|
769
|
+
* decides and which feeds the content hash.
|
|
770
|
+
*/
|
|
771
|
+
declare const translationEntrySchema: z.ZodObject<{
|
|
772
|
+
key: z.ZodString;
|
|
773
|
+
namespace: z.ZodString;
|
|
774
|
+
value: z.ZodString;
|
|
775
|
+
description: z.ZodOptional<z.ZodString>;
|
|
776
|
+
meaning: z.ZodOptional<z.ZodString>;
|
|
777
|
+
placeholders: z.ZodReadonly<z.ZodArray<z.ZodString>>;
|
|
778
|
+
isPlural: z.ZodBoolean;
|
|
779
|
+
}, z.core.$strip>;
|
|
780
|
+
/**
|
|
781
|
+
* The validated shape of one translation unit; the inferred type of {@link translationEntrySchema},
|
|
782
|
+
* made deeply readonly. See that schema for what each field carries.
|
|
783
|
+
*/
|
|
784
|
+
type TranslationEntry = Readonly<z.infer<typeof translationEntrySchema>>;
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* All translation entries for one locale and namespace, keyed by entry key. This is the neutral
|
|
788
|
+
* intermediate representation every adapter reads into and writes back out of, so nothing downstream
|
|
789
|
+
* of an adapter parses a format's own syntax.
|
|
790
|
+
*/
|
|
791
|
+
interface LocaleResource {
|
|
792
|
+
/** The locale these entries belong to (for example, "en" or "de"). */
|
|
793
|
+
readonly locale: string;
|
|
794
|
+
/** The namespace these entries belong to; empty when the format has no namespacing. */
|
|
795
|
+
readonly namespace: string;
|
|
796
|
+
/** The source format the resource came from, retained for round-trip fidelity on write. */
|
|
797
|
+
readonly format: FormatId;
|
|
798
|
+
/** Entries addressable by key, in the order the source file defined them. */
|
|
799
|
+
readonly entries: ReadonlyMap<string, TranslationEntry>;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Result of comparing source placeholders against translated-output placeholders as MULTISETS
|
|
804
|
+
* (counts matter, so dropping one of two occurrences of the same token is still a miss). Always
|
|
805
|
+
* returned, never thrown: a mismatch is data the caller decides what to do about, not a failure.
|
|
806
|
+
*/
|
|
807
|
+
interface PlaceholderIntegrityResult {
|
|
808
|
+
/** True when nothing is missing or extra: the multisets match, regardless of order. */
|
|
809
|
+
readonly matches: boolean;
|
|
810
|
+
/** Occurrences present in source but absent from the translation, one per dropped occurrence. Sorted. */
|
|
811
|
+
readonly missing: readonly string[];
|
|
812
|
+
/** Occurrences present in the translation but absent from source, one per surplus occurrence. Sorted. */
|
|
813
|
+
readonly extra: readonly string[];
|
|
814
|
+
/** Same multiset present in both, but in a different order. Never true alongside a mismatch. */
|
|
815
|
+
readonly reordered: boolean;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* The result of reading a file into core's intermediate representation. The two diagnostic lists are
|
|
820
|
+
* reported as data rather than thrown, so one bad leaf never fails a whole read.
|
|
821
|
+
*/
|
|
822
|
+
interface ReadResult {
|
|
823
|
+
/** The parsed locale resource. */
|
|
824
|
+
readonly resource: LocaleResource;
|
|
825
|
+
/**
|
|
826
|
+
* Keys whose values are invalid for the format's message syntax. Empty for a format with no
|
|
827
|
+
* message-syntax check, which treats every value as valid.
|
|
828
|
+
*/
|
|
829
|
+
readonly invalidIcuKeys: readonly string[];
|
|
830
|
+
/**
|
|
831
|
+
* Keys or dotted paths that were present in the source but excluded from translation: a stray
|
|
832
|
+
* non-string leaf (a number, boolean, or null) for a tree-based format, or a key the format's own
|
|
833
|
+
* metadata marks as not translatable, such as an Android `translatable="false"` resource. These
|
|
834
|
+
* are never translated, hashed, diffed, or checked for placeholder or ICU integrity.
|
|
835
|
+
*
|
|
836
|
+
* Reporting them is the adapter's choice, so an empty list means this adapter reported none, not
|
|
837
|
+
* necessarily that the file carried none. A tree-based adapter reports its non-string leaves
|
|
838
|
+
* automatically; a flat adapter reports by returning `{ entries, excludedLeafPaths }` from
|
|
839
|
+
* `parseEntries` instead of a bare map.
|
|
840
|
+
*
|
|
841
|
+
* Whether one survives a later rewrite of the same file depends on the adapter: an adapter that
|
|
842
|
+
* reconstructs the whole file from `resource.entries` alone drops it, while an adapter that
|
|
843
|
+
* patches only the entries it touches into the existing document (for example `apple-xcstrings`)
|
|
844
|
+
* preserves it verbatim.
|
|
845
|
+
*/
|
|
846
|
+
readonly excludedLeafPaths: readonly string[];
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* The contract every format adapter implements. A new format attaches by implementing this interface
|
|
850
|
+
* and registering it in an {@link AdapterRegistry}.
|
|
851
|
+
*
|
|
852
|
+
* Implement it through one of the two shared factories rather than from scratch:
|
|
853
|
+
* `createTreeFileAdapter` for a nested-tree format and `createFlatFileAdapter` for a flat
|
|
854
|
+
* key/value format. A format that fits neither shape implements this interface directly.
|
|
855
|
+
*
|
|
856
|
+
* An adapter shipped outside verbatra names itself with a `custom:` identifier and is attached by
|
|
857
|
+
* registering it in a registry the caller hands to the SDK. It is ordinary trusted code: verbatra
|
|
858
|
+
* does not sandbox it.
|
|
859
|
+
*/
|
|
860
|
+
interface FormatAdapter {
|
|
861
|
+
/**
|
|
862
|
+
* The single format this adapter handles: one of core's built-in `SupportedFormat` members, or a
|
|
863
|
+
* third-party `custom:` identifier for an adapter that ships outside verbatra.
|
|
864
|
+
*/
|
|
865
|
+
readonly format: FormatId;
|
|
866
|
+
/**
|
|
867
|
+
* Detect whether this adapter can handle a file, by path extension and an optional content sample.
|
|
868
|
+
* Best-effort and side-effect-free; it reads nothing from disk.
|
|
869
|
+
*
|
|
870
|
+
* @param filePath - The path of the file under consideration.
|
|
871
|
+
* @param sample - An optional leading content sample to aid detection.
|
|
872
|
+
* @returns True if this adapter could handle the file. Several adapters may return true for the same
|
|
873
|
+
* file (all JSON adapters claim `.json`); the caller disambiguates by explicit format.
|
|
874
|
+
*/
|
|
875
|
+
canHandle(filePath: string, sample?: string): boolean;
|
|
876
|
+
/**
|
|
877
|
+
* Read a file into a {@link LocaleResource} plus its message-validity result.
|
|
878
|
+
*
|
|
879
|
+
* @param filePath - The file to read.
|
|
880
|
+
* @param locale - The locale to tag the resource with.
|
|
881
|
+
* @returns The parsed resource and the keys whose values are invalid for the format.
|
|
882
|
+
* @throws `AdapterError` when the content is malformed, oversized, or structurally invalid
|
|
883
|
+
* (the implementation names the specific codes). A missing or unopenable path instead rejects with
|
|
884
|
+
* the underlying filesystem error.
|
|
885
|
+
*/
|
|
886
|
+
read(filePath: string, locale: string): Promise<ReadResult>;
|
|
887
|
+
/**
|
|
888
|
+
* Write a {@link LocaleResource} back to a file, preserving key order and structure, atomically.
|
|
889
|
+
*
|
|
890
|
+
* @param resource - The resource to serialize.
|
|
891
|
+
* @param filePath - The destination file.
|
|
892
|
+
* @throws `AdapterError` if the resource cannot be represented in the format; rejects with the
|
|
893
|
+
* underlying filesystem error on a write failure.
|
|
894
|
+
*/
|
|
895
|
+
write(resource: LocaleResource, filePath: string): Promise<void>;
|
|
896
|
+
/**
|
|
897
|
+
* Extract the format's placeholder tokens from a single value, resolving nothing.
|
|
898
|
+
*
|
|
899
|
+
* @param value - The translatable string to scan.
|
|
900
|
+
* @returns The placeholder tokens found, in document order. Does not throw.
|
|
901
|
+
*/
|
|
902
|
+
extractPlaceholders(value: string): readonly string[];
|
|
903
|
+
/**
|
|
904
|
+
* Validate a single value against the format's message syntax before it is written. Only the
|
|
905
|
+
* ICU-message formats carry a real check today (next-intl and ARB); every other adapter has no
|
|
906
|
+
* message syntax to violate and returns true for any value.
|
|
907
|
+
*
|
|
908
|
+
* @param value - The candidate translated value to validate.
|
|
909
|
+
* @returns True when the value is valid for the format's message syntax. Does not throw; an
|
|
910
|
+
* unparseable value returns false.
|
|
911
|
+
*/
|
|
912
|
+
validateMessage(value: string): boolean;
|
|
913
|
+
/**
|
|
914
|
+
* Optional whole-value placeholder comparison, used by callers instead of independently extracting
|
|
915
|
+
* each side's placeholders with {@link extractPlaceholders} and diffing the flat lists. Both
|
|
916
|
+
* adapter factories accept one, under the `comparePlaceholders` option. Among the shipped
|
|
917
|
+
* adapters two families define one, for different reasons:
|
|
918
|
+
*
|
|
919
|
+
* - The ICU formats (next-intl and ARB) compare branch by branch, because flattening a
|
|
920
|
+
* plural/select value loses which branch a placeholder came from.
|
|
921
|
+
* - The double-brace formats (i18next, ngx-translate, and YAML) add a one-directional
|
|
922
|
+
* single-brace check on top of their flat multiset comparison: a `{name}`-shaped token present
|
|
923
|
+
* in the translated value but absent from the source is reported as `extra`. Single-brace text
|
|
924
|
+
* is ordinary literal content under these formats' default delimiters, so it is never required
|
|
925
|
+
* to survive a translation, but inventing one is never a legitimate translation either.
|
|
926
|
+
*
|
|
927
|
+
* Absent for the remaining adapters, which are compared via `extractPlaceholders` plus a flat
|
|
928
|
+
* multiset check.
|
|
929
|
+
*
|
|
930
|
+
* @param sourceValue - The source value.
|
|
931
|
+
* @param targetValue - The translated value to check against it.
|
|
932
|
+
* @returns The merged placeholder-integrity result. Does not throw.
|
|
933
|
+
*/
|
|
934
|
+
comparePlaceholders?(sourceValue: string, targetValue: string): PlaceholderIntegrityResult;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* The outcome of a size-bounded read through the {@link AdapterFs} port. A path that is not a file,
|
|
939
|
+
* or a file larger than the caller's limit, is reported as a state rather than thrown, so an adapter
|
|
940
|
+
* can treat "no file yet" and "too large to trust" as ordinary branches.
|
|
941
|
+
*/
|
|
942
|
+
type BoundedReadOutcome = {
|
|
943
|
+
readonly kind: "ok";
|
|
944
|
+
readonly content: string;
|
|
945
|
+
} | {
|
|
946
|
+
readonly kind: "not-a-file";
|
|
947
|
+
} | {
|
|
948
|
+
readonly kind: "too-large";
|
|
949
|
+
};
|
|
950
|
+
/**
|
|
951
|
+
* The file-system port every format adapter reads and writes through. An adapter that takes this
|
|
952
|
+
* port instead of importing `node:fs` can be tested against an in-memory implementation and stays
|
|
953
|
+
* honest about every path it touches.
|
|
954
|
+
*
|
|
955
|
+
* This is the supported path for an adapter shipped outside verbatra, and the one verbatra hands it.
|
|
956
|
+
* It is a convention, not a sandbox: nothing stops third-party code from reaching the file system
|
|
957
|
+
* another way.
|
|
958
|
+
*/
|
|
959
|
+
interface AdapterFs {
|
|
960
|
+
/**
|
|
961
|
+
* Read a UTF-8 text file, refusing anything larger than the caller's limit.
|
|
962
|
+
*
|
|
963
|
+
* @param path - The file to read.
|
|
964
|
+
* @param maxBytes - The largest file size to read; a larger file yields `too-large` and is not read.
|
|
965
|
+
* @returns The file's content, or the state that prevented reading it.
|
|
966
|
+
*/
|
|
967
|
+
readBounded(path: string, maxBytes: number): Promise<BoundedReadOutcome>;
|
|
968
|
+
/**
|
|
969
|
+
* Write a UTF-8 text file atomically, creating missing parent directories.
|
|
970
|
+
*
|
|
971
|
+
* @param path - The destination file.
|
|
972
|
+
* @param data - The content to write.
|
|
973
|
+
* @returns Nothing, once the content is durably in place under `path`.
|
|
974
|
+
*/
|
|
975
|
+
writeFileAtomic(path: string, data: string): Promise<void>;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* The {@link AdapterFs} implementation backed by the real file system, and the default every adapter
|
|
979
|
+
* factory uses when no port is supplied. It writes through a temporary file and a rename, so an
|
|
980
|
+
* interrupted write never leaves a half-finished locale file.
|
|
981
|
+
*
|
|
982
|
+
* @example
|
|
983
|
+
* ```ts
|
|
984
|
+
* const adapter = createFlatFileAdapter({ ...options, fs: nodeAdapterFs });
|
|
985
|
+
* ```
|
|
986
|
+
*/
|
|
987
|
+
declare const nodeAdapterFs: AdapterFs;
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Outcome of resolving an adapter for a file. Structured, never thrown: an unresolvable file is a
|
|
991
|
+
* status the caller branches on, not an exception.
|
|
992
|
+
*
|
|
993
|
+
* `resolved` carries the single {@link FormatAdapter} to use. `no-match` means nothing claimed the
|
|
994
|
+
* file, and reports the formats that were tried (every registered format for a detection attempt, or
|
|
995
|
+
* just the one requested when an explicit format was given). `ambiguous` means more than one adapter
|
|
996
|
+
* claimed the file, which is routine since every JSON adapter claims `.json`; it reports the competing
|
|
997
|
+
* candidates instead of guessing, and the caller resolves it by naming a format explicitly.
|
|
998
|
+
*/
|
|
999
|
+
type AdapterResolution = {
|
|
1000
|
+
/** Exactly one adapter claimed the file. */
|
|
1001
|
+
readonly status: "resolved";
|
|
1002
|
+
/** The adapter to read and write the file with. */
|
|
1003
|
+
readonly adapter: FormatAdapter;
|
|
1004
|
+
} | {
|
|
1005
|
+
/** No registered adapter claimed the file. */
|
|
1006
|
+
readonly status: "no-match";
|
|
1007
|
+
/** The file that could not be matched. */
|
|
1008
|
+
readonly filePath: string;
|
|
1009
|
+
/**
|
|
1010
|
+
* The formats that were tried: every registered format for a detection attempt, or just the
|
|
1011
|
+
* one requested when an explicit format was given.
|
|
1012
|
+
*/
|
|
1013
|
+
readonly triedFormats: readonly FormatId[];
|
|
1014
|
+
} | {
|
|
1015
|
+
/** More than one adapter claimed the file, so detection cannot choose between them. */
|
|
1016
|
+
readonly status: "ambiguous";
|
|
1017
|
+
/** The file that several adapters claimed. */
|
|
1018
|
+
readonly filePath: string;
|
|
1019
|
+
/** The competing formats. Resolve the ambiguity by naming one of them explicitly. */
|
|
1020
|
+
readonly candidates: readonly FormatId[];
|
|
1021
|
+
};
|
|
1022
|
+
/** Options for {@link AdapterRegistry.resolve}: select a format explicitly, or aid detection. */
|
|
1023
|
+
interface ResolveOptions {
|
|
1024
|
+
/** A leading content sample to aid detection. Ignored when `format` is given. */
|
|
1025
|
+
readonly sample?: string;
|
|
1026
|
+
/** Bypass detection and select this format explicitly. */
|
|
1027
|
+
readonly format?: FormatId;
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Holds the registered adapters and resolves one for a file. Open for extension: adapters attach
|
|
1031
|
+
* through {@link AdapterRegistry.register} without changing resolution logic, and an unresolvable
|
|
1032
|
+
* file is a structured status rather than an exception.
|
|
1033
|
+
*
|
|
1034
|
+
* @example
|
|
1035
|
+
* ```ts
|
|
1036
|
+
* const registry = createDefaultRegistry().register(createTomlAdapter());
|
|
1037
|
+
* const resolution = registry.resolve("locales/de.toml");
|
|
1038
|
+
* if (resolution.status === "resolved") {
|
|
1039
|
+
* const { resource } = await resolution.adapter.read("locales/de.toml", "de");
|
|
1040
|
+
* }
|
|
1041
|
+
* ```
|
|
1042
|
+
*/
|
|
1043
|
+
declare class AdapterRegistry {
|
|
1044
|
+
private readonly adapters;
|
|
1045
|
+
/**
|
|
1046
|
+
* Register an adapter. Registration order is the order detection consults them.
|
|
1047
|
+
*
|
|
1048
|
+
* A format identifier may be registered only once: a second adapter claiming an identifier the
|
|
1049
|
+
* registry already holds is rejected, so neither a third-party adapter nor a second copy of one
|
|
1050
|
+
* can silently shadow what is already there.
|
|
1051
|
+
*
|
|
1052
|
+
* An adapter whose format is a third-party `custom:` identifier is wrapped so that an unexpected
|
|
1053
|
+
* throw from any of its contract methods surfaces as an {@link AdapterError} naming the format.
|
|
1054
|
+
* Built-in adapters are registered as they are.
|
|
1055
|
+
*
|
|
1056
|
+
* @param adapter - The adapter to add.
|
|
1057
|
+
* @returns This registry, for chaining.
|
|
1058
|
+
* @throws `AdapterError` with code `INVALID_FORMAT_ID` when the format carries the `custom:`
|
|
1059
|
+
* prefix but is not a well-formed identifier. Such an adapter is refused rather than registered
|
|
1060
|
+
* without the containment wrapper, which would silently lose its failure attribution.
|
|
1061
|
+
* @throws `AdapterError` with code `DUPLICATE_FORMAT` when the registry already holds an adapter
|
|
1062
|
+
* for this format.
|
|
1063
|
+
*
|
|
1064
|
+
* @example
|
|
1065
|
+
* ```ts
|
|
1066
|
+
* const registry = createDefaultRegistry().register(createTomlAdapter());
|
|
1067
|
+
* await translate({ config }, { adapterRegistry: registry });
|
|
1068
|
+
* ```
|
|
1069
|
+
*/
|
|
1070
|
+
register(adapter: FormatAdapter): this;
|
|
1071
|
+
private formats;
|
|
1072
|
+
private resolveByFormat;
|
|
1073
|
+
private resolveByDetection;
|
|
1074
|
+
/**
|
|
1075
|
+
* Resolve the adapter for a file, by explicit format when given, otherwise by detection.
|
|
1076
|
+
*
|
|
1077
|
+
* @param filePath - The file to resolve an adapter for.
|
|
1078
|
+
* @param options - `format` selects explicitly and skips detection; `sample` aids detection.
|
|
1079
|
+
* @returns A structured {@link AdapterResolution}: `resolved`, `no-match`, or `ambiguous`. An
|
|
1080
|
+
* unresolvable file is a status, not an exception.
|
|
1081
|
+
* @throws `AdapterError` with code `ADAPTER_FAILED` when a registered third-party adapter throws
|
|
1082
|
+
* from its own `canHandle` during detection. Resolution itself raises nothing.
|
|
1083
|
+
*/
|
|
1084
|
+
resolve(filePath: string, options?: ResolveOptions): AdapterResolution;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Build a registry holding every format adapter verbatra ships, in detection order. This is the
|
|
1089
|
+
* starting point for adding an adapter of your own: register it on the returned registry and hand
|
|
1090
|
+
* that registry to an SDK flow as its `adapterRegistry` dependency.
|
|
1091
|
+
*
|
|
1092
|
+
* @param fs - The file-system port every built-in adapter reads and writes through. Defaults to
|
|
1093
|
+
* {@link nodeAdapterFs}.
|
|
1094
|
+
* @returns A registry of the built-in adapters, ready for further registrations.
|
|
1095
|
+
*
|
|
1096
|
+
* @example
|
|
1097
|
+
* ```ts
|
|
1098
|
+
* const registry = createDefaultRegistry().register(createTomlAdapter());
|
|
1099
|
+
* await translate({ config }, { adapterRegistry: registry });
|
|
1100
|
+
* ```
|
|
1101
|
+
*/
|
|
1102
|
+
declare function createDefaultRegistry(fs?: AdapterFs): AdapterRegistry;
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* The stable, secret-free codes an {@link AdapterError} carries.
|
|
1106
|
+
*
|
|
1107
|
+
* The first seven describe a file verbatra could not handle: `INVALID_JSON`, `INVALID_YAML` and
|
|
1108
|
+
* `INVALID_XML` for malformed syntax, `INVALID_STRUCTURE` for a parseable file of the wrong shape,
|
|
1109
|
+
* `MAX_DEPTH_EXCEEDED` and `INPUT_TOO_LARGE` for the read caps, and `MIXED_STRUCTURE` for a file
|
|
1110
|
+
* that mixes flat and nested keys where the format forbids it.
|
|
1111
|
+
*
|
|
1112
|
+
* The last three describe an adapter rather than a file. `ADAPTER_FAILED` attributes an unexpected
|
|
1113
|
+
* throw to the third-party adapter it came from, so a plugin defect never surfaces as an
|
|
1114
|
+
* unattributed verbatra stack trace. `DUPLICATE_FORMAT` rejects registering a second adapter for a
|
|
1115
|
+
* format identifier a registry already holds. `INVALID_FORMAT_ID` rejects registering an adapter
|
|
1116
|
+
* whose `custom:` identifier is malformed, which would otherwise be registered without the
|
|
1117
|
+
* containment wrapper and silently lose its failure attribution.
|
|
1118
|
+
*
|
|
1119
|
+
* The set is closed. A third-party adapter raises `INVALID_STRUCTURE` (or another member that fits)
|
|
1120
|
+
* rather than minting its own code.
|
|
1121
|
+
*/
|
|
1122
|
+
type AdapterErrorCode = "INVALID_JSON" | "INVALID_YAML" | "INVALID_XML" | "INVALID_STRUCTURE" | "MAX_DEPTH_EXCEEDED" | "INPUT_TOO_LARGE" | "MIXED_STRUCTURE" | "ADAPTER_FAILED" | "DUPLICATE_FORMAT" | "INVALID_FORMAT_ID";
|
|
1123
|
+
/**
|
|
1124
|
+
* A structured, secret-free failure raised by a format adapter or by the adapter registry. Catch it
|
|
1125
|
+
* to branch on {@link AdapterErrorCode} instead of matching on message text.
|
|
1126
|
+
*
|
|
1127
|
+
* @example
|
|
1128
|
+
* ```ts
|
|
1129
|
+
* try {
|
|
1130
|
+
* await adapter.read("locales/de.json", "de");
|
|
1131
|
+
* } catch (error) {
|
|
1132
|
+
* if (error instanceof AdapterError && error.code === "INVALID_JSON") {
|
|
1133
|
+
* console.error(error.message);
|
|
1134
|
+
* }
|
|
1135
|
+
* }
|
|
1136
|
+
* ```
|
|
1137
|
+
*/
|
|
1138
|
+
declare class AdapterError extends Error {
|
|
1139
|
+
/** The stable code for this failure. */
|
|
1140
|
+
readonly code: AdapterErrorCode;
|
|
1141
|
+
/**
|
|
1142
|
+
* @param code - The stable code for this failure.
|
|
1143
|
+
* @param message - A human-readable description. Never include a secret or a file's contents.
|
|
1144
|
+
*/
|
|
1145
|
+
constructor(code: AdapterErrorCode, message: string);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/** A value in a locale tree about to be serialized, with key order preserved at every level. */
|
|
1149
|
+
type OrderedValue = string | number | boolean | null | OrderedRecord | readonly OrderedValue[];
|
|
1150
|
+
/** A locale tree about to be serialized, keyed in the order it will be written out. */
|
|
1151
|
+
type OrderedRecord = ReadonlyMap<string, OrderedValue>;
|
|
1152
|
+
|
|
1153
|
+
/** A terminal value in a parsed locale tree: everything that is not a nested object. */
|
|
1154
|
+
type JsonLeaf = string | number | boolean | null;
|
|
1155
|
+
/** Any node in a parsed locale tree: a leaf, or a nested record of further nodes. */
|
|
1156
|
+
type JsonTree = JsonLeaf | JsonRecord;
|
|
1157
|
+
/** A parsed locale tree node, keyed in the order the source document defined its keys. */
|
|
1158
|
+
type JsonRecord = ReadonlyMap<string, JsonTree>;
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* Finds a format's placeholder tokens in one translatable value, in document order. Resolves
|
|
1162
|
+
* nothing and never throws; a value with no placeholders yields an empty list.
|
|
1163
|
+
*/
|
|
1164
|
+
type ExtractPlaceholders = (value: string) => readonly string[];
|
|
1165
|
+
/**
|
|
1166
|
+
* Reports the keys whose values are invalid for a format's message syntax, given every entry read
|
|
1167
|
+
* from one file. Only the ICU-message formats need one; a format with no message syntax omits it.
|
|
1168
|
+
*/
|
|
1169
|
+
type ComputeInvalidIcuKeys = (entries: ReadonlyMap<string, TranslationEntry>) => readonly string[];
|
|
1170
|
+
/**
|
|
1171
|
+
* Decides whether one candidate value is valid for a format's message syntax before it is written.
|
|
1172
|
+
* Never throws: an unparseable value returns false.
|
|
1173
|
+
*/
|
|
1174
|
+
type ValidateMessage = (value: string) => boolean;
|
|
1175
|
+
/**
|
|
1176
|
+
* Compares a source value against its translation as whole values rather than as two flat token
|
|
1177
|
+
* lists, for a format whose structure (an ICU plural or select branch, for example) would be lost by
|
|
1178
|
+
* flattening. Never throws: a mismatch is data.
|
|
1179
|
+
*/
|
|
1180
|
+
type ComparePlaceholders = (sourceValue: string, targetValue: string) => PlaceholderIntegrityResult;
|
|
1181
|
+
/**
|
|
1182
|
+
* Rejects a parsed tree that is structurally wrong for a format, by throwing an `AdapterError`.
|
|
1183
|
+
* Returns nothing when the tree is acceptable.
|
|
1184
|
+
*/
|
|
1185
|
+
type ValidateTree = (tree: JsonRecord) => void;
|
|
1186
|
+
/**
|
|
1187
|
+
* Decides whether a leading content sample looks like this format, so two adapters claiming the same
|
|
1188
|
+
* file extension can still be told apart. Consulted only when a sample is available.
|
|
1189
|
+
*/
|
|
1190
|
+
type Sniff = (sample: string) => boolean;
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* What a flat format's `parseEntries` returns when it has content to report as skipped. Returning a
|
|
1194
|
+
* bare map instead is equivalent to returning one of these with no skipped paths.
|
|
1195
|
+
*/
|
|
1196
|
+
interface FlatParseResult {
|
|
1197
|
+
/** The entries read from the file, keyed by entry key, in document order. */
|
|
1198
|
+
readonly entries: Map<string, TranslationEntry>;
|
|
1199
|
+
/**
|
|
1200
|
+
* Keys the file carried but that are excluded from translation, for example one the format's own
|
|
1201
|
+
* metadata marks as not translatable. Omit it when the format has no such concept.
|
|
1202
|
+
*/
|
|
1203
|
+
readonly excludedLeafPaths?: readonly string[];
|
|
1204
|
+
}
|
|
1205
|
+
/** What a flat format's `parseEntries` may return: the entries alone, or entries plus diagnostics. */
|
|
1206
|
+
type FlatParseOutcome = Map<string, TranslationEntry> | FlatParseResult;
|
|
1207
|
+
/** Everything {@link createFlatFileAdapter} needs to turn a flat key/value format into an adapter. */
|
|
1208
|
+
interface FlatFileAdapterOptions {
|
|
1209
|
+
/** The format this adapter claims: a built-in name, or a `custom:` identifier of your own. */
|
|
1210
|
+
readonly format: FormatId;
|
|
1211
|
+
/** The lowercase file extensions this format owns, each including the leading dot. */
|
|
1212
|
+
readonly extensions: readonly string[];
|
|
1213
|
+
/**
|
|
1214
|
+
* Optional content check, consulted when a sample is available, so this adapter does not claim
|
|
1215
|
+
* every file that merely shares one of its extensions.
|
|
1216
|
+
*/
|
|
1217
|
+
readonly sniff?: Sniff;
|
|
1218
|
+
/**
|
|
1219
|
+
* Parse one file's text into entries keyed by entry key, in document order. Throw an
|
|
1220
|
+
* `AdapterError` for content this format cannot represent; anything else thrown is reported as a
|
|
1221
|
+
* structural failure.
|
|
1222
|
+
*/
|
|
1223
|
+
readonly parseEntries: (content: string, namespace: string, filePath: string, fs: AdapterFs) => FlatParseOutcome | Promise<FlatParseOutcome>;
|
|
1224
|
+
/**
|
|
1225
|
+
* Render entries back to the format's text, preserving key order. Receives the destination path
|
|
1226
|
+
* and the port, for a format that has to consult the existing file to write in place.
|
|
1227
|
+
*/
|
|
1228
|
+
readonly serializeEntries: (entries: ReadonlyMap<string, TranslationEntry>, filePath: string, fs: AdapterFs) => Promise<string> | string;
|
|
1229
|
+
/** Find this format's placeholder tokens in one value. */
|
|
1230
|
+
readonly extractPlaceholders: ExtractPlaceholders;
|
|
1231
|
+
/** Optional message-syntax check applied to a candidate value before it is written. */
|
|
1232
|
+
readonly validateMessage?: ValidateMessage;
|
|
1233
|
+
/** Optional per-file report of the keys whose values are invalid for this format's message syntax. */
|
|
1234
|
+
readonly computeInvalidIcuKeys?: ComputeInvalidIcuKeys;
|
|
1235
|
+
/**
|
|
1236
|
+
* Optional whole-value placeholder comparison, for a format whose structure a flat token list
|
|
1237
|
+
* would lose. Omitted adapters are compared by `extractPlaceholders` plus a flat multiset check.
|
|
1238
|
+
*/
|
|
1239
|
+
readonly comparePlaceholders?: ComparePlaceholders;
|
|
1240
|
+
/** The file-system port to read and write through. Defaults to `nodeAdapterFs`. */
|
|
1241
|
+
readonly fs?: AdapterFs;
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Build a {@link FormatAdapter} for a flat key/value format: one where every entry is addressed by a
|
|
1245
|
+
* single key with no nesting, such as Java `.properties` or Android `strings.xml`. The factory
|
|
1246
|
+
* supplies the bounded read, the atomic write, extension detection and the structured error
|
|
1247
|
+
* handling; you supply only the format's own parsing and serialization.
|
|
1248
|
+
*
|
|
1249
|
+
* @param options - The format's parsing, serialization and detection behaviour.
|
|
1250
|
+
* @returns A complete adapter, ready to register on an `AdapterRegistry`.
|
|
1251
|
+
* @throws Nothing itself. The returned adapter's `read` and `write` raise `AdapterError` for content
|
|
1252
|
+
* this format cannot represent, and reject with the underlying filesystem error otherwise.
|
|
1253
|
+
*
|
|
1254
|
+
* @example
|
|
1255
|
+
* ```ts
|
|
1256
|
+
* const tomlAdapter = createFlatFileAdapter({
|
|
1257
|
+
* format: "custom:toml",
|
|
1258
|
+
* extensions: [".toml"],
|
|
1259
|
+
* parseEntries: (content, namespace) => parseToml(content, namespace),
|
|
1260
|
+
* serializeEntries: (entries) => serializeToml(entries),
|
|
1261
|
+
* extractPlaceholders: (value) => [...value.matchAll(/\{\w+\}/g)].map((m) => m[0]),
|
|
1262
|
+
* });
|
|
1263
|
+
* ```
|
|
1264
|
+
*/
|
|
1265
|
+
declare function createFlatFileAdapter(options: FlatFileAdapterOptions): FormatAdapter;
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* Derives the per-entry facts a format decides for itself from one key and its string value: the
|
|
1269
|
+
* placeholder tokens the value carries, and whether the key is one plural form of a set.
|
|
1270
|
+
*/
|
|
1271
|
+
type DeriveEntry = (key: string, value: string) => {
|
|
1272
|
+
readonly placeholders: readonly string[];
|
|
1273
|
+
readonly isPlural: boolean;
|
|
1274
|
+
};
|
|
1275
|
+
/**
|
|
1276
|
+
* How a dotted key in the source document is addressed. `literal-leaf` keeps `"a.b"` as one key
|
|
1277
|
+
* whose name contains a dot; `path-notation` reads it as the nested path `a` then `b`.
|
|
1278
|
+
*/
|
|
1279
|
+
type KeyMode = "literal-leaf" | "path-notation";
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* Builds the tree to serialize from the entries about to be written, for a format that patches an
|
|
1283
|
+
* existing document rather than rebuilding it from the entries alone.
|
|
1284
|
+
*/
|
|
1285
|
+
type BuildWriteTree = (entries: ReadonlyMap<string, TranslationEntry>, filePath: string, fs: AdapterFs) => OrderedRecord | Promise<OrderedRecord>;
|
|
1286
|
+
/**
|
|
1287
|
+
* Pulls per-key translator descriptions out of the raw file text, for a format that carries them as
|
|
1288
|
+
* metadata alongside the values.
|
|
1289
|
+
*/
|
|
1290
|
+
type DeriveDescriptions = (content: string) => ReadonlyMap<string, string>;
|
|
1291
|
+
/** Everything {@link createTreeFileAdapter} needs to turn a nested-tree format into an adapter. */
|
|
1292
|
+
interface TreeFileAdapterOptions {
|
|
1293
|
+
/** The format this adapter claims: a built-in name, or a `custom:` identifier of your own. */
|
|
1294
|
+
readonly format: FormatId;
|
|
1295
|
+
/** The lowercase file extensions this format owns, each including the leading dot. */
|
|
1296
|
+
readonly extensions: readonly string[];
|
|
1297
|
+
/**
|
|
1298
|
+
* Optional content check, consulted when a sample is available, so this adapter does not claim
|
|
1299
|
+
* every file that merely shares one of its extensions.
|
|
1300
|
+
*/
|
|
1301
|
+
readonly sniff?: Sniff;
|
|
1302
|
+
/** Parse one file's text into a nested tree, preserving key order at every level. */
|
|
1303
|
+
readonly parse: (content: string) => JsonRecord;
|
|
1304
|
+
/** Render a nested tree back to the format's text, preserving key order at every level. */
|
|
1305
|
+
readonly serialize: (tree: OrderedRecord) => string;
|
|
1306
|
+
/** Derive the placeholder tokens and the plural flag for one leaf. */
|
|
1307
|
+
readonly deriveEntry: DeriveEntry;
|
|
1308
|
+
/** Find this format's placeholder tokens in one value. */
|
|
1309
|
+
readonly extractPlaceholders: ExtractPlaceholders;
|
|
1310
|
+
/** Optional per-file report of the keys whose values are invalid for this format's message syntax. */
|
|
1311
|
+
readonly computeInvalidIcuKeys?: ComputeInvalidIcuKeys;
|
|
1312
|
+
/** Optional message-syntax check applied to a candidate value before it is written. */
|
|
1313
|
+
readonly validateMessage?: ValidateMessage;
|
|
1314
|
+
/** Optional structural check on the parsed tree, throwing an `AdapterError` for a shape this format forbids. */
|
|
1315
|
+
readonly validateTree?: ValidateTree;
|
|
1316
|
+
/** Optional override for how the tree to write is assembled, for a format that patches the existing document. */
|
|
1317
|
+
readonly buildWriteTree?: BuildWriteTree;
|
|
1318
|
+
/** How a dotted key is addressed. Defaults to `"literal-leaf"`. */
|
|
1319
|
+
readonly keyMode?: KeyMode;
|
|
1320
|
+
/** Optional whole-value placeholder comparison, for a format whose structure flattening would lose. */
|
|
1321
|
+
readonly comparePlaceholders?: ComparePlaceholders;
|
|
1322
|
+
/** Optional extraction of per-key translator descriptions from the raw file text. */
|
|
1323
|
+
readonly deriveDescriptions?: DeriveDescriptions;
|
|
1324
|
+
/** The file-system port to read and write through. Defaults to `nodeAdapterFs`. */
|
|
1325
|
+
readonly fs?: AdapterFs;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Build a {@link FormatAdapter} for a nested-tree format: one whose entries live at paths through
|
|
1329
|
+
* nested objects, such as i18next JSON or YAML. The factory supplies the bounded read, the atomic
|
|
1330
|
+
* write, tree flattening and unflattening, extension detection and the structured error handling;
|
|
1331
|
+
* you supply only the format's own parsing, serialization and per-leaf facts.
|
|
1332
|
+
*
|
|
1333
|
+
* @param options - The format's parsing, serialization, detection and per-entry behaviour.
|
|
1334
|
+
* @returns A complete adapter, ready to register on an `AdapterRegistry`.
|
|
1335
|
+
* @throws Nothing itself. The returned adapter's `read` and `write` raise `AdapterError` for content
|
|
1336
|
+
* this format cannot represent, and reject with the underlying filesystem error otherwise.
|
|
1337
|
+
*
|
|
1338
|
+
* @example
|
|
1339
|
+
* ```ts
|
|
1340
|
+
* const hoconAdapter = createTreeFileAdapter({
|
|
1341
|
+
* format: "custom:hocon",
|
|
1342
|
+
* extensions: [".conf"],
|
|
1343
|
+
* parse: (content) => parseHocon(content),
|
|
1344
|
+
* serialize: (tree) => serializeHocon(tree),
|
|
1345
|
+
* deriveEntry: (_key, value) => ({ placeholders: tokensIn(value), isPlural: false }),
|
|
1346
|
+
* extractPlaceholders: (value) => tokensIn(value),
|
|
1347
|
+
* });
|
|
1348
|
+
* ```
|
|
1349
|
+
*/
|
|
1350
|
+
declare function createTreeFileAdapter(options: TreeFileAdapterOptions): FormatAdapter;
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* The outcome of a size-bounded text read. A file that is absent or larger than the caller's limit
|
|
1354
|
+
* is reported as a state rather than thrown, so a caller can treat "no file yet" as normal and an
|
|
1355
|
+
* oversized file as a distinct, actionable condition.
|
|
1356
|
+
*/
|
|
1357
|
+
type BoundedFileRead = {
|
|
1358
|
+
/** The file was read in full. */
|
|
1359
|
+
readonly kind: "ok";
|
|
1360
|
+
/** The file's content, decoded as UTF-8. */
|
|
1361
|
+
readonly content: string;
|
|
1362
|
+
} | {
|
|
1363
|
+
/** No readable file exists at the path. */
|
|
1364
|
+
readonly kind: "missing";
|
|
1365
|
+
} | {
|
|
1366
|
+
/** The file exists but exceeds the requested byte limit, so nothing was read. */
|
|
1367
|
+
readonly kind: "too-large";
|
|
1368
|
+
};
|
|
1369
|
+
/**
|
|
1370
|
+
* The outcome of a size-bounded binary read, used for interchange workbooks. Mirrors
|
|
1371
|
+
* {@link BoundedFileRead} but yields raw bytes rather than decoded text.
|
|
1372
|
+
*/
|
|
1373
|
+
type BoundedBytesRead = {
|
|
1374
|
+
/** The file was read in full. */
|
|
1375
|
+
readonly kind: "ok";
|
|
1376
|
+
/** The file's raw bytes. */
|
|
1377
|
+
readonly bytes: Uint8Array;
|
|
1378
|
+
} | {
|
|
1379
|
+
/** No readable file exists at the path. */
|
|
1380
|
+
readonly kind: "missing";
|
|
1381
|
+
} | {
|
|
1382
|
+
/** The file exists but exceeds the requested byte limit, so nothing was read. */
|
|
1383
|
+
readonly kind: "too-large";
|
|
1384
|
+
};
|
|
1385
|
+
/**
|
|
1386
|
+
* What one entry in a directory listing is: a regular file, a directory, or anything else. A
|
|
1387
|
+
* symbolic link is reported as `other`, never followed, which is what keeps a source scan inside
|
|
1388
|
+
* the roots it was given.
|
|
1389
|
+
*/
|
|
1390
|
+
interface DirectoryEntry {
|
|
1391
|
+
/** The entry's own name, with no directory part. */
|
|
1392
|
+
readonly name: string;
|
|
1393
|
+
/** What the entry is. Anything that is not a regular file or a directory is `other`. */
|
|
1394
|
+
readonly kind: "file" | "directory" | "other";
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* The file-system port the SDK's own I/O goes through. Supplying your own implementation as a
|
|
1398
|
+
* `deps.fs` option redirects that I/O, which is how the SDK's tests avoid touching disk and how an
|
|
1399
|
+
* embedding application can back part of a project with something other than a local disk.
|
|
1400
|
+
*
|
|
1401
|
+
* The seam carries every file the SDK touches: the run-status file, the lock-file, the config
|
|
1402
|
+
* glossary, workbook and interchange I/O, and the locale files themselves, which the adapters read
|
|
1403
|
+
* and write through a port built from this one. The single exception is a caller-supplied
|
|
1404
|
+
* `deps.adapterRegistry`: those adapters were constructed by the caller, so their file access is
|
|
1405
|
+
* whatever the caller wired into them, and supplying both means the caller owns that wiring.
|
|
1406
|
+
*
|
|
1407
|
+
* Reads are size-bounded by contract so that a hostile or accidentally huge file cannot exhaust
|
|
1408
|
+
* memory. Writes are expected to be atomic: the default implementation writes to a temporary file
|
|
1409
|
+
* and renames it into place, so a crash mid-write never leaves a half-written file behind.
|
|
1410
|
+
* Directory creation is the caller's job, so an implementation whose `writeFile` targets a real
|
|
1411
|
+
* directory tree must also implement `mkdir`.
|
|
1412
|
+
*/
|
|
1413
|
+
interface SdkFs {
|
|
1414
|
+
/** Reports whether a readable file exists at the path. */
|
|
1415
|
+
fileExists(path: string): Promise<boolean>;
|
|
1416
|
+
/** Reads a file as UTF-8 text, refusing to read more than `maxBytes`. */
|
|
1417
|
+
readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
|
|
1418
|
+
/** Reads a file as raw bytes, refusing to read more than `maxBytes`. */
|
|
1419
|
+
readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
|
|
1420
|
+
/** Writes UTF-8 text to the path, replacing any existing file atomically. */
|
|
1421
|
+
writeFile(path: string, data: string): Promise<void>;
|
|
1422
|
+
/** Writes raw bytes to the path, replacing any existing file atomically. */
|
|
1423
|
+
writeBytes(path: string, data: Uint8Array): Promise<void>;
|
|
1424
|
+
/**
|
|
1425
|
+
* Creates a file only if it does not already exist, returning `false` when it does. This is the
|
|
1426
|
+
* primitive behind the per-locale write lock, so it must be atomic against other processes.
|
|
1427
|
+
*/
|
|
1428
|
+
createExclusive(path: string, data: string): Promise<boolean>;
|
|
1429
|
+
/** Removes the file at the path, succeeding even when it is already absent. */
|
|
1430
|
+
deleteFile(path: string): Promise<void>;
|
|
1431
|
+
/** Creates a directory and any missing parents. Optional; omit it if the backing store has no directories. */
|
|
1432
|
+
mkdir?(path: string): Promise<void>;
|
|
1433
|
+
/**
|
|
1434
|
+
* Lists one directory's immediate entries, without recursing and without following symbolic
|
|
1435
|
+
* links. Optional, so an implementation written before source extraction existed keeps working;
|
|
1436
|
+
* {@link extract} is the only entry point that needs it and reports its absence as
|
|
1437
|
+
* `EXTRACT_FS_UNSUPPORTED`.
|
|
1438
|
+
*
|
|
1439
|
+
* @param path - The directory to list.
|
|
1440
|
+
* @returns Every immediate entry with its kind. Rejects when the path is not a readable directory.
|
|
1441
|
+
*/
|
|
1442
|
+
readDirectory?(path: string): Promise<readonly DirectoryEntry[]>;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* The on-disk translation memory, read from and written to `verbatra.cache.json` (see
|
|
1447
|
+
* {@link CACHE_FILE_NAME}). A run consults it before calling the provider, so a string that was
|
|
1448
|
+
* already translated under the same configuration is reused instead of paid for again.
|
|
1449
|
+
*
|
|
1450
|
+
* Entries are keyed by configuration fingerprint, then locale, then source-content hash. The
|
|
1451
|
+
* fingerprint layer means that changing the provider, model, tone, or glossary does not silently
|
|
1452
|
+
* reuse translations produced under the old settings; those entries simply stop matching.
|
|
1453
|
+
*
|
|
1454
|
+
* `sources` sits beside them as a flat hash-to-text index. A hash cannot be compared for
|
|
1455
|
+
* similarity, so the source text itself has to be on file for a fuzzy match to have anything to
|
|
1456
|
+
* measure a changed string against. It is deliberately outside the fingerprint layer: the source
|
|
1457
|
+
* text a hash stands for does not depend on which provider translated it.
|
|
1458
|
+
*/
|
|
1459
|
+
interface TranslationMemory {
|
|
1460
|
+
/** The cache schema version. A file from a newer version is ignored rather than trusted. */
|
|
1461
|
+
readonly version: number;
|
|
1462
|
+
/** Cached values, nested as configuration fingerprint, then locale, then source-content hash. */
|
|
1463
|
+
readonly entries: Readonly<Record<string, Readonly<Record<string, Readonly<Record<string, string>>>>>>;
|
|
1464
|
+
/**
|
|
1465
|
+
* The source text each content hash stands for. Empty for a cache carried forward from schema
|
|
1466
|
+
* version 1, which stored no source text; it fills in as later runs touch those hashes again.
|
|
1467
|
+
*/
|
|
1468
|
+
readonly sources: Readonly<Record<string, string>>;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/**
|
|
1472
|
+
* The file name of the project's translation memory, resolved against the run's working directory.
|
|
1473
|
+
* Commit it to reuse translations across machines and CI runs, or add it to `.gitignore` to treat
|
|
1474
|
+
* the cache as purely local. Deleting it is always safe: the next run simply repays for the strings
|
|
1475
|
+
* it would have reused.
|
|
1476
|
+
*/
|
|
1477
|
+
declare const CACHE_FILE_NAME = "verbatra.cache.json";
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* The zod schema for the `provider` block, discriminated on `id`. Each variant's options are
|
|
1481
|
+
* validated strictly, so an option that belongs to a different provider is reported as an error
|
|
1482
|
+
* rather than ignored. It is embedded in {@link verbatraConfigSchema} and produces
|
|
1483
|
+
* {@link ProviderConfig}.
|
|
1484
|
+
*/
|
|
1485
|
+
declare const providerConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
1486
|
+
id: z.ZodLiteral<"anthropic">;
|
|
1487
|
+
options: z.ZodObject<{
|
|
1488
|
+
model: z.ZodString;
|
|
1489
|
+
maxTokens: z.ZodNumber;
|
|
1490
|
+
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
1491
|
+
}, z.core.$strict>;
|
|
1492
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1493
|
+
id: z.ZodLiteral<"openai">;
|
|
1494
|
+
options: z.ZodObject<{
|
|
1495
|
+
model: z.ZodString;
|
|
1496
|
+
maxOutputTokens: z.ZodNumber;
|
|
1497
|
+
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
1498
|
+
}, z.core.$strict>;
|
|
1499
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1500
|
+
id: z.ZodLiteral<"gemini">;
|
|
1501
|
+
options: z.ZodObject<{
|
|
1502
|
+
model: z.ZodString;
|
|
1503
|
+
maxOutputTokens: z.ZodNumber;
|
|
1504
|
+
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
1505
|
+
}, z.core.$strict>;
|
|
1506
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1507
|
+
id: z.ZodLiteral<"deepl">;
|
|
1508
|
+
options: z.ZodObject<{
|
|
1509
|
+
glossaryId: z.ZodOptional<z.ZodString>;
|
|
1510
|
+
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
1511
|
+
}, z.core.$strict>;
|
|
1512
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1513
|
+
id: z.ZodLiteral<"google-translate">;
|
|
1514
|
+
options: z.ZodObject<{
|
|
1515
|
+
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
1516
|
+
}, z.core.$strict>;
|
|
1517
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1518
|
+
id: z.ZodLiteral<"openai-compatible">;
|
|
1519
|
+
options: z.ZodObject<{
|
|
1520
|
+
baseUrl: z.ZodURL;
|
|
1521
|
+
model: z.ZodString;
|
|
1522
|
+
maxOutputTokens: z.ZodNumber;
|
|
1523
|
+
apiKeyEnvVar: z.ZodOptional<z.ZodString>;
|
|
1524
|
+
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
1525
|
+
}, z.core.$strict>;
|
|
1526
|
+
}, z.core.$strip>], "id">;
|
|
1527
|
+
/**
|
|
1528
|
+
* The `provider` block of a verbatra config: a discriminated union on `id`, so each provider
|
|
1529
|
+
* carries exactly the options it supports and nothing else.
|
|
1530
|
+
*
|
|
1531
|
+
* No variant has a field for an API key. Keys are read from the environment by the provider itself
|
|
1532
|
+
* (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `DEEPL_API_KEY`, or
|
|
1533
|
+
* `GOOGLE_TRANSLATE_API_KEY`), which is what keeps them out of config files and out of version
|
|
1534
|
+
* control. The `openai-compatible` variant may name a different environment variable through
|
|
1535
|
+
* `apiKeyEnvVar`, but still never holds the key itself.
|
|
1536
|
+
*/
|
|
1537
|
+
type ProviderConfig = z.infer<typeof providerConfigSchema>;
|
|
1538
|
+
/**
|
|
1539
|
+
* The identifier of a supported translation provider: `anthropic`, `openai`, `gemini`, `deepl`,
|
|
1540
|
+
* `google-translate`, or `openai-compatible`. The last one targets a local or self-hosted server
|
|
1541
|
+
* that speaks the OpenAI chat-completions API.
|
|
1542
|
+
*/
|
|
1543
|
+
type ProviderId = ProviderConfig["id"];
|
|
1544
|
+
|
|
1545
|
+
/**
|
|
1546
|
+
* The zod schema every verbatra config is validated against. {@link loadConfig} applies it, so most
|
|
1547
|
+
* consumers never call it directly; reach for it when you build a config programmatically and want
|
|
1548
|
+
* to validate it before handing it to {@link translate}, or when you surface config errors in your
|
|
1549
|
+
* own tooling.
|
|
1550
|
+
*
|
|
1551
|
+
* The object is strict, so an unrecognized key is an error rather than being silently ignored: a
|
|
1552
|
+
* typo in a config file is reported instead of quietly doing nothing. The one exception is the
|
|
1553
|
+
* optional `$schema` key, accepted so a JSON or YAML config can point an editor at the JSON Schema
|
|
1554
|
+
* document the package ships as `@verbatra/sdk/config-schema.json`.
|
|
1555
|
+
*
|
|
1556
|
+
* `fuzzyCache` is off unless `enabled` is set. With it on, a source string whose earlier form is in
|
|
1557
|
+
* the translation memory close enough to clear `threshold` reuses that translation instead of
|
|
1558
|
+
* paying the provider for it. `threshold` is a similarity ratio from `0.5` to `1` and defaults to `0.9`.
|
|
1559
|
+
*
|
|
1560
|
+
* Beyond the per-field checks, two whole-config rules are enforced: `targetLocales` must not
|
|
1561
|
+
* contain the source locale, and it must not contain two locales that differ only in case (they
|
|
1562
|
+
* would collide on a case-insensitive file system). The `{locale}` token requirement is a
|
|
1563
|
+
* per-field check on `files.pattern`.
|
|
1564
|
+
*/
|
|
1565
|
+
declare const verbatraConfigSchema: z.ZodObject<{
|
|
1566
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
1567
|
+
sourceLocale: z.ZodString;
|
|
1568
|
+
targetLocales: z.ZodArray<z.ZodString>;
|
|
1569
|
+
format: z.ZodUnion<readonly [z.ZodEnum<{
|
|
1570
|
+
"i18next-json": "i18next-json";
|
|
1571
|
+
"vue-i18n-json": "vue-i18n-json";
|
|
1572
|
+
"next-intl-json": "next-intl-json";
|
|
1573
|
+
"ngx-translate-json": "ngx-translate-json";
|
|
1574
|
+
xliff: "xliff";
|
|
484
1575
|
yaml: "yaml";
|
|
485
1576
|
arb: "arb";
|
|
486
1577
|
properties: "properties";
|
|
@@ -488,7 +1579,9 @@ declare const verbatraConfigSchema: z.ZodObject<{
|
|
|
488
1579
|
"apple-xcstrings": "apple-xcstrings";
|
|
489
1580
|
"android-xml": "android-xml";
|
|
490
1581
|
"gettext-po": "gettext-po";
|
|
491
|
-
|
|
1582
|
+
ini: "ini";
|
|
1583
|
+
resx: "resx";
|
|
1584
|
+
}>, z.ZodTemplateLiteral<`custom:${string}`>]>;
|
|
492
1585
|
files: z.ZodObject<{
|
|
493
1586
|
pattern: z.ZodString;
|
|
494
1587
|
localeStyle: z.ZodOptional<z.ZodEnum<{
|
|
@@ -547,12 +1640,40 @@ declare const verbatraConfigSchema: z.ZodObject<{
|
|
|
547
1640
|
}>>;
|
|
548
1641
|
prune: z.ZodOptional<z.ZodBoolean>;
|
|
549
1642
|
generatePlurals: z.ZodOptional<z.ZodBoolean>;
|
|
1643
|
+
fuzzyCache: z.ZodOptional<z.ZodObject<{
|
|
1644
|
+
enabled: z.ZodBoolean;
|
|
1645
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
1646
|
+
}, z.core.$strict>>;
|
|
550
1647
|
maxBatchSize: z.ZodOptional<z.ZodNumber>;
|
|
1648
|
+
maxLength: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
551
1649
|
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
552
1650
|
budgetBehavior: z.ZodOptional<z.ZodEnum<{
|
|
553
1651
|
warn: "warn";
|
|
554
1652
|
stop: "stop";
|
|
555
1653
|
}>>;
|
|
1654
|
+
rates: z.ZodOptional<z.ZodObject<{
|
|
1655
|
+
asOf: z.ZodISODate;
|
|
1656
|
+
currency: z.ZodString;
|
|
1657
|
+
table: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
|
|
1658
|
+
inputPerMillionTokens: z.ZodNumber;
|
|
1659
|
+
outputPerMillionTokens: z.ZodNumber;
|
|
1660
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1661
|
+
perMillionCharacters: z.ZodNumber;
|
|
1662
|
+
}, z.core.$strict>]>>;
|
|
1663
|
+
}, z.core.$strict>>;
|
|
1664
|
+
extract: z.ZodOptional<z.ZodObject<{
|
|
1665
|
+
framework: z.ZodEnum<{
|
|
1666
|
+
i18next: "i18next";
|
|
1667
|
+
}>;
|
|
1668
|
+
roots: z.ZodArray<z.ZodString>;
|
|
1669
|
+
exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1670
|
+
literals: z.ZodOptional<z.ZodObject<{
|
|
1671
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1672
|
+
}, z.core.$strict>>;
|
|
1673
|
+
unused: z.ZodOptional<z.ZodObject<{
|
|
1674
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1675
|
+
}, z.core.$strict>>;
|
|
1676
|
+
}, z.core.$strict>>;
|
|
556
1677
|
}, z.core.$strict>;
|
|
557
1678
|
/**
|
|
558
1679
|
* A config exactly as it is written in a `verbatra.config.ts` file, before the SDK resolves
|
|
@@ -720,6 +1841,46 @@ declare function defineConfig(config: AuthoringConfigFor<"google-translate">): V
|
|
|
720
1841
|
*/
|
|
721
1842
|
declare function defineConfig(config: AuthoringConfig): VerbatraConfigInput;
|
|
722
1843
|
|
|
1844
|
+
/**
|
|
1845
|
+
* The zod schema for the optional `extract` block, validated strictly so a misspelled field is
|
|
1846
|
+
* reported rather than silently ignored. It is embedded in {@link verbatraConfigSchema} and
|
|
1847
|
+
* produces {@link ExtractionConfig}.
|
|
1848
|
+
*/
|
|
1849
|
+
declare const extractionConfigSchema: z.ZodObject<{
|
|
1850
|
+
framework: z.ZodEnum<{
|
|
1851
|
+
i18next: "i18next";
|
|
1852
|
+
}>;
|
|
1853
|
+
roots: z.ZodArray<z.ZodString>;
|
|
1854
|
+
exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1855
|
+
literals: z.ZodOptional<z.ZodObject<{
|
|
1856
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1857
|
+
}, z.core.$strict>>;
|
|
1858
|
+
unused: z.ZodOptional<z.ZodObject<{
|
|
1859
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1860
|
+
}, z.core.$strict>>;
|
|
1861
|
+
}, z.core.$strict>;
|
|
1862
|
+
/**
|
|
1863
|
+
* The `extract` block of a verbatra config: which translation framework's call sites the source
|
|
1864
|
+
* scan looks for, which directories it walks, and which directory names it skips on the way.
|
|
1865
|
+
*
|
|
1866
|
+
* `roots` are resolved against the run's working directory, and the scan never reads outside them.
|
|
1867
|
+
* `exclude` adds directory names to the set that is always skipped, which already covers
|
|
1868
|
+
* `node_modules`, `.git`, and the usual build output directories.
|
|
1869
|
+
*
|
|
1870
|
+
* `literals.ignore` lists string literals the untranslated-literal scan (`verbatra doctor
|
|
1871
|
+
* --literals`) holds back project-wide, matched against the whole literal text as it is reported:
|
|
1872
|
+
* whitespace collapsed and, in JSX text or a quoted JSX attribute value, character references such
|
|
1873
|
+
* as `&` decoded, so an entry is written `Tom & Jerry`, not `Tom & Jerry`. A held-back
|
|
1874
|
+
* literal is still reported, under `suppressed`, so nothing vanishes silently.
|
|
1875
|
+
*
|
|
1876
|
+
* `unused.ignore` names catalog keys the unused-key report must not count as unused, for a key
|
|
1877
|
+
* referenced only from outside the scanned source (a server template, a CMS, a test fixture). Each
|
|
1878
|
+
* entry is an exact key, or a pattern in which `*` matches any run of characters, such as
|
|
1879
|
+
* `emails.*`. It matches the decoded key as the report shows it (`Welcome. Enjoy`, not the escaped
|
|
1880
|
+
* catalog spelling). A matched key is reported as ignored, never dropped from the report.
|
|
1881
|
+
*/
|
|
1882
|
+
type ExtractionConfig = z.infer<typeof extractionConfigSchema>;
|
|
1883
|
+
|
|
723
1884
|
/**
|
|
724
1885
|
* Where a loaded config's glossary came from, reported by {@link loadConfigWithMeta}. The resolved
|
|
725
1886
|
* {@link VerbatraConfig} holds only the final term map, so this is the one place a tool can tell an
|
|
@@ -929,6 +2090,67 @@ declare function loadConfigWithMeta(options?: LoadConfigOptions): Promise<Loaded
|
|
|
929
2090
|
*/
|
|
930
2091
|
declare function loadConfig(options?: LoadConfigOptions): Promise<VerbatraConfig>;
|
|
931
2092
|
|
|
2093
|
+
/**
|
|
2094
|
+
* What a provider charges for: `tokens` for the prompt-driven LLMs, which bill the prompt and the
|
|
2095
|
+
* completion separately, and `characters` for the machine-translation APIs, which bill the source
|
|
2096
|
+
* text they are handed and report no token usage at all.
|
|
2097
|
+
*/
|
|
2098
|
+
type BillingUnit = "tokens" | "characters";
|
|
2099
|
+
/**
|
|
2100
|
+
* How one provider bills. `billedByApi` is false for a self-hosted endpoint, which still consumes
|
|
2101
|
+
* tokens but sends no invoice, so an estimate reports its quantity and no currency figure.
|
|
2102
|
+
*/
|
|
2103
|
+
interface ProviderBilling {
|
|
2104
|
+
/** The quantity this provider charges for. */
|
|
2105
|
+
readonly unit: BillingUnit;
|
|
2106
|
+
/** Whether a hosted API bills for the work at all. */
|
|
2107
|
+
readonly billedByApi: boolean;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
declare const tokenRateSchema: z.ZodObject<{
|
|
2111
|
+
inputPerMillionTokens: z.ZodNumber;
|
|
2112
|
+
outputPerMillionTokens: z.ZodNumber;
|
|
2113
|
+
}, z.core.$strict>;
|
|
2114
|
+
declare const characterRateSchema: z.ZodObject<{
|
|
2115
|
+
perMillionCharacters: z.ZodNumber;
|
|
2116
|
+
}, z.core.$strict>;
|
|
2117
|
+
declare const modelRateSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
2118
|
+
inputPerMillionTokens: z.ZodNumber;
|
|
2119
|
+
outputPerMillionTokens: z.ZodNumber;
|
|
2120
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2121
|
+
perMillionCharacters: z.ZodNumber;
|
|
2122
|
+
}, z.core.$strict>]>;
|
|
2123
|
+
declare const rateCardSchema: z.ZodObject<{
|
|
2124
|
+
asOf: z.ZodISODate;
|
|
2125
|
+
currency: z.ZodString;
|
|
2126
|
+
table: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
|
|
2127
|
+
inputPerMillionTokens: z.ZodNumber;
|
|
2128
|
+
outputPerMillionTokens: z.ZodNumber;
|
|
2129
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2130
|
+
perMillionCharacters: z.ZodNumber;
|
|
2131
|
+
}, z.core.$strict>]>>;
|
|
2132
|
+
}, z.core.$strict>;
|
|
2133
|
+
/**
|
|
2134
|
+
* A rate charging by tokens, with the prompt and the completion priced separately. Every hosted LLM
|
|
2135
|
+
* provider bills this way.
|
|
2136
|
+
*/
|
|
2137
|
+
type TokenRate = z.infer<typeof tokenRateSchema>;
|
|
2138
|
+
/** A rate charging by source characters, which is how the machine-translation APIs bill. */
|
|
2139
|
+
type CharacterRate = z.infer<typeof characterRateSchema>;
|
|
2140
|
+
/** One entry of a {@link RateCard}: either a token rate or a character rate, never both. */
|
|
2141
|
+
type ModelRate = z.infer<typeof modelRateSchema>;
|
|
2142
|
+
/**
|
|
2143
|
+
* The rates a project supplies so an estimate can be expressed in money. verbatra ships no prices of
|
|
2144
|
+
* its own: they are a third party's, they change without notice, and a number baked into a release
|
|
2145
|
+
* would quietly go stale. The card carries the date it was read and the currency it is written in,
|
|
2146
|
+
* so any figure derived from it is visibly as old as `asOf` says it is.
|
|
2147
|
+
*
|
|
2148
|
+
* `table` is keyed by rate key: `provider/model` for a provider configured with a model (for
|
|
2149
|
+
* example `gemini/gemini-2.5-flash`), and the bare provider id for one without (`deepl`,
|
|
2150
|
+
* `google-translate`).
|
|
2151
|
+
*/
|
|
2152
|
+
type RateCard = z.infer<typeof rateCardSchema>;
|
|
2153
|
+
|
|
932
2154
|
/**
|
|
933
2155
|
* The stable, structured failure codes the SDK throws. Branch on {@link SdkError.code} rather than
|
|
934
2156
|
* on a message; the messages are written for humans and are not part of the contract.
|
|
@@ -997,306 +2219,547 @@ declare function loadConfig(options?: LoadConfigOptions): Promise<VerbatraConfig
|
|
|
997
2219
|
* {@link translate} before any locale runs, and by {@link watch} once at startup, before any
|
|
998
2220
|
* watching begins, since the value is fixed for the session rather than re-read per run.
|
|
999
2221
|
* - `CONCURRENCY_BUDGET_CONFLICT`: a live run requested a `concurrency` above 1 while a token
|
|
1000
|
-
* budget is configured. The
|
|
1001
|
-
*
|
|
2222
|
+
* budget is configured. The ceiling itself would still hold, but which locale loses its
|
|
2223
|
+
* remaining work would depend on the order the locales interleave, so the same project would not
|
|
2224
|
+
* produce the same run twice. A dry run is exempt, since it never consults the budget.
|
|
1002
2225
|
* - `TARGET_UNWRITABLE`: a target locale file could not be written, because its directory is not
|
|
1003
2226
|
* writable, does not exist, is read-only, or is out of space. The message names the target file
|
|
1004
2227
|
* relative to `cwd` and the underlying file-system code, never the internal temporary file the
|
|
1005
2228
|
* atomic write uses. Thrown by {@link editEntry} and {@link retranslateEntry}, which act on one
|
|
1006
2229
|
* locale. {@link translate} and {@link importWorkbook} do not throw it: they record it on that
|
|
1007
2230
|
* locale's {@link LocaleSummary} and carry on with the other locales.
|
|
2231
|
+
* - `PSEUDO_OUTPUT_CONFLICT`: {@link pseudolocalize} was asked to generate a pseudolocale that
|
|
2232
|
+
* names a configured locale, or to write one onto a configured locale file. Refused before
|
|
2233
|
+
* anything is read or written, so a generated pseudolocale can never overwrite a real
|
|
2234
|
+
* translation or stand in for one.
|
|
2235
|
+
* - `SOURCE_UNWRITABLE`: the source locale file could not be written. Thrown by {@link extract}
|
|
2236
|
+
* alone, since it is the only entry point that writes the source locale. The `xliff` and
|
|
2237
|
+
* `apple-xcstrings` formats reach it when no catalog exists yet, because neither is created from
|
|
2238
|
+
* nothing.
|
|
2239
|
+
* - `EXTRACT_NOT_CONFIGURED`: {@link extract} was called with a config that carries no `extract`
|
|
2240
|
+
* block, so there is no framework to look for and no source root to walk.
|
|
2241
|
+
* - `EXTRACT_FS_UNSUPPORTED`: {@link extract} was given a `deps.fs` that implements no
|
|
2242
|
+
* `readDirectory`, so no source file can be discovered. The member is optional on {@link SdkFs}
|
|
2243
|
+
* precisely so an implementation written before extraction existed keeps compiling; this is the
|
|
2244
|
+
* error it gets if it is then handed to {@link extract}.
|
|
2245
|
+
* - `TYPES_OUTPUT_CONFLICT`: {@link generateTypes} refused its output path. Before anything is
|
|
2246
|
+
* read or written, it refuses a path that names no file, is absolute, climbs out of the working
|
|
2247
|
+
* directory, or does not end in `.ts`, `.mts` or `.cts`, and one naming a configured locale
|
|
2248
|
+
* file, the lock file, the translation-memory cache, a file verbatra searches for its
|
|
2249
|
+
* configuration, or the configuration file the run loaded, compared case-insensitively. A
|
|
2250
|
+
* generating run, never a `check` run, also refuses to replace an existing file there that does
|
|
2251
|
+
* not begin with the header line verbatra writes, and leaves that file untouched.
|
|
2252
|
+
* - `TYPES_UNWRITABLE`: the declaration file {@link generateTypes} produces could not be written,
|
|
2253
|
+
* because its directory is not writable, does not exist, or the disk is out of space.
|
|
1008
2254
|
* - `LOCALE_FAILED`: never thrown. It is the fallback code recorded on a failed
|
|
1009
2255
|
* {@link LocaleSummary} when a per-locale failure carries no code of its own.
|
|
1010
2256
|
*/
|
|
1011
|
-
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";
|
|
2257
|
+
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" | "PSEUDO_OUTPUT_CONFLICT" | "SOURCE_UNWRITABLE" | "EXTRACT_NOT_CONFIGURED" | "EXTRACT_FS_UNSUPPORTED" | "TYPES_OUTPUT_CONFLICT" | "TYPES_UNWRITABLE" | "LOCALE_FAILED";
|
|
2258
|
+
/**
|
|
2259
|
+
* The single structured error the SDK throws. Every whole-run failure surfaces as an `SdkError`
|
|
2260
|
+
* carrying a stable {@link SdkErrorCode}; per-locale failures, provider notices, and integrity
|
|
2261
|
+
* findings are reported as data on the {@link RunSummary} instead of being thrown.
|
|
2262
|
+
*
|
|
2263
|
+
* An `SdkError` never carries a secret in its message.
|
|
2264
|
+
*/
|
|
2265
|
+
declare class SdkError extends Error {
|
|
2266
|
+
/** The stable {@link SdkErrorCode} for this failure. Branch on this, not on the message. */
|
|
2267
|
+
readonly code: SdkErrorCode;
|
|
2268
|
+
/**
|
|
2269
|
+
* @param code - The stable failure code.
|
|
2270
|
+
* @param message - A human-readable description of the failure. Never contains a secret.
|
|
2271
|
+
* @param options - `cause` carries the error this one wraps, such as the interchange reader's
|
|
2272
|
+
* error for a `SOURCE_INVALID` TMX file. Read that file's line, column and unit with
|
|
2273
|
+
* `tmxErrorLocation` rather than from the cause directly.
|
|
2274
|
+
*/
|
|
2275
|
+
constructor(code: SdkErrorCode, message: string, options?: {
|
|
2276
|
+
readonly cause?: unknown;
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
/**
|
|
2281
|
+
* Conditions the SDK itself reports on a locale, as opposed to the {@link ProviderNotice} codes a
|
|
2282
|
+
* provider raises. Each marks a run that completed but did something a caller may want to know
|
|
2283
|
+
* about.
|
|
2284
|
+
*
|
|
2285
|
+
* - `PLURAL_CATEGORIES_INCOMPLETE`: plural generation could not produce every category the target
|
|
2286
|
+
* language requires, so the entry is written with the categories that were produced.
|
|
2287
|
+
* - `SUB_BATCH_FAILED`: one sub-batch of a locale failed while others succeeded. The locale is
|
|
2288
|
+
* reported as `partial` rather than failed.
|
|
2289
|
+
* - `BLANK_ROW_BASELINE_RETAINED`: an imported handoff row was blank, so the existing translation
|
|
2290
|
+
* and its lock-file baseline were kept rather than being erased.
|
|
2291
|
+
* - `BUDGET_TOKENS_EXCEEDED`: the configured token budget was reached. Under `warn` the run
|
|
2292
|
+
* continues and the overrun is reported; under `stop` the request that would have crossed the
|
|
2293
|
+
* ceiling is withheld before it is sent, and so is every request after it.
|
|
2294
|
+
* - `CACHE_VERSION_UNRECOGNIZED`: the translation memory is at a version this release does not
|
|
2295
|
+
* understand, so it was ignored rather than trusted.
|
|
2296
|
+
*/
|
|
2297
|
+
type SdkNoticeCode = "PLURAL_CATEGORIES_INCOMPLETE" | "SUB_BATCH_FAILED" | "BLANK_ROW_BASELINE_RETAINED" | "BUDGET_TOKENS_EXCEEDED" | "CACHE_VERSION_UNRECOGNIZED";
|
|
2298
|
+
/**
|
|
2299
|
+
* Token usage as reported by the provider. Absent when the provider does not report usage, which is
|
|
2300
|
+
* the case for machine-translation APIs such as DeepL that do not bill in tokens.
|
|
2301
|
+
*/
|
|
2302
|
+
interface UsageSummary {
|
|
2303
|
+
/** Tokens consumed by the prompts sent to the provider. */
|
|
2304
|
+
readonly inputTokens: number;
|
|
2305
|
+
/** Tokens consumed by the provider's responses. */
|
|
2306
|
+
readonly outputTokens: number;
|
|
2307
|
+
}
|
|
2308
|
+
/**
|
|
2309
|
+
* What a run does about its token budget. `warn`, the default, counts the spend and reports the
|
|
2310
|
+
* overrun without withholding anything. `stop` turns the figure into a ceiling: before each
|
|
2311
|
+
* provider request the run projects what that request will cost, and withholds it, and everything
|
|
2312
|
+
* after it, rather than send a request that would take the run past `maxTokens`.
|
|
2313
|
+
*/
|
|
2314
|
+
type BudgetBehavior = "warn" | "stop";
|
|
2315
|
+
/**
|
|
2316
|
+
* The token budget in force for one run, and how much of it that run consumed.
|
|
2317
|
+
*
|
|
2318
|
+
* It is enforced for every provider, including the machine-translation APIs that report no usage of
|
|
2319
|
+
* their own: those are counted from the same projection the pre-run estimate uses, so a ceiling set
|
|
2320
|
+
* against DeepL or Google Cloud Translation withholds work rather than sitting inert.
|
|
2321
|
+
* {@link supported} says which of the two the figure came from.
|
|
2322
|
+
*
|
|
2323
|
+
* It bounds one run, not a session. {@link watch} starts a fresh budget for every run it triggers,
|
|
2324
|
+
* so a watch session can spend up to `maxTokens` again on each source edit. It also covers
|
|
2325
|
+
* {@link translate} and nothing else: `retranslateEntry` constructs its own provider on a separate
|
|
2326
|
+
* path, reached from the Studio dashboard and the agent tools, and no budget here caps that spend.
|
|
2327
|
+
*
|
|
2328
|
+
* Under `stop` the ceiling is checked before every request the run sends, each half of a re-split
|
|
2329
|
+
* request included, and a refused request is never sent. What a reservation cannot bound is what
|
|
2330
|
+
* happens inside a request it already admitted. The provider layer sends one repair call of its own
|
|
2331
|
+
* when keys come back missing, so one admitted batch can cost up to about twice its projection; and
|
|
2332
|
+
* the count is only as good as what the provider reports, so a provider under-reporting its usage
|
|
2333
|
+
* would leave the ceiling further away than it looks. Both land at reconciliation, and nothing
|
|
2334
|
+
* further is admitted once they do. A request that fails or comes back truncated keeps its whole
|
|
2335
|
+
* projection charged rather than being refunded, because such a call has usually already billed for
|
|
2336
|
+
* its prompt.
|
|
2337
|
+
*/
|
|
2338
|
+
interface RunBudget {
|
|
2339
|
+
/** The configured ceiling, in tokens. */
|
|
2340
|
+
readonly maxTokens: number;
|
|
2341
|
+
/** Whether passing the ceiling only warns, or withholds work to stay under it. */
|
|
2342
|
+
readonly behavior: BudgetBehavior;
|
|
2343
|
+
/**
|
|
2344
|
+
* Whether {@link tokensUsed} is entirely the provider's own reported usage. False as soon as one
|
|
2345
|
+
* counted request came back without a usable figure, so the total is partly verbatra's own
|
|
2346
|
+
* projection: a machine-translation API reports none at all, and a failed or truncated request
|
|
2347
|
+
* reports none either. The budget is enforced either way.
|
|
2348
|
+
*/
|
|
2349
|
+
readonly supported: boolean;
|
|
2350
|
+
/**
|
|
2351
|
+
* Tokens counted across the run: reported usage for every request that gave one, and the
|
|
2352
|
+
* projection for every request that did not.
|
|
2353
|
+
*/
|
|
2354
|
+
readonly tokensUsed: number;
|
|
2355
|
+
/**
|
|
2356
|
+
* True once the run reached its ceiling: either `tokensUsed` reached or passed `maxTokens`, or,
|
|
2357
|
+
* under `stop`, a request was withheld because sending it would have passed it.
|
|
2358
|
+
*/
|
|
2359
|
+
readonly exceeded: boolean;
|
|
2360
|
+
}
|
|
2361
|
+
/**
|
|
2362
|
+
* Why a {@link RunEstimate} does or does not carry a currency figure.
|
|
2363
|
+
*
|
|
2364
|
+
* - `priced`: a rate for this provider and model was found on the configured rate card and applied.
|
|
2365
|
+
* - `no-rate-on-file`: no rate was configured for {@link RunEstimate.rateKey}. The quantity is still
|
|
2366
|
+
* reported; the money is not, because verbatra ships no prices of its own and will not guess one.
|
|
2367
|
+
* - `rate-unit-mismatch`: a rate exists but is written in the wrong unit for this provider, for
|
|
2368
|
+
* instance a per-character price against a token-billed model. It is refused rather than applied.
|
|
2369
|
+
* - `not-billed`: the provider is a self-hosted endpoint, so no API bills for the run at all.
|
|
2370
|
+
*/
|
|
2371
|
+
type EstimatePricing = "priced" | "no-rate-on-file" | "rate-unit-mismatch" | "not-billed";
|
|
1012
2372
|
/**
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
*
|
|
2373
|
+
* What a pre-run estimate deliberately leaves out. Each code names a way the real run can land
|
|
2374
|
+
* below the figure, above it, or away from it, so an estimate is never mistaken for an invoice.
|
|
2375
|
+
*
|
|
2376
|
+
* - `CACHE_NOT_CONSULTED`: the estimate does not read the translation memory, so keys a live run
|
|
2377
|
+
* would serve from cache are still counted.
|
|
2378
|
+
* - `SOURCE_DUPLICATES_NOT_DEDUPLICATED`: a live run sends one representative per identical source
|
|
2379
|
+
* string; the estimate counts every key.
|
|
2380
|
+
* - `TRANSPORT_RETRIES_NOT_COUNTED`: the estimate counts one billable call per planned request. The
|
|
2381
|
+
* provider SDKs retry a failed call underneath that, so one planned request can become several
|
|
2382
|
+
* attempts on the wire, and a retried attempt that reached the model is still billed.
|
|
2383
|
+
* - `TRANSLATION_LENGTH_IS_ESTIMATED`: the translation does not exist yet, so the response is sized
|
|
2384
|
+
* from the source value plus a fixed expansion allowance. A target language that runs longer than
|
|
2385
|
+
* the allowance reports more completion tokens than this figure predicts.
|
|
2386
|
+
* - `TOKEN_COUNT_IS_HEURISTIC`: tokens are derived from character counts, not from the provider's
|
|
2387
|
+
* own tokenizer.
|
|
2388
|
+
* - `REPAIR_REQUESTS_NOT_COUNTED`: the extra requests an incomplete response can trigger are not
|
|
2389
|
+
* counted: one bounded repair round when keys came back missing, and a retry in halves, which
|
|
2390
|
+
* recurses, when the output was cut off. A repair round re-sends the system rules, the glossary
|
|
2391
|
+
* and the tone in full, so repairing one key in a large-glossary batch costs close to a whole
|
|
2392
|
+
* extra request.
|
|
1018
2393
|
*/
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
2394
|
+
type EstimateCaveatCode = "CACHE_NOT_CONSULTED" | "SOURCE_DUPLICATES_NOT_DEDUPLICATED" | "TRANSPORT_RETRIES_NOT_COUNTED" | "TRANSLATION_LENGTH_IS_ESTIMATED" | "TOKEN_COUNT_IS_HEURISTIC" | "REPAIR_REQUESTS_NOT_COUNTED";
|
|
2395
|
+
/**
|
|
2396
|
+
* The estimated billing quantity for one target locale, without the money. `inputTokens` and
|
|
2397
|
+
* `outputTokens` are present only for a token-billed provider, and `sourceCharacters` only for a
|
|
2398
|
+
* character-billed one, so a machine-translation API is never handed a token figure it does not
|
|
2399
|
+
* bill by.
|
|
2400
|
+
*/
|
|
2401
|
+
interface LocaleEstimateQuantity {
|
|
2402
|
+
/** The target locale this estimate covers. */
|
|
2403
|
+
readonly locale: string;
|
|
2404
|
+
/** How many keys would be sent for this locale, translation and plural generation together. */
|
|
2405
|
+
readonly keys: number;
|
|
2406
|
+
/** How many provider requests those keys would be split into. */
|
|
2407
|
+
readonly requests: number;
|
|
2408
|
+
/** Estimated prompt tokens. Present only when the provider bills by tokens. */
|
|
2409
|
+
readonly inputTokens?: number;
|
|
2410
|
+
/** Estimated completion tokens. Present only when the provider bills by tokens. */
|
|
2411
|
+
readonly outputTokens?: number;
|
|
2412
|
+
/** Estimated source characters. Present only when the provider bills by characters. */
|
|
2413
|
+
readonly sourceCharacters?: number;
|
|
2414
|
+
}
|
|
2415
|
+
/** One locale of a {@link PricedRunEstimate}: the quantity plus the money it comes to. */
|
|
2416
|
+
interface PricedLocaleEstimate extends LocaleEstimateQuantity {
|
|
2417
|
+
/** The estimated cost for this locale, in {@link PricedRunEstimate.currency}. */
|
|
2418
|
+
readonly cost: number;
|
|
2419
|
+
}
|
|
2420
|
+
/** One locale of an {@link UnpricedRunEstimate}: the quantity, and deliberately no money. */
|
|
2421
|
+
interface UnpricedLocaleEstimate extends LocaleEstimateQuantity {
|
|
2422
|
+
/** Never present: the run carries no rate, so no locale of it carries a figure. */
|
|
2423
|
+
readonly cost?: undefined;
|
|
2424
|
+
}
|
|
2425
|
+
/**
|
|
2426
|
+
* One target locale of a {@link RunEstimate}. It carries a `cost` exactly when the run it belongs
|
|
2427
|
+
* to was priced, so there is no such thing as a priced estimate with a costless locale.
|
|
2428
|
+
*/
|
|
2429
|
+
type LocaleEstimate = PricedLocaleEstimate | UnpricedLocaleEstimate;
|
|
2430
|
+
/**
|
|
2431
|
+
* Everything a {@link RunEstimate} reports regardless of whether it could be priced: which provider
|
|
2432
|
+
* the figure was computed for, how much would be sent, and what the figure leaves out.
|
|
2433
|
+
*/
|
|
2434
|
+
interface EstimateIdentity {
|
|
2435
|
+
/** The provider the estimate was computed for. */
|
|
2436
|
+
readonly provider: ProviderId;
|
|
2437
|
+
/** The configured model, absent for a provider that takes none (`deepl`, `google-translate`). */
|
|
2438
|
+
readonly model?: string;
|
|
1022
2439
|
/**
|
|
1023
|
-
*
|
|
1024
|
-
*
|
|
2440
|
+
* The key a rate is filed under in the config's `rates.table`: `provider/model` for a provider
|
|
2441
|
+
* configured with a model, and the bare provider id otherwise.
|
|
2442
|
+
*/
|
|
2443
|
+
readonly rateKey: string;
|
|
2444
|
+
/**
|
|
2445
|
+
* Keys that would be sent across every locale, counting both the keys that would be translated
|
|
2446
|
+
* and the plural forms that would be generated.
|
|
1025
2447
|
*/
|
|
1026
|
-
|
|
2448
|
+
readonly keys: number;
|
|
2449
|
+
/** Provider requests across every locale, generation batches included. */
|
|
2450
|
+
readonly requests: number;
|
|
2451
|
+
/** What this figure leaves out. Always present, never empty. See {@link EstimateCaveatCode}. */
|
|
2452
|
+
readonly caveats: readonly EstimateCaveatCode[];
|
|
2453
|
+
}
|
|
2454
|
+
/** The quantity a token-billed provider charges for: the prompt and the completion, separately. */
|
|
2455
|
+
interface TokenRunQuantity extends EstimateIdentity {
|
|
2456
|
+
/** Always `tokens` for a prompt-driven LLM. */
|
|
2457
|
+
readonly unit: "tokens";
|
|
2458
|
+
/** Estimated prompt tokens across every locale. */
|
|
2459
|
+
readonly inputTokens: number;
|
|
2460
|
+
/** Estimated completion tokens across every locale. */
|
|
2461
|
+
readonly outputTokens: number;
|
|
2462
|
+
/** Never present: a token-billed provider does not bill source characters. */
|
|
2463
|
+
readonly sourceCharacters?: undefined;
|
|
2464
|
+
}
|
|
2465
|
+
/** The quantity a character-billed provider charges for: the source text handed to it. */
|
|
2466
|
+
interface CharacterRunQuantity extends EstimateIdentity {
|
|
2467
|
+
/** Always `characters` for a machine-translation API. */
|
|
2468
|
+
readonly unit: "characters";
|
|
2469
|
+
/** Estimated source characters across every locale. */
|
|
2470
|
+
readonly sourceCharacters: number;
|
|
2471
|
+
/** Never present: a machine-translation API reports no token usage at all. */
|
|
2472
|
+
readonly inputTokens?: undefined;
|
|
2473
|
+
/** Never present: a machine-translation API reports no token usage at all. */
|
|
2474
|
+
readonly outputTokens?: undefined;
|
|
1027
2475
|
}
|
|
1028
|
-
|
|
1029
2476
|
/**
|
|
1030
|
-
*
|
|
1031
|
-
*
|
|
1032
|
-
* one: `i18next-json` is i18next's nested JSON with its plural key suffixes, `vue-i18n-json` is Vue
|
|
1033
|
-
* I18n's JSON with its pipe-separated plural values, `next-intl-json` is next-intl's ICU-message JSON,
|
|
1034
|
-
* and `ngx-translate-json` is ngx-translate's nested JSON. The remaining eight are `xliff` (the XLIFF
|
|
1035
|
-
* interchange XML), `yaml` (plain nested YAML), `arb` (Flutter's Application Resource Bundle),
|
|
1036
|
-
* `properties` (Java and Spring `.properties` files), `apple-strings` (Apple's flat `.strings`
|
|
1037
|
-
* localization format for iOS and macOS), `apple-xcstrings` (Apple's Xcode String Catalog, a single
|
|
1038
|
-
* JSON document holding every locale of a `.xcstrings` catalogue together), `android-xml` (Android's
|
|
1039
|
-
* `res/values/strings.xml` resource format, including `<plurals>` and `translatable="false"` entries),
|
|
1040
|
-
* and `gettext-po` (GNU gettext's `.po` and `.pot` catalogs, including `msgctxt` disambiguation and
|
|
1041
|
-
* `msgid_plural`/`msgstr[n]` plural forms keyed by the file's own `Plural-Forms` index count).
|
|
1042
|
-
*
|
|
1043
|
-
* The set is closed: a format outside it cannot be represented, so adding one means adding a member
|
|
1044
|
-
* here and an adapter that claims it.
|
|
2477
|
+
* What a run would send, discriminated on {@link unit} so the billed dimension is always present
|
|
2478
|
+
* and the other one is never readable as a zero.
|
|
1045
2479
|
*/
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
2480
|
+
type RunEstimateQuantity = TokenRunQuantity | CharacterRunQuantity;
|
|
2481
|
+
interface PricedEstimateMoney {
|
|
2482
|
+
/** Always `priced`: a rate was found and applied. */
|
|
2483
|
+
readonly pricing: "priced";
|
|
2484
|
+
/** The rate card's currency code. */
|
|
2485
|
+
readonly currency: string;
|
|
2486
|
+
/**
|
|
2487
|
+
* The date the rates were read, as the config declared it. Print it beside any figure: a rate
|
|
2488
|
+
* card is exactly as current as this date.
|
|
2489
|
+
*/
|
|
2490
|
+
readonly asOf: string;
|
|
2491
|
+
/** The per-locale breakdown, in the same order as {@link RunSummary.locales}. */
|
|
2492
|
+
readonly locales: readonly PricedLocaleEstimate[];
|
|
2493
|
+
/** The estimated total, in {@link currency}. */
|
|
2494
|
+
readonly cost: number;
|
|
2495
|
+
}
|
|
2496
|
+
interface UnpricedEstimateMoney {
|
|
2497
|
+
/** Why no currency figure is present. See {@link EstimatePricing}. */
|
|
2498
|
+
readonly pricing: Exclude<EstimatePricing, "priced">;
|
|
2499
|
+
/** The per-locale breakdown, in the same order as {@link RunSummary.locales}. */
|
|
2500
|
+
readonly locales: readonly UnpricedLocaleEstimate[];
|
|
2501
|
+
/** Never present: there is no rate to express a figure in. */
|
|
2502
|
+
readonly currency?: undefined;
|
|
2503
|
+
/** Never present: there is no rate, so there is no date it was read on. */
|
|
2504
|
+
readonly asOf?: undefined;
|
|
2505
|
+
/** Never present: an absent rate is reported as absent, never as a cost of zero. */
|
|
2506
|
+
readonly cost?: undefined;
|
|
2507
|
+
}
|
|
2508
|
+
interface TokenPricedRunEstimate extends TokenRunQuantity, PricedEstimateMoney {
|
|
2509
|
+
}
|
|
2510
|
+
interface CharacterPricedRunEstimate extends CharacterRunQuantity, PricedEstimateMoney {
|
|
2511
|
+
}
|
|
2512
|
+
interface TokenUnpricedRunEstimate extends TokenRunQuantity, UnpricedEstimateMoney {
|
|
2513
|
+
}
|
|
2514
|
+
interface CharacterUnpricedRunEstimate extends CharacterRunQuantity, UnpricedEstimateMoney {
|
|
2515
|
+
}
|
|
1063
2516
|
/**
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
* a placeholder in that format.
|
|
1067
|
-
*
|
|
1068
|
-
* The fields are: `key`, the entry's identifier within its namespace, non-empty and already flattened
|
|
1069
|
-
* to the dotted path an adapter produced; `namespace`, the namespace the entry belongs to, empty when
|
|
1070
|
-
* the format has no namespacing; `value`, the translatable string itself, which is untrusted input and
|
|
1071
|
-
* may be empty; `description` and `meaning`, both optional free-form context that a provider is told to
|
|
1072
|
-
* use only for disambiguation and never to translate or echo back; `placeholders`, the format's
|
|
1073
|
-
* placeholder tokens in document order, used for the output integrity check and to decide which entries
|
|
1074
|
-
* a placeholder-blind provider must withhold; and `isPlural`, whether the entry carries plural forms
|
|
1075
|
-
* (an i18next plural key suffix, a vue-i18n pipe-separated value, or an ICU plural), which the adapter
|
|
1076
|
-
* decides and which feeds the content hash.
|
|
2517
|
+
* An estimate a rate could be applied to: the config supplied a rate for `rateKey` in the right
|
|
2518
|
+
* unit, so the run carries a currency figure and the date that rate was read.
|
|
1077
2519
|
*/
|
|
1078
|
-
|
|
1079
|
-
key: z.ZodString;
|
|
1080
|
-
namespace: z.ZodString;
|
|
1081
|
-
value: z.ZodString;
|
|
1082
|
-
description: z.ZodOptional<z.ZodString>;
|
|
1083
|
-
meaning: z.ZodOptional<z.ZodString>;
|
|
1084
|
-
placeholders: z.ZodReadonly<z.ZodArray<z.ZodString>>;
|
|
1085
|
-
isPlural: z.ZodBoolean;
|
|
1086
|
-
}, z.core.$strip>;
|
|
2520
|
+
type PricedRunEstimate = TokenPricedRunEstimate | CharacterPricedRunEstimate;
|
|
1087
2521
|
/**
|
|
1088
|
-
*
|
|
1089
|
-
*
|
|
2522
|
+
* An estimate carrying quantity and no money, with `pricing` saying why. verbatra ships no prices
|
|
2523
|
+
* of its own and will not guess one, so the absence is reported rather than rendered as a cost of
|
|
2524
|
+
* zero.
|
|
1090
2525
|
*/
|
|
1091
|
-
type
|
|
1092
|
-
|
|
2526
|
+
type UnpricedRunEstimate = TokenUnpricedRunEstimate | CharacterUnpricedRunEstimate;
|
|
1093
2527
|
/**
|
|
1094
|
-
*
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
2528
|
+
* A pre-run estimate: what a run would send, and what that would cost at the rates the project
|
|
2529
|
+
* supplied. It is computed without constructing a provider, reading an API key, or making a network
|
|
2530
|
+
* call.
|
|
2531
|
+
*
|
|
2532
|
+
* It bounds the plan, not the invoice. Every provider call a live run schedules is counted,
|
|
2533
|
+
* including the plural-generation batches; the prompt is measured from the request payload that
|
|
2534
|
+
* would actually be sent rather than modelled; and the response is sized from the source value plus
|
|
2535
|
+
* an expansion allowance. Against that plan a live run usually spends less, because it consults the
|
|
2536
|
+
* translation memory and collapses identical source strings.
|
|
2537
|
+
*
|
|
2538
|
+
* It can also spend more, and {@link EstimateCaveatCode} names every way: the provider SDKs retry a
|
|
2539
|
+
* failed call underneath each planned request, an incomplete response triggers a repair round or a
|
|
2540
|
+
* recursive split, a target language can expand past the allowance, and the token figure is a
|
|
2541
|
+
* character heuristic rather than the provider's own tokenizer. Treat it as a planning figure, not
|
|
2542
|
+
* as a ceiling that cannot be crossed.
|
|
2543
|
+
*
|
|
2544
|
+
* It covers {@link translate} and nothing else. `retranslateEntry` constructs a provider and calls
|
|
2545
|
+
* it on its own path, reached from the Studio dashboard and the agent tools, and no estimate here
|
|
2546
|
+
* sees that spend.
|
|
2547
|
+
*
|
|
2548
|
+
* Branch on {@link pricing}: a {@link PricedRunEstimate} carries `cost`, `currency` and `asOf`, and
|
|
2549
|
+
* an {@link UnpricedRunEstimate} carries none of them and says why.
|
|
1097
2550
|
*/
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
/** The
|
|
1102
|
-
readonly
|
|
1103
|
-
/**
|
|
1104
|
-
readonly
|
|
1105
|
-
/** Entries addressable by key, in the order the source file defined them. */
|
|
1106
|
-
readonly entries: ReadonlyMap<string, TranslationEntry>;
|
|
2551
|
+
type RunEstimate = PricedRunEstimate | UnpricedRunEstimate;
|
|
2552
|
+
/** A condition the SDK reported on a locale. See {@link SdkNoticeCode}. */
|
|
2553
|
+
interface SdkNotice {
|
|
2554
|
+
/** The stable notice code. Branch on this, not on the message. */
|
|
2555
|
+
readonly code: SdkNoticeCode;
|
|
2556
|
+
/** A human-readable description of the condition. */
|
|
2557
|
+
readonly message: string;
|
|
1107
2558
|
}
|
|
1108
|
-
|
|
1109
2559
|
/**
|
|
1110
|
-
*
|
|
1111
|
-
*
|
|
1112
|
-
* returned, never thrown: a mismatch is data the caller decides what to do about, not a failure.
|
|
2560
|
+
* Any notice attached to a locale: either one the provider raised, such as a downgraded formality
|
|
2561
|
+
* or an ignored glossary, or one the SDK raised. Discriminate on `code`.
|
|
1113
2562
|
*/
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
2563
|
+
type LocaleNotice = ProviderNotice | SdkNotice;
|
|
2564
|
+
/**
|
|
2565
|
+
* A translated key the provider layer flagged as worth a human look. The translation was still
|
|
2566
|
+
* written; these are advisory quality signals, not rejections.
|
|
2567
|
+
*/
|
|
2568
|
+
interface NeedsReviewEntry {
|
|
2569
|
+
/** The key that was flagged. */
|
|
2570
|
+
readonly key: string;
|
|
2571
|
+
/** Why it was flagged. A key can carry more than one reason. */
|
|
2572
|
+
readonly reasons: readonly ReviewReasonCode[];
|
|
1123
2573
|
}
|
|
1124
|
-
|
|
1125
2574
|
/**
|
|
1126
|
-
*
|
|
1127
|
-
*
|
|
2575
|
+
* A key whose translation was reused from the translation memory even though its source string had
|
|
2576
|
+
* changed, because the earlier source it was translated from is close enough to the current one.
|
|
2577
|
+
*
|
|
2578
|
+
* It is reported apart from {@link LocaleSummary.cacheHits} on purpose: an exact cache hit is the
|
|
2579
|
+
* same string it always was, while this is a translation of text that is no longer the source text.
|
|
2580
|
+
* {@link similarity} and {@link previousSource} are the evidence for the reuse, so a reader can
|
|
2581
|
+
* judge it rather than take it on trust.
|
|
2582
|
+
*
|
|
2583
|
+
* A reuse is written to the locale file but deliberately not locked: the key keeps the lock-file
|
|
2584
|
+
* baseline it already had, so it stays changed, is offered and flagged again on every later run at
|
|
2585
|
+
* no provider cost, and settles only once a real translation lands. Every key listed here also
|
|
2586
|
+
* carries `FUZZY_CACHE_REUSE` in {@link LocaleSummary.needsReview}.
|
|
1128
2587
|
*/
|
|
1129
|
-
interface
|
|
1130
|
-
/** The
|
|
1131
|
-
readonly
|
|
2588
|
+
interface FuzzyCacheHit {
|
|
2589
|
+
/** The key whose translation was reused. */
|
|
2590
|
+
readonly key: string;
|
|
2591
|
+
/** The key's source text at the time this translation was produced. */
|
|
2592
|
+
readonly previousSource: string;
|
|
1132
2593
|
/**
|
|
1133
|
-
*
|
|
1134
|
-
*
|
|
2594
|
+
* How alike {@link previousSource} and the current source are, from `0` to `1`, as measured by
|
|
2595
|
+
* the configured `fuzzyCache.threshold`. Always at or above that threshold.
|
|
1135
2596
|
*/
|
|
1136
|
-
readonly
|
|
2597
|
+
readonly similarity: number;
|
|
2598
|
+
}
|
|
2599
|
+
/** A row of an imported handoff that could not be read. Reported rather than aborting the import. */
|
|
2600
|
+
interface MalformedRowReport {
|
|
2601
|
+
/** The row's 1-based index within its sheet or file. */
|
|
2602
|
+
readonly row: number;
|
|
1137
2603
|
/**
|
|
1138
|
-
*
|
|
1139
|
-
*
|
|
1140
|
-
* metadata marks as not translatable. These are never translated, hashed, diffed, or checked for
|
|
1141
|
-
* placeholder or ICU integrity. Whether one survives a later rewrite of the same file depends on
|
|
1142
|
-
* the adapter: an adapter that reconstructs the whole file from `resource.entries` alone drops it,
|
|
1143
|
-
* while an adapter that patches only the entries it touches into the existing document (for
|
|
1144
|
-
* example `apple-xcstrings`) preserves it verbatim. Empty for a file with no such leaves.
|
|
2604
|
+
* The 1-based physical line in the source file, for delimited formats where a quoted value can
|
|
2605
|
+
* span several lines. Absent for `.xlsx`, which has rows but no lines.
|
|
1145
2606
|
*/
|
|
1146
|
-
readonly
|
|
2607
|
+
readonly line?: number;
|
|
2608
|
+
/** The column whose value was missing or unusable. */
|
|
2609
|
+
readonly column: string;
|
|
1147
2610
|
}
|
|
1148
2611
|
/**
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
2612
|
+
* A key that appeared more than once in an imported handoff. The first occurrence wins and the rest
|
|
2613
|
+
* are reported here, so a translator who duplicated a row learns which value was actually used.
|
|
2614
|
+
*/
|
|
2615
|
+
interface DuplicateKeyReport {
|
|
2616
|
+
/** The key that appeared more than once. */
|
|
2617
|
+
readonly key: string;
|
|
2618
|
+
/** The 1-based row index of the ignored occurrence. */
|
|
2619
|
+
readonly row: number;
|
|
2620
|
+
/** The 1-based physical line of the ignored occurrence, for delimited formats. Absent for `.xlsx`. */
|
|
2621
|
+
readonly line?: number;
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* Everything that happened for one locale during a run. The key lists are disjoint accounts of what
|
|
2625
|
+
* became of each key, so a caller can reconstruct the whole run without re-reading any file.
|
|
1151
2626
|
*
|
|
1152
|
-
*
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1155
|
-
* neither shape implements this interface directly, and either way it first needs its member added
|
|
1156
|
-
* to core's {@link SupportedFormat}.
|
|
2627
|
+
* A locale that failed outright still appears here with `status: "failed"` and an `error`, rather
|
|
2628
|
+
* than the whole run throwing. That is the central contract of {@link translate}: one unreachable
|
|
2629
|
+
* provider or one unwritable file does not discard the locales that succeeded.
|
|
1157
2630
|
*/
|
|
1158
|
-
interface
|
|
1159
|
-
/** The
|
|
1160
|
-
readonly
|
|
2631
|
+
interface LocaleSummary {
|
|
2632
|
+
/** The target locale this summary describes. */
|
|
2633
|
+
readonly locale: string;
|
|
1161
2634
|
/**
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
1164
|
-
*
|
|
1165
|
-
* @param filePath - The path of the file under consideration.
|
|
1166
|
-
* @param sample - An optional leading content sample to aid detection.
|
|
1167
|
-
* @returns True if this adapter could handle the file. Several adapters may return true for the same
|
|
1168
|
-
* file (all JSON adapters claim `.json`); the caller disambiguates by explicit format.
|
|
2635
|
+
* `succeeded` when everything asked for was done, `partial` when some keys were translated and
|
|
2636
|
+
* others were not, and `failed` when the locale produced no usable result.
|
|
1169
2637
|
*/
|
|
1170
|
-
|
|
2638
|
+
readonly status: "succeeded" | "partial" | "failed";
|
|
2639
|
+
/** Keys newly translated by the provider in this run. */
|
|
2640
|
+
readonly translated: readonly string[];
|
|
2641
|
+
/** Keys already up to date against the lock-file baseline, so no provider call was made. */
|
|
2642
|
+
readonly unchanged: readonly string[];
|
|
2643
|
+
/** Keys present in this locale but no longer in the source. Reported, and removed only when pruning. */
|
|
2644
|
+
readonly orphaned: readonly string[];
|
|
2645
|
+
/** Orphaned keys that were actually removed, which happens only when the run was asked to prune. */
|
|
2646
|
+
readonly pruned: readonly string[];
|
|
1171
2647
|
/**
|
|
1172
|
-
*
|
|
1173
|
-
*
|
|
1174
|
-
* @param filePath - The file to read.
|
|
1175
|
-
* @param locale - The locale to tag the resource with.
|
|
1176
|
-
* @returns The parsed resource and the keys whose values are invalid for the format.
|
|
1177
|
-
* @throws `AdapterError` when the content is malformed, oversized, or structurally invalid
|
|
1178
|
-
* (the implementation names the specific codes). A missing or unopenable path instead rejects with
|
|
1179
|
-
* the underlying filesystem error.
|
|
2648
|
+
* Keys whose source text is not valid ICU. They are skipped rather than sent to the provider,
|
|
2649
|
+
* because a broken source message cannot yield a sound translation.
|
|
1180
2650
|
*/
|
|
1181
|
-
|
|
2651
|
+
readonly invalidIcuSource: readonly string[];
|
|
1182
2652
|
/**
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1185
|
-
* @
|
|
1186
|
-
* @param filePath - The destination file.
|
|
1187
|
-
* @throws `AdapterError` if the resource cannot be represented in the format; rejects with the
|
|
1188
|
-
* underlying filesystem error on a write failure.
|
|
2653
|
+
* Keys served from the translation memory instead of the provider, and so not paid for. Only
|
|
2654
|
+
* exact matches: a reuse for a source string that has since changed is reported in
|
|
2655
|
+
* {@link fuzzyHits} instead, never here.
|
|
1189
2656
|
*/
|
|
1190
|
-
|
|
2657
|
+
readonly cacheHits: readonly string[];
|
|
1191
2658
|
/**
|
|
1192
|
-
*
|
|
1193
|
-
*
|
|
1194
|
-
*
|
|
1195
|
-
*
|
|
2659
|
+
* Keys whose translation was reused for a source string that had changed, with the score and the
|
|
2660
|
+
* earlier source behind each reuse. Always empty unless `fuzzyCache.enabled` was set. These keys
|
|
2661
|
+
* are written but not locked, so they reappear here on every run until they are really
|
|
2662
|
+
* translated. See {@link FuzzyCacheHit}.
|
|
1196
2663
|
*/
|
|
1197
|
-
|
|
2664
|
+
readonly fuzzyHits: readonly FuzzyCacheHit[];
|
|
1198
2665
|
/**
|
|
1199
|
-
*
|
|
1200
|
-
*
|
|
1201
|
-
* message syntax to violate and returns true for any value.
|
|
1202
|
-
*
|
|
1203
|
-
* @param value - The candidate translated value to validate.
|
|
1204
|
-
* @returns True when the value is valid for the format's message syntax. Does not throw; an
|
|
1205
|
-
* unparseable value returns false.
|
|
2666
|
+
* Keys whose translation was refused by the integrity gate, for instance because it dropped a
|
|
2667
|
+
* placeholder. The previous translation, if any, is left untouched.
|
|
1206
2668
|
*/
|
|
1207
|
-
|
|
2669
|
+
readonly integrityMismatches: readonly string[];
|
|
2670
|
+
/** Keys the provider failed to translate, for instance because their sub-batch errored. */
|
|
2671
|
+
readonly providerFailures: readonly string[];
|
|
2672
|
+
/**
|
|
2673
|
+
* Keys whose plural categories were generated for the target language rather than translated one
|
|
2674
|
+
* by one. On a dry run, the forms a live run would generate: generation is a provider call of
|
|
2675
|
+
* its own, so a dry run reports it rather than reporting nothing.
|
|
2676
|
+
*/
|
|
2677
|
+
readonly generated: readonly string[];
|
|
2678
|
+
/** Keys not translated because the token budget was exhausted under `stop` behavior. */
|
|
2679
|
+
readonly budgetWithheld: readonly string[];
|
|
2680
|
+
/** Token usage for this locale. Absent when the provider does not report usage. */
|
|
2681
|
+
readonly usage?: UsageSummary;
|
|
2682
|
+
/** Provider and SDK notices raised while running this locale. Always present, possibly empty. */
|
|
2683
|
+
readonly notices: readonly LocaleNotice[];
|
|
2684
|
+
/** Translated keys flagged as worth a human look. The translations were still written. */
|
|
2685
|
+
readonly needsReview: readonly NeedsReviewEntry[];
|
|
2686
|
+
/** Keys left with no translation after the run, whatever the cause. */
|
|
2687
|
+
readonly unfilled: readonly string[];
|
|
2688
|
+
/** Unreadable rows from an imported handoff. Always empty for a {@link translate} run. */
|
|
2689
|
+
readonly malformedRows: readonly MalformedRowReport[];
|
|
2690
|
+
/** Repeated keys from an imported handoff. Always empty for a {@link translate} run. */
|
|
2691
|
+
readonly duplicateKeys: readonly DuplicateKeyReport[];
|
|
1208
2692
|
/**
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1211
|
-
* families define one, for different reasons:
|
|
1212
|
-
*
|
|
1213
|
-
* - The ICU formats (next-intl and ARB) compare branch by branch, because flattening a
|
|
1214
|
-
* plural/select value loses which branch a placeholder came from.
|
|
1215
|
-
* - The double-brace formats (i18next, ngx-translate, and YAML) add a one-directional
|
|
1216
|
-
* single-brace check on top of their flat multiset comparison: a `{name}`-shaped token present
|
|
1217
|
-
* in the translated value but absent from the source is reported as `extra`. Single-brace text
|
|
1218
|
-
* is ordinary literal content under these formats' default delimiters, so it is never required
|
|
1219
|
-
* to survive a translation, but inventing one is never a legitimate translation either.
|
|
1220
|
-
*
|
|
1221
|
-
* Absent for the remaining adapters, which are compared via `extractPlaceholders` plus a flat
|
|
1222
|
-
* multiset check.
|
|
2693
|
+
* Why this locale failed, when the failure came from a thrown error. The `code` is the failure's
|
|
2694
|
+
* own code where it has one, and `LOCALE_FAILED` otherwise. Never carries a secret.
|
|
1223
2695
|
*
|
|
1224
|
-
*
|
|
1225
|
-
*
|
|
1226
|
-
*
|
|
2696
|
+
* Absent unless `status` is `failed`, but a `failed` locale does not always carry it: a locale
|
|
2697
|
+
* whose every key was withheld by the integrity gate, a provider failure, or the token budget is
|
|
2698
|
+
* `failed` with nothing thrown, so this stays undefined and the withheld keys are the account of
|
|
2699
|
+
* what went wrong. Treat it as an optional detail on a failure, never as the failure test.
|
|
1227
2700
|
*/
|
|
1228
|
-
|
|
2701
|
+
readonly error?: {
|
|
2702
|
+
/** The failure's own code where it had one, and `LOCALE_FAILED` otherwise. */
|
|
2703
|
+
readonly code: string;
|
|
2704
|
+
/** A human-readable description of the failure. Never contains a secret. */
|
|
2705
|
+
readonly message: string;
|
|
2706
|
+
};
|
|
1229
2707
|
}
|
|
1230
|
-
|
|
1231
2708
|
/**
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
2709
|
+
* The result of a whole run, returned by {@link translate}, {@link watch}, and
|
|
2710
|
+
* {@link importWorkbook}.
|
|
1234
2711
|
*
|
|
1235
|
-
*
|
|
1236
|
-
*
|
|
1237
|
-
*
|
|
1238
|
-
* claimed the file, which is routine since every JSON adapter claims `.json`; it reports the competing
|
|
1239
|
-
* candidates instead of guessing, and the caller resolves it by naming a format explicitly.
|
|
2712
|
+
* Per-locale outcomes are data, not exceptions: inspect `failed` and `partial` to decide an exit
|
|
2713
|
+
* code rather than relying on the call to throw. Only whole-run failures, such as an invalid config
|
|
2714
|
+
* or an unreadable source file, throw an {@link SdkError}.
|
|
1240
2715
|
*/
|
|
1241
|
-
|
|
1242
|
-
/**
|
|
1243
|
-
readonly
|
|
1244
|
-
/** The adapter to read and write the file with. */
|
|
1245
|
-
readonly adapter: FormatAdapter;
|
|
1246
|
-
} | {
|
|
1247
|
-
/** No registered adapter claimed the file. */
|
|
1248
|
-
readonly status: "no-match";
|
|
1249
|
-
/** The file that could not be matched. */
|
|
1250
|
-
readonly filePath: string;
|
|
2716
|
+
interface RunSummary {
|
|
2717
|
+
/** True when the run computed everything but wrote nothing and called no provider. */
|
|
2718
|
+
readonly dryRun: boolean;
|
|
1251
2719
|
/**
|
|
1252
|
-
* The
|
|
1253
|
-
*
|
|
2720
|
+
* The full per-locale account. A {@link translate} or {@link watch} run reports them in
|
|
2721
|
+
* configured target order, whatever order a `locales` subset was given in and whatever order
|
|
2722
|
+
* concurrent locales finished in. An {@link importWorkbook} run does not: it reports them in the
|
|
2723
|
+
* order the handoff yielded them, then appends the configured locales the handoff had nothing
|
|
2724
|
+
* for. Match an entry on its `locale` rather than on its position.
|
|
1254
2725
|
*/
|
|
1255
|
-
readonly
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
readonly status: "ambiguous";
|
|
1259
|
-
/** The file that several adapters claimed. */
|
|
1260
|
-
readonly filePath: string;
|
|
1261
|
-
/** The competing formats. Resolve the ambiguity by naming one of them explicitly. */
|
|
1262
|
-
readonly candidates: readonly SupportedFormat[];
|
|
1263
|
-
};
|
|
1264
|
-
/** Options for {@link AdapterRegistry.resolve}: select a format explicitly, or aid detection. */
|
|
1265
|
-
interface ResolveOptions {
|
|
1266
|
-
/** A leading content sample to aid detection. Ignored when `format` is given. */
|
|
1267
|
-
readonly sample?: string;
|
|
1268
|
-
/** Bypass detection and select this format explicitly. */
|
|
1269
|
-
readonly format?: SupportedFormat;
|
|
1270
|
-
}
|
|
1271
|
-
/**
|
|
1272
|
-
* Holds the registered adapters and resolves one for a file. Open for extension: adapters attach
|
|
1273
|
-
* through {@link AdapterRegistry.register} without changing resolution logic, and resolution never
|
|
1274
|
-
* throws.
|
|
1275
|
-
*/
|
|
1276
|
-
declare class AdapterRegistry {
|
|
1277
|
-
private readonly adapters;
|
|
2726
|
+
readonly locales: readonly LocaleSummary[];
|
|
2727
|
+
/** Names of the locales whose status is `succeeded`. */
|
|
2728
|
+
readonly succeeded: readonly string[];
|
|
1278
2729
|
/**
|
|
1279
|
-
*
|
|
1280
|
-
*
|
|
1281
|
-
*
|
|
1282
|
-
* @param adapter - The adapter to add.
|
|
1283
|
-
* @returns This registry, for chaining.
|
|
2730
|
+
* Names of the locales whose status is `partial`: written to disk, but with keys still missing.
|
|
2731
|
+
* Check this alongside `failed` before treating a run as clean. The CLI exits `1` on a partial
|
|
2732
|
+
* locale for that reason.
|
|
1284
2733
|
*/
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
2734
|
+
readonly partial: readonly string[];
|
|
2735
|
+
/** Names of the locales whose status is `failed`. Check this before treating a run as clean. */
|
|
2736
|
+
readonly failed: readonly string[];
|
|
2737
|
+
/** Token usage summed across every locale. Absent when the provider does not report usage. */
|
|
2738
|
+
readonly usage?: UsageSummary;
|
|
2739
|
+
/** The token budget in force, present only when the config set one. */
|
|
2740
|
+
readonly budget?: RunBudget;
|
|
1289
2741
|
/**
|
|
1290
|
-
*
|
|
1291
|
-
*
|
|
1292
|
-
* @param filePath - The file to resolve an adapter for.
|
|
1293
|
-
* @param options - `format` selects explicitly and skips detection; `sample` aids detection.
|
|
1294
|
-
* @returns A structured {@link AdapterResolution}: `resolved`, `no-match`, or `ambiguous`. Never
|
|
1295
|
-
* throws; an unresolvable file is a status, not an exception.
|
|
2742
|
+
* What the run would have cost, present only when it was asked for a pre-run estimate. Always a
|
|
2743
|
+
* dry run: an estimate constructs no provider and spends nothing.
|
|
1296
2744
|
*/
|
|
1297
|
-
|
|
2745
|
+
readonly estimate?: RunEstimate;
|
|
1298
2746
|
}
|
|
1299
2747
|
|
|
2748
|
+
/**
|
|
2749
|
+
* Where a finished run stands against its token budget.
|
|
2750
|
+
*
|
|
2751
|
+
* - `within`: the run never reached its ceiling.
|
|
2752
|
+
* - `stopped-before-ceiling`: under `stop`, a request was withheld because sending it would have
|
|
2753
|
+
* passed the ceiling, while the counted total was still below it.
|
|
2754
|
+
* - `reached`: the counted total reached or passed the ceiling.
|
|
2755
|
+
*/
|
|
2756
|
+
type BudgetStanding = "within" | "stopped-before-ceiling" | "reached";
|
|
2757
|
+
/**
|
|
2758
|
+
* Classifies a {@link RunBudget} into its {@link BudgetStanding}, so every surface that reports a
|
|
2759
|
+
* budget tells a `stop` run halted short of its ceiling apart from one that actually reached it.
|
|
2760
|
+
*/
|
|
2761
|
+
declare function budgetStanding(budget: RunBudget): BudgetStanding;
|
|
2762
|
+
|
|
1300
2763
|
/** One locale's counts in a {@link CheckSummary}. */
|
|
1301
2764
|
interface LocaleCheckSummary {
|
|
1302
2765
|
/** The target locale these counts describe. */
|
|
@@ -1309,6 +2772,12 @@ interface LocaleCheckSummary {
|
|
|
1309
2772
|
readonly upToDate: number;
|
|
1310
2773
|
/** True when this locale has nothing missing and nothing stale. */
|
|
1311
2774
|
readonly inSync: boolean;
|
|
2775
|
+
/**
|
|
2776
|
+
* Every source string this locale translates more than one way under different keys, present only
|
|
2777
|
+
* when {@link CheckInput.consistency} is true (an empty array then means the locale is
|
|
2778
|
+
* consistent). This is a report and nothing more: it never changes `inSync` or any count.
|
|
2779
|
+
*/
|
|
2780
|
+
readonly inconsistencies?: readonly InconsistencyGroup[];
|
|
1312
2781
|
}
|
|
1313
2782
|
/** The result of {@link check}: per-locale counts plus one project-wide verdict. */
|
|
1314
2783
|
interface CheckSummary {
|
|
@@ -1325,6 +2794,11 @@ interface CheckInput {
|
|
|
1325
2794
|
readonly cwd?: string;
|
|
1326
2795
|
/** Restrict the report to these target locales. Defaults to every configured target locale. */
|
|
1327
2796
|
readonly locales?: readonly string[];
|
|
2797
|
+
/**
|
|
2798
|
+
* Also report, per locale, every source string translated more than one way under different keys
|
|
2799
|
+
* (see {@link LocaleCheckSummary.inconsistencies}). Defaults to false.
|
|
2800
|
+
*/
|
|
2801
|
+
readonly consistency?: boolean;
|
|
1328
2802
|
}
|
|
1329
2803
|
/** Injectable dependencies for {@link check}. Every field has a working default. */
|
|
1330
2804
|
interface CheckDeps {
|
|
@@ -1345,6 +2819,16 @@ interface CheckDeps {
|
|
|
1345
2819
|
*
|
|
1346
2820
|
* Use {@link diff} instead when you need the key names rather than the counts.
|
|
1347
2821
|
*
|
|
2822
|
+
* With {@link CheckInput.consistency} set, each locale also lists the source strings it translates
|
|
2823
|
+
* more than one way under different keys. Only keys that are up to date are compared, since a stale
|
|
2824
|
+
* translation belongs to an older source text. Values are compared after Unicode NFC normalization,
|
|
2825
|
+
* folding line endings, and trimming leading and trailing whitespace; internal whitespace and letter
|
|
2826
|
+
* case count. Keys whose description, meaning, plural flag, or gettext `msgctxt` differ are never
|
|
2827
|
+
* grouped, and neither are the different plural forms of one key: a format that stores each form
|
|
2828
|
+
* under its own key (i18next, Apple `.stringsdict` and `.xcstrings`, Android, gettext) is compared
|
|
2829
|
+
* per plural category or gettext `msgstr` index. The report never affects `inSync`, a count, or any
|
|
2830
|
+
* file, and a translation identical to its own source is not a finding here.
|
|
2831
|
+
*
|
|
1348
2832
|
* Note that a malformed target locale file surfaces the adapter's own error and code rather than a
|
|
1349
2833
|
* wrapped {@link SdkError}, because only source reads are wrapped. Its message names the offending
|
|
1350
2834
|
* locale and the resolved path. A caller that maps SDK codes should be ready for an unrecognized
|
|
@@ -1366,6 +2850,121 @@ interface CheckDeps {
|
|
|
1366
2850
|
*/
|
|
1367
2851
|
declare function check(input: CheckInput, deps?: CheckDeps): Promise<CheckSummary>;
|
|
1368
2852
|
|
|
2853
|
+
/**
|
|
2854
|
+
* Why an unused-key report could not be produced at all.
|
|
2855
|
+
*
|
|
2856
|
+
* - `EXTRACT_NOT_CONFIGURED`: the config carries no `extract` block, so there is no source to scan.
|
|
2857
|
+
* - `EXTRACT_FS_UNSUPPORTED`: the supplied file system implements no `readDirectory`.
|
|
2858
|
+
* - `FRAMEWORK_NOT_MODELED`: the configured format is loaded by a runtime (next-intl, Vue I18n, or
|
|
2859
|
+
* ngx-translate) whose key lookups the i18next source scan does not model.
|
|
2860
|
+
* - `NO_SOURCE_FILES`: there is no source file under the configured roots at all.
|
|
2861
|
+
* - `NO_REFERENCES_FOUND`: the scanned files name no translation key and hold no dynamic key site,
|
|
2862
|
+
* while the catalog is not empty, so the scan most likely looked at the wrong code.
|
|
2863
|
+
*/
|
|
2864
|
+
type UnusedKeysNotRunReason = "EXTRACT_NOT_CONFIGURED" | "EXTRACT_FS_UNSUPPORTED" | "FRAMEWORK_NOT_MODELED" | "NO_SOURCE_FILES" | "NO_REFERENCES_FOUND";
|
|
2865
|
+
/**
|
|
2866
|
+
* Why a completed scan cannot rule a key unused with confidence.
|
|
2867
|
+
*
|
|
2868
|
+
* - `dynamic-keys`: a key argument has no static part (`t(key)`, a template literal with no static
|
|
2869
|
+
* head, a concatenation), so any catalog key could be the one it reaches.
|
|
2870
|
+
* - `dynamic-key-prefix`: a `keyPrefix` option or a `getFixedT` prefix argument is not a static
|
|
2871
|
+
* string, so every key called under it is unknown.
|
|
2872
|
+
* - `trans-without-key`: a `Trans` element carries no static `i18nKey`.
|
|
2873
|
+
* - `aliased-translate-function`: the translate function is assigned somewhere its calls cannot be
|
|
2874
|
+
* followed, such as an object property.
|
|
2875
|
+
* - `translate-function-escapes`: a translate function bound from a recognised source appears
|
|
2876
|
+
* somewhere other than a direct call, its own binding, a local alias, or the `t` attribute of a
|
|
2877
|
+
* `Trans` or `Translation` element, so code the scan cannot follow may call it with any key.
|
|
2878
|
+
* - `unrecognised-translate-source`: `useTranslation`, `withTranslation`, `getFixedT`, a
|
|
2879
|
+
* `Translation` render prop, a member `t`, or a `t` import is used in a shape the scan does not
|
|
2880
|
+
* recognise, so the prefix and keys of the function it yields are unknown.
|
|
2881
|
+
* - `template-files-not-scanned`: the roots hold template files (`.vue`, `.svelte`, `.html`, and
|
|
2882
|
+
* similar) the scan does not read.
|
|
2883
|
+
* - `incomplete-scan`: a file or directory could not be read to its end.
|
|
2884
|
+
*/
|
|
2885
|
+
type UnusedKeysUnreliableReason = "dynamic-keys" | "dynamic-key-prefix" | "trans-without-key" | "aliased-translate-function" | "translate-function-escapes" | "unrecognised-translate-source" | "template-files-not-scanned" | "incomplete-scan";
|
|
2886
|
+
/** One place that makes an unused-key report unreliable. */
|
|
2887
|
+
interface UnusedKeysSite {
|
|
2888
|
+
/** The file or directory, relative to the run's working directory, with forward slashes. */
|
|
2889
|
+
readonly file: string;
|
|
2890
|
+
/** The one-based line, when the reason points at a line. */
|
|
2891
|
+
readonly line?: number;
|
|
2892
|
+
/** For `incomplete-scan`, why the file was skipped (for example `too-large`). */
|
|
2893
|
+
readonly detail?: string;
|
|
2894
|
+
}
|
|
2895
|
+
/** One reason an unused-key report is unreliable, with the places that caused it. */
|
|
2896
|
+
interface UnusedKeysUnreliability {
|
|
2897
|
+
/** The stable reason code. */
|
|
2898
|
+
readonly reason: UnusedKeysUnreliableReason;
|
|
2899
|
+
/** How many places caused it. For template files this can exceed the listed `sites`. */
|
|
2900
|
+
readonly count: number;
|
|
2901
|
+
/**
|
|
2902
|
+
* The places, deduplicated on file and line. Every place is listed, except template files, which
|
|
2903
|
+
* are capped at the first 20.
|
|
2904
|
+
*/
|
|
2905
|
+
readonly sites: readonly UnusedKeysSite[];
|
|
2906
|
+
}
|
|
2907
|
+
/**
|
|
2908
|
+
* A source-catalog key in an unused-key report. `key` is the key as a person and the source spell
|
|
2909
|
+
* it, with the format's internal encoding undone (`Welcome. Enjoy` rather than `Welcome\. Enjoy`,
|
|
2910
|
+
* a gettext `msgctxt` shown as `context|msgid`); `extract.unused.ignore` patterns match it.
|
|
2911
|
+
* `catalogKey` is the key exactly as the catalog adapter and the per-locale `orphaned` lists spell
|
|
2912
|
+
* it, for a tool that needs to address the entry.
|
|
2913
|
+
*/
|
|
2914
|
+
interface UnusedKey {
|
|
2915
|
+
/** The decoded key, as shown to people and matched by `extract.unused.ignore`. */
|
|
2916
|
+
readonly key: string;
|
|
2917
|
+
/** The key as the catalog adapter encodes it. */
|
|
2918
|
+
readonly catalogKey: string;
|
|
2919
|
+
}
|
|
2920
|
+
/** A source-catalog key no static reference names, but a template-literal key could still reach. */
|
|
2921
|
+
interface PossiblyDynamicKey extends UnusedKey {
|
|
2922
|
+
/** The static head of the template-literal key that could reach it. */
|
|
2923
|
+
readonly prefix: string;
|
|
2924
|
+
}
|
|
2925
|
+
/** A template-literal key argument with a static head, located in the source. */
|
|
2926
|
+
interface UnusedKeysPrefixSite extends SourceLocation {
|
|
2927
|
+
/** The static head every key the site can reach starts with. */
|
|
2928
|
+
readonly prefix: string;
|
|
2929
|
+
}
|
|
2930
|
+
/** An unused-key report produced from a source scan. */
|
|
2931
|
+
interface UnusedKeysScan {
|
|
2932
|
+
/**
|
|
2933
|
+
* `complete` when nothing in the scan casts doubt on the verdict; `unreliable` when
|
|
2934
|
+
* {@link UnusedKeysScan.unreliableBecause} is not empty, so a listed key may still be in use.
|
|
2935
|
+
*/
|
|
2936
|
+
readonly status: "complete" | "unreliable";
|
|
2937
|
+
/** Every reason the verdict cannot be trusted. Empty exactly when `status` is `complete`. */
|
|
2938
|
+
readonly unreliableBecause: readonly UnusedKeysUnreliability[];
|
|
2939
|
+
/** How many source files were read and scanned. */
|
|
2940
|
+
readonly scannedFiles: number;
|
|
2941
|
+
/** Source-catalog keys no scanned reference names, in catalog order. */
|
|
2942
|
+
readonly unused: readonly UnusedKey[];
|
|
2943
|
+
/**
|
|
2944
|
+
* Unreferenced source-catalog keys a template-literal key with a static head could reach, in
|
|
2945
|
+
* catalog order. They are listed apart from `unused` and never fail a run.
|
|
2946
|
+
*/
|
|
2947
|
+
readonly possiblyDynamic: readonly PossiblyDynamicKey[];
|
|
2948
|
+
/** Unreferenced source-catalog keys matched by `extract.unused.ignore`, in catalog order. */
|
|
2949
|
+
readonly ignored: readonly UnusedKey[];
|
|
2950
|
+
/** The template-literal key sites behind `possiblyDynamic`. */
|
|
2951
|
+
readonly dynamicPrefixes: readonly UnusedKeysPrefixSite[];
|
|
2952
|
+
}
|
|
2953
|
+
/**
|
|
2954
|
+
* The source-locale catalog keys that nothing in the scanned source references. Distinct from the
|
|
2955
|
+
* per-locale `orphaned` lists, which compare a target locale against the source catalog.
|
|
2956
|
+
*/
|
|
2957
|
+
type UnusedKeysReport = UnusedKeysNotRun | UnusedKeysScan;
|
|
2958
|
+
/** An unused-key report that could not run. It carries no key list, so nothing is misreported. */
|
|
2959
|
+
interface UnusedKeysNotRun {
|
|
2960
|
+
/** Always `not-run`. */
|
|
2961
|
+
readonly status: "not-run";
|
|
2962
|
+
/** The stable reason code. Branch on this, not on the message. */
|
|
2963
|
+
readonly reason: UnusedKeysNotRunReason;
|
|
2964
|
+
/** A human-readable explanation. */
|
|
2965
|
+
readonly message: string;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
1369
2968
|
/** One locale's pending work in a {@link DiffSummary}, as key names rather than counts. */
|
|
1370
2969
|
interface LocaleDiff {
|
|
1371
2970
|
/** The target locale this entry describes. */
|
|
@@ -1391,22 +2990,45 @@ interface DiffSummary {
|
|
|
1391
2990
|
readonly hasPendingChanges: boolean;
|
|
1392
2991
|
/** Per-locale key lists, in configured target order. */
|
|
1393
2992
|
readonly locales: readonly LocaleDiff[];
|
|
2993
|
+
/**
|
|
2994
|
+
* Source-catalog keys nothing in the scanned application source references. Present only when
|
|
2995
|
+
* {@link DiffInput.unused} was set. It is a separate axis from each locale's `orphaned` list and
|
|
2996
|
+
* never flips {@link DiffSummary.hasPendingChanges}.
|
|
2997
|
+
*/
|
|
2998
|
+
readonly unused?: UnusedKeysReport;
|
|
1394
2999
|
}
|
|
1395
3000
|
/** Input for {@link diff}. */
|
|
1396
3001
|
interface DiffInput {
|
|
1397
3002
|
/** The resolved project config, normally from {@link loadConfig}. */
|
|
1398
3003
|
readonly config: VerbatraConfig;
|
|
1399
|
-
/**
|
|
3004
|
+
/**
|
|
3005
|
+
* Directory the `files.pattern` is resolved against. Defaults to the process working directory.
|
|
3006
|
+
*/
|
|
1400
3007
|
readonly cwd?: string;
|
|
1401
3008
|
/** Restrict the report to these target locales. Defaults to every configured target locale. */
|
|
1402
3009
|
readonly locales?: readonly string[];
|
|
3010
|
+
/**
|
|
3011
|
+
* Also scan the application source configured in the `extract` block and report the
|
|
3012
|
+
* source-catalog keys nothing references, as {@link DiffSummary.unused}. Off by default.
|
|
3013
|
+
*/
|
|
3014
|
+
readonly unused?: boolean;
|
|
1403
3015
|
}
|
|
1404
3016
|
/** Injectable dependencies for {@link diff}. Every field has a working default. */
|
|
1405
3017
|
interface DiffDeps {
|
|
1406
|
-
/**
|
|
3018
|
+
/**
|
|
3019
|
+
* Format-adapter registry to resolve the configured format. Defaults to the built-in registry.
|
|
3020
|
+
*/
|
|
1407
3021
|
readonly adapterRegistry?: AdapterRegistry;
|
|
1408
|
-
/**
|
|
3022
|
+
/**
|
|
3023
|
+
* File-system port. Defaults to the real file system. The unused-key scan needs its
|
|
3024
|
+
* `readDirectory` member and reports its absence as `EXTRACT_FS_UNSUPPORTED`.
|
|
3025
|
+
*/
|
|
1409
3026
|
readonly fs?: SdkFs;
|
|
3027
|
+
/**
|
|
3028
|
+
* Extractor factory for the unused-key scan. Defaults to the built-in table keyed by the
|
|
3029
|
+
* configured framework.
|
|
3030
|
+
*/
|
|
3031
|
+
readonly createExtractor?: (framework: SourceFramework) => SourceExtractor;
|
|
1410
3032
|
}
|
|
1411
3033
|
/**
|
|
1412
3034
|
* Reports the per-locale drift between the source and each target locale as key names, without
|
|
@@ -1417,19 +3039,34 @@ interface DiffDeps {
|
|
|
1417
3039
|
* since the key was last translated rather than merely that the two strings differ. Orphaned keys
|
|
1418
3040
|
* are reported but never removed here; pruning happens only in {@link translate}.
|
|
1419
3041
|
*
|
|
3042
|
+
* With `unused` set, it also scans the application source named by the config's `extract` block
|
|
3043
|
+
* and reports, in {@link DiffSummary.unused}, the source-catalog keys no static reference names.
|
|
3044
|
+
* The scan models the i18next runtime: `t` calls and their aliases, `keyPrefix` and `getFixedT`
|
|
3045
|
+
* prefixes, `Trans` keys, namespace-qualified keys, and the plural and context variants of a
|
|
3046
|
+
* referenced key. It is read-only like the rest of `diff`: it never removes, rewrites, or reorders
|
|
3047
|
+
* a catalog, and it constructs no provider and reads no API key. Keys matched by
|
|
3048
|
+
* `extract.unused.ignore` are listed as `ignored`, and keys a template-literal key with a static
|
|
3049
|
+
* head could reach as `possiblyDynamic`, never as `unused`. The report is `complete` only when the
|
|
3050
|
+
* scan can bound every key the source reaches; otherwise it is `unreliable` and names each reason
|
|
3051
|
+
* with its sites, or `not-run` with a reason code and no key list at all when there is nothing
|
|
3052
|
+
* trustworthy to judge by (no `extract` block, a format whose runtime is not modeled, no source
|
|
3053
|
+
* file, or no reference anywhere in the scanned files).
|
|
3054
|
+
*
|
|
1420
3055
|
* Note that a malformed target locale file surfaces the adapter's own error and code rather than a
|
|
1421
3056
|
* wrapped {@link SdkError}, because only source reads are wrapped. Its message names the offending
|
|
1422
3057
|
* locale and the resolved path. A caller that maps SDK codes should be ready for an unrecognized
|
|
1423
3058
|
* error from a target file.
|
|
1424
3059
|
*
|
|
1425
|
-
* @param input - The config
|
|
1426
|
-
* @param deps - Optional adapter registry
|
|
1427
|
-
* @returns Per-locale missing, changed, and orphaned key lists
|
|
3060
|
+
* @param input - The config, the optional locale filter, and whether to report unused keys.
|
|
3061
|
+
* @param deps - Optional adapter registry, file-system, and extractor-factory overrides.
|
|
3062
|
+
* @returns Per-locale missing, changed, and orphaned key lists, plus the unused-key report when
|
|
3063
|
+
* asked for.
|
|
1428
3064
|
*
|
|
1429
3065
|
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
1430
3066
|
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
1431
3067
|
* cannot be combined, or a configured locale has no valid path spelling under that style.
|
|
1432
|
-
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same
|
|
3068
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same
|
|
3069
|
+
* path.
|
|
1433
3070
|
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
1434
3071
|
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
1435
3072
|
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
@@ -1447,8 +3084,11 @@ declare function diff(input: DiffInput, deps?: DiffDeps): Promise<DiffSummary>;
|
|
|
1447
3084
|
* - `api-key`: the environment variable the configured provider reads its key from is set.
|
|
1448
3085
|
* - `source-file`: the source locale file exists at its resolved path, is a regular file, and
|
|
1449
3086
|
* parses under the configured format.
|
|
3087
|
+
* - `untranslated-literals`: the application source configured in the `extract` block holds no
|
|
3088
|
+
* hardcoded user-facing string literal and no file the scan could not read. It runs only when
|
|
3089
|
+
* {@link DoctorInput.literals} is set.
|
|
1450
3090
|
*/
|
|
1451
|
-
type DoctorCheckId = "config" | "format-adapter" | "provider" | "api-key" | "source-file";
|
|
3091
|
+
type DoctorCheckId = "config" | "format-adapter" | "provider" | "api-key" | "source-file" | "untranslated-literals";
|
|
1452
3092
|
/**
|
|
1453
3093
|
* The verdict on one {@link DoctorCheck}. `skipped` is reported only for the checks that need a
|
|
1454
3094
|
* loaded config when the `config` check itself failed, so a skipped check is never a problem of its
|
|
@@ -1474,8 +3114,18 @@ interface DoctorCheck {
|
|
|
1474
3114
|
interface DoctorResult {
|
|
1475
3115
|
/** True only when no check failed. This is the value a script should branch on. */
|
|
1476
3116
|
readonly ok: boolean;
|
|
1477
|
-
/**
|
|
3117
|
+
/**
|
|
3118
|
+
* Every check that ran, always in the same order. A setup run has one entry per setup check:
|
|
3119
|
+
* `config`, `format-adapter`, `provider`, `api-key`, and `source-file`. A literal run
|
|
3120
|
+
* ({@link DoctorInput.literals}) has exactly two: `config` and `untranslated-literals`.
|
|
3121
|
+
*/
|
|
1478
3122
|
readonly checks: readonly DoctorCheck[];
|
|
3123
|
+
/**
|
|
3124
|
+
* What the untranslated-literal scan found. Present only on a literal run whose scan actually
|
|
3125
|
+
* ran; absent when the config could not be loaded, no `extract` block is configured, or the
|
|
3126
|
+
* file system cannot list directories.
|
|
3127
|
+
*/
|
|
3128
|
+
readonly literals?: LiteralScan;
|
|
1479
3129
|
}
|
|
1480
3130
|
/** Input for {@link doctor}. */
|
|
1481
3131
|
interface DoctorInput {
|
|
@@ -1483,6 +3133,13 @@ interface DoctorInput {
|
|
|
1483
3133
|
readonly cwd?: string;
|
|
1484
3134
|
/** An explicit config file to validate, bypassing the search. A missing file is an error rather than a failed check. */
|
|
1485
3135
|
readonly configPath?: string;
|
|
3136
|
+
/**
|
|
3137
|
+
* Run the untranslated-literal scan instead of the setup checks: the config is loaded, then the
|
|
3138
|
+
* source roots of its `extract` block are scanned for hardcoded user-facing string literals. The
|
|
3139
|
+
* provider, API-key, format, and source-file checks do not run, so no API key environment
|
|
3140
|
+
* variable is looked at and a run with no key set can pass.
|
|
3141
|
+
*/
|
|
3142
|
+
readonly literals?: boolean;
|
|
1486
3143
|
}
|
|
1487
3144
|
/** Injectable dependencies for {@link doctor}. Every field has a working default. */
|
|
1488
3145
|
interface DoctorDeps {
|
|
@@ -1527,7 +3184,18 @@ interface DoctorDeps {
|
|
|
1527
3184
|
* verdict they could not reach, and {@link DoctorResult.ok} is false because the config check
|
|
1528
3185
|
* itself failed.
|
|
1529
3186
|
*
|
|
1530
|
-
*
|
|
3187
|
+
* With `literals: true` it runs the untranslated-literal scan instead of the setup checks. The
|
|
3188
|
+
* source roots of the config's `extract` block are read, never written, and every string literal
|
|
3189
|
+
* or piece of JSX text that reads as user-facing text without going through a recognised
|
|
3190
|
+
* translation call is returned in {@link DoctorResult.literals} with its file, line, column, and
|
|
3191
|
+
* an excerpt of at most 80 characters. Literals held back by a `verbatra-ignore-next-line` or
|
|
3192
|
+
* `verbatra-ignore-line` comment, or by `extract.literals.ignore`, are returned under `suppressed`
|
|
3193
|
+
* rather than dropped. The check fails when anything was found or when a file could not be
|
|
3194
|
+
* scanned, so a partial scan is never reported as clean. No provider is constructed and no API key
|
|
3195
|
+
* environment variable is read.
|
|
3196
|
+
*
|
|
3197
|
+
* @param input - The working directory, an optional explicit config path, and whether to run the
|
|
3198
|
+
* untranslated-literal scan instead of the setup checks.
|
|
1531
3199
|
* @param deps - Optional adapter registry, file-system, and config-loader overrides.
|
|
1532
3200
|
* @returns Every check with its verdict, and the project-wide `ok` verdict.
|
|
1533
3201
|
*
|
|
@@ -1548,7 +3216,7 @@ interface DoctorDeps {
|
|
|
1548
3216
|
declare function doctor(input?: DoctorInput, deps?: DoctorDeps): Promise<DoctorResult>;
|
|
1549
3217
|
|
|
1550
3218
|
/**
|
|
1551
|
-
*
|
|
3219
|
+
* Every reason a candidate translation can be refused before it is written. The same gate guards
|
|
1552
3220
|
* {@link editEntry} and {@link retranslateEntry}, so a hand-typed value and a provider-produced one
|
|
1553
3221
|
* are held to identical standards.
|
|
1554
3222
|
*
|
|
@@ -1559,6 +3227,48 @@ declare function doctor(input?: DoctorInput, deps?: DoctorDeps): Promise<DoctorR
|
|
|
1559
3227
|
* interpolation would break at runtime. For the double-brace formats (i18next, ngx-translate, and
|
|
1560
3228
|
* YAML) this also covers a single-brace `{name}`-shaped token the candidate invented and the
|
|
1561
3229
|
* source never had, which is a fabrication whichever interpolation delimiters the project uses.
|
|
3230
|
+
* - `markup`: the candidate does not carry the same inline HTML or XML tags as the source, or it
|
|
3231
|
+
* carries them unbalanced, mis-nested, or newly nested inside another tag of the same name, any
|
|
3232
|
+
* of which breaks the rendering of the string the way a dropped placeholder breaks its
|
|
3233
|
+
* interpolation, or it would make an HTML parser create markup the source does not have. Tags
|
|
3234
|
+
* are read the way an HTML parser reads them (quoted attribute values may contain `>`, and the
|
|
3235
|
+
* content of a raw text element such as `script`, `style`, `title` or `textarea` is text up to
|
|
3236
|
+
* its closing tag) and compared as a multiset of names plus attribute names, so a different word
|
|
3237
|
+
* order and a translated attribute value are both accepted, and the two spellings of a void
|
|
3238
|
+
* element (`<br>` and `<br/>`) are one tag. Tag names are compared exactly, while the HTML void
|
|
3239
|
+
* elements are recognised as needing no closing tag in any case spelling. An unclosed bracketed
|
|
3240
|
+
* word that is only a name of letters, digits, hyphens, and underscores, such as `<Enter>`, is
|
|
3241
|
+
* set aside on both sides and counted like a tag once the source carries tags; where the source
|
|
3242
|
+
* has no tags, the candidate is refused for a closing tag with no opening tag, an unclosed tag
|
|
3243
|
+
* that carries anything beyond its name, any closed pair, void, or self-closing tag, and a
|
|
3244
|
+
* bracketed word the source does not carry that names a standard HTML element, `image`, or a
|
|
3245
|
+
* custom element (any name with a hyphen). Comments, CDATA sections, declarations, processing
|
|
3246
|
+
* instructions, and a `</` not followed by a letter end where an HTML parser ends them and are
|
|
3247
|
+
* compared as a multiset of their text, so one the candidate adds, drops, or rewrites is
|
|
3248
|
+
* refused. A source whose own tags are not well formed is still compared as a multiset, and no
|
|
3249
|
+
* source is too large to compare; a candidate is refused once it carries more than twice its
|
|
3250
|
+
* source's tags and constructs, or more than 256 for a smaller source. Also refused: a tag the
|
|
3251
|
+
* candidate never finishes when the source finishes all of its own; a `javascript:`,
|
|
3252
|
+
* `vbscript:`, or `data:` scheme in a URL attribute value, read after decoding character
|
|
3253
|
+
* references, unless the source carries that exact value on the same tag and attribute; a URL
|
|
3254
|
+
* in such a value whose scheme and authority no source value of the same tag and attribute has,
|
|
3255
|
+
* or a relative URL made absolute; a changed value of `meta` `http-equiv` or `content`, `script`
|
|
3256
|
+
* `type`, `nomodule`, `integrity` or `crossorigin`, `link` `rel`, `as`, `integrity`,
|
|
3257
|
+
* `crossorigin` or `type`, `base` `target`, SVG animation `attributeName` or `attributeType`,
|
|
3258
|
+
* `iframe` `sandbox`, `allow` or `allowfullscreen`, `form` `method`, `enctype` or `target`, or
|
|
3259
|
+
* any `style`, `srcdoc` or event handler; any change to the content of a `script`, `style`,
|
|
3260
|
+
* `iframe`, `noembed`, `noframes`, `noscript`, `xmp`, or `plaintext` element (`title` and
|
|
3261
|
+
* `textarea` content may be translated); and, unless the candidate is the source unchanged,
|
|
3262
|
+
* raw text an HTML parser can read two ways (a `script` whose content opens `<!--`, a `noscript`
|
|
3263
|
+
* whose content holds a `<`, a raw text element whose content holds a `<` after an `svg`,
|
|
3264
|
+
* `math`, or `select` tag) or a CDATA section or `image` tag in a value that opens `svg`, `math`,
|
|
3265
|
+
* or `select`. The check stands down per tag: a tag the format already reports as a placeholder
|
|
3266
|
+
* (an XLIFF inline element, a next-intl or ARB ICU rich-text tag) is left to the `placeholder`
|
|
3267
|
+
* reason together with as many closing tags as it has openings, while one the candidate leaves
|
|
3268
|
+
* unclosed, one that no longer nests with the other tags as in the source, and one the source
|
|
3269
|
+
* carries only as text are still refused, and any other tag in the same value is still
|
|
3270
|
+
* compared, including a second spelling of the same name. The refusal's `details` names the
|
|
3271
|
+
* offending tags.
|
|
1562
3272
|
* - `icu`: the candidate is not a valid ICU message under the configured format's adapter.
|
|
1563
3273
|
* - `degenerate`: the candidate collapsed into runaway output rather than a translation. Two shapes
|
|
1564
3274
|
* are detected: the candidate is at least twelve times the length of a source of meaningful
|
|
@@ -1566,8 +3276,25 @@ declare function doctor(input?: DoctorInput, deps?: DoctorDeps): Promise<DoctorR
|
|
|
1566
3276
|
* echo of the source is not degenerate by this rule; it surfaces as the `EQUALS_SOURCE` review
|
|
1567
3277
|
* reason instead, which flags rather than refuses.
|
|
1568
3278
|
* - `empty`: the source has text but the candidate is blank, which would silently erase a string.
|
|
3279
|
+
*
|
|
3280
|
+
* This tuple is the single source of truth for the set. {@link IntegrityGateReason} is derived from
|
|
3281
|
+
* it, so build any runtime validator or exhaustive lookup from this value rather than retyping the
|
|
3282
|
+
* members; a hand-copied list silently falls behind the next addition.
|
|
3283
|
+
*
|
|
3284
|
+
* @example
|
|
3285
|
+
* ```ts
|
|
3286
|
+
* import { INTEGRITY_GATE_REASONS } from "@verbatra/sdk";
|
|
3287
|
+
* import { z } from "zod";
|
|
3288
|
+
*
|
|
3289
|
+
* const reasonSchema = z.enum(INTEGRITY_GATE_REASONS);
|
|
3290
|
+
* ```
|
|
3291
|
+
*/
|
|
3292
|
+
declare const INTEGRITY_GATE_REASONS: readonly ["placeholder", "markup", "icu", "degenerate", "empty"];
|
|
3293
|
+
/**
|
|
3294
|
+
* One of {@link INTEGRITY_GATE_REASONS}. The union is derived from that tuple rather than written
|
|
3295
|
+
* out again, so a reason can only be added in one place.
|
|
1569
3296
|
*/
|
|
1570
|
-
type IntegrityGateReason =
|
|
3297
|
+
type IntegrityGateReason = (typeof INTEGRITY_GATE_REASONS)[number];
|
|
1571
3298
|
|
|
1572
3299
|
/** Input for {@link editEntry}. */
|
|
1573
3300
|
interface EditEntryInput {
|
|
@@ -1604,6 +3331,14 @@ type EditEntryResult = {
|
|
|
1604
3331
|
readonly accepted: false;
|
|
1605
3332
|
/** Which integrity rule the value broke. */
|
|
1606
3333
|
readonly reason: IntegrityGateReason;
|
|
3334
|
+
/**
|
|
3335
|
+
* The specific tags behind a `markup` refusal, each prefixed with `-` for one the source had
|
|
3336
|
+
* and the candidate dropped or `+` for one the candidate invented. A candidate carrying more
|
|
3337
|
+
* than twice its source's inline tags and constructs is named as `+more than N inline tags`
|
|
3338
|
+
* instead, where N is at least 256. Absent when no single tag is at fault, such as markup
|
|
3339
|
+
* that came back mis-nested, and absent for every other reason.
|
|
3340
|
+
*/
|
|
3341
|
+
readonly details?: readonly string[];
|
|
1607
3342
|
/** The rejected value, echoed back so a UI can show what was refused. */
|
|
1608
3343
|
readonly value: string;
|
|
1609
3344
|
};
|
|
@@ -1657,6 +3392,242 @@ type EditEntryResult = {
|
|
|
1657
3392
|
*/
|
|
1658
3393
|
declare function editEntry(input: EditEntryInput, deps?: EditEntryDeps): Promise<EditEntryResult>;
|
|
1659
3394
|
|
|
3395
|
+
/** One key the run added to the source catalog, and the call site it was found at. */
|
|
3396
|
+
interface AddedKey {
|
|
3397
|
+
/** The key as written at the call site. */
|
|
3398
|
+
readonly key: string;
|
|
3399
|
+
/** The value written into the catalog: the call site's default, or the empty string when it had none. */
|
|
3400
|
+
readonly value: string;
|
|
3401
|
+
/** The source file the key was found in, relative to the run's working directory. */
|
|
3402
|
+
readonly file: string;
|
|
3403
|
+
/** The one-based line the key was found on. */
|
|
3404
|
+
readonly line: number;
|
|
3405
|
+
}
|
|
3406
|
+
/**
|
|
3407
|
+
* The result of {@link extract}. Everything it reports is a key, a value, or a file and line: a
|
|
3408
|
+
* source file's contents never travel in it.
|
|
3409
|
+
*/
|
|
3410
|
+
interface ExtractResult {
|
|
3411
|
+
/** The source locale file the run targeted, relative to the run's working directory. */
|
|
3412
|
+
readonly sourcePath: string;
|
|
3413
|
+
/** How many source files were read and scanned. */
|
|
3414
|
+
readonly scannedFiles: number;
|
|
3415
|
+
/** Keys that were not in the catalog and were added, in the order they were found. */
|
|
3416
|
+
readonly added: readonly AddedKey[];
|
|
3417
|
+
/** How many of the keys found were already in the catalog and were left untouched. */
|
|
3418
|
+
readonly existingKeys: number;
|
|
3419
|
+
/** Added keys whose call site carried no default, so they were written with an empty value. */
|
|
3420
|
+
readonly withoutDefault: readonly string[];
|
|
3421
|
+
/** Call sites whose key argument was not a static string, reported rather than guessed at. */
|
|
3422
|
+
readonly dynamic: readonly SourceLocation[];
|
|
3423
|
+
/** Keys found twice with two different default values. Neither value is written. */
|
|
3424
|
+
readonly conflicts: readonly KeyConflict[];
|
|
3425
|
+
/** Files and directories the scan could not read, which never abort the run. */
|
|
3426
|
+
readonly diagnostics: readonly ScanDiagnostic[];
|
|
3427
|
+
/** Whether the source catalog was actually written. False for a dry run and for a run that added nothing. */
|
|
3428
|
+
readonly written: boolean;
|
|
3429
|
+
/** Whether this was a dry run. */
|
|
3430
|
+
readonly dryRun: boolean;
|
|
3431
|
+
}
|
|
3432
|
+
/** Input for {@link extract}. */
|
|
3433
|
+
interface ExtractInput {
|
|
3434
|
+
/** A resolved config whose `extract` block names the framework and the source roots. */
|
|
3435
|
+
readonly config: VerbatraConfig;
|
|
3436
|
+
/** Directory the roots and locale paths are resolved against. Defaults to the process working directory. */
|
|
3437
|
+
readonly cwd?: string;
|
|
3438
|
+
/** Report what would be added without writing anything. */
|
|
3439
|
+
readonly dryRun?: boolean;
|
|
3440
|
+
}
|
|
3441
|
+
/** Injectable dependencies for {@link extract}. Every field has a working default. */
|
|
3442
|
+
interface ExtractDeps {
|
|
3443
|
+
/** Format-adapter registry to resolve the configured format against. Defaults to the built-in registry. */
|
|
3444
|
+
readonly adapterRegistry?: AdapterRegistry;
|
|
3445
|
+
/**
|
|
3446
|
+
* File-system port. It backs both the source scan and the catalog write, so its `readDirectory`
|
|
3447
|
+
* member must be implemented. Defaults to the real file system.
|
|
3448
|
+
*/
|
|
3449
|
+
readonly fs?: SdkFs;
|
|
3450
|
+
/** Extractor factory. Defaults to the built-in table keyed by the configured framework. */
|
|
3451
|
+
readonly createExtractor?: (framework: SourceFramework) => SourceExtractor;
|
|
3452
|
+
}
|
|
3453
|
+
/**
|
|
3454
|
+
* Scans a project's own source for translation call sites and merges what it finds into the source
|
|
3455
|
+
* locale catalog. It is the one entry point that reads application code rather than a locale file,
|
|
3456
|
+
* which is what makes verbatra usable on a project that has no catalog yet.
|
|
3457
|
+
*
|
|
3458
|
+
* It spends nothing: no provider is constructed, no API key environment variable is read, and no
|
|
3459
|
+
* network request is made. A run with no key set succeeds.
|
|
3460
|
+
*
|
|
3461
|
+
* Only the source locale file is ever written, and only keys that are genuinely new are added: a
|
|
3462
|
+
* key already in the catalog keeps its value exactly as it stands, so an editorial fix is never
|
|
3463
|
+
* reverted by a stale default left at a call site. A key in the catalog that no call site mentions
|
|
3464
|
+
* is left alone. A run that finds nothing new writes nothing at all, so it leaves the file
|
|
3465
|
+
* byte-identical and its modification time untouched.
|
|
3466
|
+
*
|
|
3467
|
+
* Everything the scan cannot resolve is reported as data rather than thrown. A call site whose key
|
|
3468
|
+
* argument is not a static string is reported in `dynamic` and never written with a guessed key; a
|
|
3469
|
+
* key found twice with two different defaults is reported in `conflicts` and neither value is
|
|
3470
|
+
* written; a file or directory that cannot be read is reported in `diagnostics` and the scan
|
|
3471
|
+
* carries on. The result never carries a source file's contents, only keys, values, and locations.
|
|
3472
|
+
*
|
|
3473
|
+
* @param input - The config (whose `extract` block names the framework and roots), the working
|
|
3474
|
+
* directory, and whether this is a dry run.
|
|
3475
|
+
* @param deps - Optional adapter registry, file-system, and extractor-factory overrides.
|
|
3476
|
+
* @returns What was added, what was already there, and everything the scan could not resolve.
|
|
3477
|
+
*
|
|
3478
|
+
* @throws {@link SdkError} `EXTRACT_NOT_CONFIGURED`: the config carries no `extract` block.
|
|
3479
|
+
* @throws {@link SdkError} `EXTRACT_FS_UNSUPPORTED`: the supplied `deps.fs` implements no
|
|
3480
|
+
* `readDirectory`, so no source file can be discovered.
|
|
3481
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
3482
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: a source catalog exists but could not be parsed.
|
|
3483
|
+
* @throws {@link SdkError} `SOURCE_UNWRITABLE`: the source catalog could not be written. The
|
|
3484
|
+
* `xliff` and `apple-xcstrings` formats reach this when no catalog exists yet: neither is created
|
|
3485
|
+
* from nothing, here or anywhere else in verbatra.
|
|
3486
|
+
*
|
|
3487
|
+
* @example
|
|
3488
|
+
* ```ts
|
|
3489
|
+
* import { extract, loadConfig } from "@verbatra/sdk";
|
|
3490
|
+
*
|
|
3491
|
+
* const config = await loadConfig();
|
|
3492
|
+
* const result = await extract({ config, dryRun: true });
|
|
3493
|
+
* console.log(`${result.added.length} new keys, ${result.dynamic.length} dynamic call sites`);
|
|
3494
|
+
* ```
|
|
3495
|
+
*/
|
|
3496
|
+
declare function extract(input: ExtractInput, deps?: ExtractDeps): Promise<ExtractResult>;
|
|
3497
|
+
|
|
3498
|
+
/** Why verbatra declined to describe a message's arguments rather than guessing at them. */
|
|
3499
|
+
type UnresolvedArgumentReason = "invalid-message-syntax" | "mixed-argument-styles" | "argument-index-out-of-range";
|
|
3500
|
+
|
|
3501
|
+
/**
|
|
3502
|
+
* Where {@link generateTypes} writes its declaration when the caller names no path: a `.d.ts` at
|
|
3503
|
+
* the root of the working directory. The file is a checked-in artifact, not a local scratch file:
|
|
3504
|
+
* it belongs in version control so a consumer type-checks against it without running verbatra
|
|
3505
|
+
* first, and so `check` mode has a committed file to compare against.
|
|
3506
|
+
*/
|
|
3507
|
+
declare const DEFAULT_TYPES_PATH = "verbatra-types.d.ts";
|
|
3508
|
+
/** One key whose arguments verbatra declined to describe, and why. */
|
|
3509
|
+
interface UnresolvedMessage {
|
|
3510
|
+
/** The key, which is still declared, with arguments nothing is claimed about. */
|
|
3511
|
+
readonly key: string;
|
|
3512
|
+
/** Why the arguments could not be determined. */
|
|
3513
|
+
readonly reason: UnresolvedArgumentReason;
|
|
3514
|
+
}
|
|
3515
|
+
/** Input for {@link generateTypes}. */
|
|
3516
|
+
interface GenerateTypesInput {
|
|
3517
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
3518
|
+
readonly config: VerbatraConfig;
|
|
3519
|
+
/** Directory the `files.pattern` and the output path are resolved against. Defaults to the process working directory. */
|
|
3520
|
+
readonly cwd?: string;
|
|
3521
|
+
/**
|
|
3522
|
+
* Where to write the declaration, relative to `cwd`. Defaults to {@link DEFAULT_TYPES_PATH}.
|
|
3523
|
+
* Refused with `TYPES_OUTPUT_CONFLICT`, before anything is read or written, when it names no
|
|
3524
|
+
* file, is absolute, climbs out of `cwd`, does not end in `.ts`, `.mts` or `.cts`, or names a
|
|
3525
|
+
* configured locale file, the lock file, the translation-memory cache, a file verbatra searches
|
|
3526
|
+
* for its configuration, or the {@link GenerateTypesInput.configPath} file. Names are compared
|
|
3527
|
+
* case-insensitively. A generating run also refuses to replace an existing file there unless
|
|
3528
|
+
* that file begins with the header line verbatra writes, after any leading byte order mark and
|
|
3529
|
+
* blank lines, and refuses one too large to verify.
|
|
3530
|
+
*/
|
|
3531
|
+
readonly out?: string;
|
|
3532
|
+
/**
|
|
3533
|
+
* The configuration file `config` was loaded from, absolute or relative to `cwd`. It is refused
|
|
3534
|
+
* as the output path even when its name is not one verbatra searches for.
|
|
3535
|
+
*/
|
|
3536
|
+
readonly configPath?: string;
|
|
3537
|
+
/**
|
|
3538
|
+
* Compare instead of writing. The run reports whether the file on disk matches what a fresh
|
|
3539
|
+
* generation would produce and leaves every file untouched.
|
|
3540
|
+
*/
|
|
3541
|
+
readonly check?: boolean;
|
|
3542
|
+
}
|
|
3543
|
+
/** Injectable dependencies for {@link generateTypes}. Every field has a working default. */
|
|
3544
|
+
interface GenerateTypesDeps {
|
|
3545
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
3546
|
+
readonly adapterRegistry?: AdapterRegistry;
|
|
3547
|
+
/** File-system port. Defaults to the real file system. */
|
|
3548
|
+
readonly fs?: SdkFs;
|
|
3549
|
+
}
|
|
3550
|
+
/** The result of {@link generateTypes}: what was declared, and whether the file on disk matched. */
|
|
3551
|
+
interface GenerateTypesResult {
|
|
3552
|
+
/** Absolute path of the declaration file. */
|
|
3553
|
+
readonly path: string;
|
|
3554
|
+
/** The source catalog the declaration was built from, relative to the working directory. */
|
|
3555
|
+
readonly sourcePath: string;
|
|
3556
|
+
/** How many keys the declaration carries. */
|
|
3557
|
+
readonly keys: number;
|
|
3558
|
+
/**
|
|
3559
|
+
* How many of those keys take at least one argument verbatra could determine. A key listed in
|
|
3560
|
+
* {@link GenerateTypesResult.unresolved} is not counted here.
|
|
3561
|
+
*/
|
|
3562
|
+
readonly withArguments: number;
|
|
3563
|
+
/** Keys that are declared but whose arguments could not be determined, and why. */
|
|
3564
|
+
readonly unresolved: readonly UnresolvedMessage[];
|
|
3565
|
+
/** Keys the adapter reported as excluded from translation, which are never declared. */
|
|
3566
|
+
readonly excluded: readonly string[];
|
|
3567
|
+
/** Keys the adapter marked as carrying plural forms. Each sibling is declared on its own. */
|
|
3568
|
+
readonly plural: readonly string[];
|
|
3569
|
+
/** Whether the declaration file was written. Always false in `check` mode. */
|
|
3570
|
+
readonly written: boolean;
|
|
3571
|
+
/** Whether the file on disk differed from the freshly generated declaration when the run started. */
|
|
3572
|
+
readonly stale: boolean;
|
|
3573
|
+
/** Whether this was a `check` run. */
|
|
3574
|
+
readonly check: boolean;
|
|
3575
|
+
}
|
|
3576
|
+
/**
|
|
3577
|
+
* Generates a TypeScript declaration for a project's source catalog: a union of every key it holds
|
|
3578
|
+
* and, per key, the arguments its message interpolates. A consumer that types its translation
|
|
3579
|
+
* function against it turns a misspelled key and a missing interpolation argument into compile
|
|
3580
|
+
* errors instead of runtime lookup failures.
|
|
3581
|
+
*
|
|
3582
|
+
* It reads one file (the source locale catalog) and writes one file (the declaration). It
|
|
3583
|
+
* constructs no provider, reads no API key and makes no network request, so it runs on a fresh
|
|
3584
|
+
* checkout before any key exists.
|
|
3585
|
+
*
|
|
3586
|
+
* The keys are exactly what the format adapter produced when reading the catalog, in document
|
|
3587
|
+
* order, so two runs over an unchanged catalog write byte-identical bytes. Arguments come from the
|
|
3588
|
+
* placeholder tokens the adapter extracted, except for the ICU message formats (`next-intl-json`
|
|
3589
|
+
* and `arb`): there each message is analysed with the same ICU parser the adapter uses, so an
|
|
3590
|
+
* argument that only some `select` or `plural` branches use is still declared, as optional. Keys
|
|
3591
|
+
* are emitted as quoted string literals, so a key carrying a dot, a reserved word, a leading digit,
|
|
3592
|
+
* a quote, or nothing at all is declared verbatim rather than dropped or re-split.
|
|
3593
|
+
*
|
|
3594
|
+
* What it will not claim is as important as what it will. A message whose placeholders name their
|
|
3595
|
+
* arguments gets an object shape; one whose placeholders are numbered or anonymous gets a readonly
|
|
3596
|
+
* tuple; one that takes nothing gets a shape that makes passing an argument a type error. A
|
|
3597
|
+
* message whose syntax the adapter reported as invalid, and one that appears to name and number
|
|
3598
|
+
* its arguments at once, are declared with {@link GenerateTypesResult.unresolved} recording why,
|
|
3599
|
+
* rather than being silently declared as taking nothing. Argument types come only from what the
|
|
3600
|
+
* catalog actually records: `number` where the format annotated one, and a `string | number` alias
|
|
3601
|
+
* everywhere else, never a permissive `any`. A name used with several types is declared as the
|
|
3602
|
+
* union of what each use accepts.
|
|
3603
|
+
*
|
|
3604
|
+
* With `check` set, nothing is written: the run reports whether the committed file still matches
|
|
3605
|
+
* what a fresh generation would produce, which is the shape a CI gate wants.
|
|
3606
|
+
*
|
|
3607
|
+
* @param input - The config, the output path, and whether to check rather than write.
|
|
3608
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
3609
|
+
* @returns What was declared, and whether the file on disk matched.
|
|
3610
|
+
*
|
|
3611
|
+
* @example
|
|
3612
|
+
* ```ts
|
|
3613
|
+
* const result = await generateTypes({ config });
|
|
3614
|
+
* console.log(`${result.keys} keys declared in ${result.path}`);
|
|
3615
|
+
* ```
|
|
3616
|
+
*
|
|
3617
|
+
* @throws {@link SdkError} `TYPES_OUTPUT_CONFLICT`: the output path is refused (see
|
|
3618
|
+
* {@link GenerateTypesInput.out} for the full set), or a generating run found a file there that
|
|
3619
|
+
* does not begin with the header verbatra writes.
|
|
3620
|
+
* @throws {@link SdkError} `TYPES_UNWRITABLE`: the declaration file could not be written.
|
|
3621
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
3622
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
3623
|
+
* cannot be combined.
|
|
3624
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same
|
|
3625
|
+
* path.
|
|
3626
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
3627
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
3628
|
+
*/
|
|
3629
|
+
declare function generateTypes(input: GenerateTypesInput, deps?: GenerateTypesDeps): Promise<GenerateTypesResult>;
|
|
3630
|
+
|
|
1660
3631
|
/** One key's placeholder and ICU verdict in a {@link LocaleKeyIntegrity} report. */
|
|
1661
3632
|
interface KeyIntegrityEntry {
|
|
1662
3633
|
/** The key this verdict describes. */
|
|
@@ -1671,6 +3642,20 @@ interface KeyIntegrityEntry {
|
|
|
1671
3642
|
readonly extra: readonly string[];
|
|
1672
3643
|
/** True when the translation parses as a valid ICU message under the configured format. */
|
|
1673
3644
|
readonly icuValid: boolean;
|
|
3645
|
+
/**
|
|
3646
|
+
* True when the translation carries the source's inline HTML or XML tags, well formed. Compared
|
|
3647
|
+
* exactly as the write-time gate compares them, so a value already on disk is judged by the same
|
|
3648
|
+
* rule as one about to be written.
|
|
3649
|
+
*/
|
|
3650
|
+
readonly markupMatches: boolean;
|
|
3651
|
+
/**
|
|
3652
|
+
* The tags behind a `markupMatches: false` verdict, each prefixed with `-` for one the source has
|
|
3653
|
+
* and the translation lacks or `+` for one the translation added, or `+more than N inline tags`
|
|
3654
|
+
* for a translation carrying more than twice its source's inline tags and constructs (N is at
|
|
3655
|
+
* least 256). Empty when the markup matches, and empty when no single tag is at fault because
|
|
3656
|
+
* the translation's markup is structurally broken.
|
|
3657
|
+
*/
|
|
3658
|
+
readonly markupDetails: readonly string[];
|
|
1674
3659
|
}
|
|
1675
3660
|
/** One locale's per-key integrity verdicts. */
|
|
1676
3661
|
interface LocaleKeyIntegrity {
|
|
@@ -1706,9 +3691,13 @@ interface KeyIntegrityDeps {
|
|
|
1706
3691
|
* edit may have just invalidated. Only keys present in both the source and the target are judged,
|
|
1707
3692
|
* since a missing translation has no placeholders to compare.
|
|
1708
3693
|
*
|
|
1709
|
-
* This is the read-only counterpart to the
|
|
1710
|
-
* {@link retranslateEntry} enforce at write time, and the data behind a
|
|
1711
|
-
* per-key integrity indicator.
|
|
3694
|
+
* This is the read-only counterpart to the placeholder, ICU and inline-markup checks the gate that
|
|
3695
|
+
* {@link editEntry} and {@link retranslateEntry} enforce at write time, and the data behind a
|
|
3696
|
+
* review dashboard's per-key integrity indicator. Reporting markup here is what makes drift that
|
|
3697
|
+
* predates the gate visible at all: a translation written before the check existed, edited outside
|
|
3698
|
+
* verbatra, or produced by a path that never crossed the gate is judged here by the same rule.
|
|
3699
|
+
* Emptiness and degeneracy are deliberately not reported: both describe a candidate that was
|
|
3700
|
+
* refused rather than a state a stored translation can usefully be in.
|
|
1712
3701
|
*
|
|
1713
3702
|
* Note that a malformed target locale file surfaces the adapter's own error and code rather than a
|
|
1714
3703
|
* wrapped {@link SdkError}, because only source reads are wrapped. Its message names the offending
|
|
@@ -2022,313 +4011,229 @@ interface LockStateDeps {
|
|
|
2022
4011
|
*/
|
|
2023
4012
|
declare function lockState(input: LockStateInput, deps?: LockStateDeps): Promise<LockStateResult>;
|
|
2024
4013
|
|
|
2025
|
-
/**
|
|
2026
|
-
|
|
2027
|
-
* `deps.createProvider` to {@link translate}, {@link watch}, and {@link retranslateEntry}, it is the
|
|
2028
|
-
* seam for injecting a stub in a test or a provider the SDK does not ship.
|
|
2029
|
-
*
|
|
2030
|
-
* The default implementation dispatches on the provider ID and reads the API key from the
|
|
2031
|
-
* environment. A factory that throws is reported as `PROVIDER_CONSTRUCTION_FAILED`.
|
|
2032
|
-
*/
|
|
2033
|
-
type CreateProvider = (config: ProviderConfig) => TranslationProvider;
|
|
2034
|
-
|
|
2035
|
-
/** Input for {@link retranslateEntry}. */
|
|
2036
|
-
interface RetranslateEntryInput {
|
|
4014
|
+
/** Input for {@link pseudolocalize}. */
|
|
4015
|
+
interface PseudolocalizeInput {
|
|
2037
4016
|
/** The resolved project config, normally from {@link loadConfig}. */
|
|
2038
4017
|
readonly config: VerbatraConfig;
|
|
2039
4018
|
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
2040
4019
|
readonly cwd?: string;
|
|
2041
|
-
/** The target locale to retranslate into. Must be a configured target locale. */
|
|
2042
|
-
readonly locale: string;
|
|
2043
|
-
/** The key to retranslate. Must exist in the source resource. */
|
|
2044
|
-
readonly key: string;
|
|
2045
|
-
}
|
|
2046
|
-
/** Injectable dependencies for {@link retranslateEntry}. Every field has a working default. */
|
|
2047
|
-
interface RetranslateEntryDeps {
|
|
2048
|
-
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
2049
|
-
readonly adapterRegistry?: AdapterRegistry;
|
|
2050
|
-
/** Provider factory. Defaults to constructing the provider named in the config. */
|
|
2051
|
-
readonly createProvider?: CreateProvider;
|
|
2052
|
-
/** File-system port. Defaults to the real file system. */
|
|
2053
|
-
readonly fs?: SdkFs;
|
|
2054
|
-
}
|
|
2055
|
-
/**
|
|
2056
|
-
* The outcome of {@link retranslateEntry}. As with {@link EditEntryResult}, a value refused by the
|
|
2057
|
-
* integrity gate is reported as data rather than thrown, because it is an expected outcome of
|
|
2058
|
-
* asking a provider for a fresh translation.
|
|
2059
|
-
*/
|
|
2060
|
-
type RetranslateEntryResult = {
|
|
2061
|
-
/** The provider's value passed the integrity gate and was written. */
|
|
2062
|
-
readonly accepted: true;
|
|
2063
|
-
/** The newly translated value now stored for the key. */
|
|
2064
|
-
readonly value: string;
|
|
2065
4020
|
/**
|
|
2066
|
-
*
|
|
2067
|
-
*
|
|
2068
|
-
* either way; these are advisory.
|
|
4021
|
+
* The pseudolocale's BCP-47 code. Defaults to `en-XA`. It must not be the source locale or any
|
|
4022
|
+
* configured target locale, so a pseudolocale can never stand in for a real translation.
|
|
2069
4023
|
*/
|
|
2070
|
-
readonly
|
|
2071
|
-
} | {
|
|
2072
|
-
/** The value was refused; nothing was written and the previous translation is intact. */
|
|
2073
|
-
readonly accepted: false;
|
|
2074
|
-
/** Which integrity rule the provider's value broke. */
|
|
2075
|
-
readonly reason: IntegrityGateReason;
|
|
2076
|
-
/** The rejected value, echoed back so a UI can show what was refused. */
|
|
2077
|
-
readonly value: string;
|
|
2078
|
-
};
|
|
2079
|
-
/**
|
|
2080
|
-
* Re-runs the configured provider for a single key and saves the result. This is the paid
|
|
2081
|
-
* counterpart to {@link editEntry}: it calls the provider and therefore spends tokens, which is why
|
|
2082
|
-
* a UI should gate it behind an explicit user action.
|
|
2083
|
-
*
|
|
2084
|
-
* The returned value goes through the same integrity gate as a full run, so a translation that
|
|
2085
|
-
* loses a placeholder or breaks ICU syntax is refused and nothing is written. Provider quality
|
|
2086
|
-
* signals are surfaced on an accepted result as `reviewReasons` rather than blocking the write.
|
|
2087
|
-
*
|
|
2088
|
-
* The locale's write lock is taken before the provider is called and held across it, covering the
|
|
2089
|
-
* whole read-translate-write cycle, so a slow provider keeps the lock for the length of its call
|
|
2090
|
-
* and a concurrent {@link translate} run on that locale waits. The lock is held for a refused
|
|
2091
|
-
* translation too, since the gate runs inside it. An accepted value then updates the lock-file
|
|
2092
|
-
* baseline and feeds the translation memory, so a later {@link translate} run sees the key as up
|
|
2093
|
-
* to date.
|
|
2094
|
-
*
|
|
2095
|
-
* Note that the target locale file surfaces the adapter's own error and code rather than a wrapped
|
|
2096
|
-
* {@link SdkError}, on the write as well as on the read, because only the source read is wrapped.
|
|
2097
|
-
* A malformed target file fails the read with a message naming the offending locale and the
|
|
2098
|
-
* resolved path. The write raises the adapter's error too, when the entries cannot be represented
|
|
2099
|
-
* in the configured format or the existing destination file cannot be read back to be updated in
|
|
2100
|
-
* place, and that error is re-thrown unchanged so its own code survives for a caller that maps
|
|
2101
|
-
* adapter codes to its own copy. A caller that maps SDK codes should be ready for an unrecognized
|
|
2102
|
-
* error from a target file on either path.
|
|
2103
|
-
*
|
|
2104
|
-
* @param input - The config, locale, and key to retranslate.
|
|
2105
|
-
* @param deps - Optional adapter registry, provider factory, and file-system overrides.
|
|
2106
|
-
* @returns Whether the new value was accepted, with review reasons or the rejection reason.
|
|
2107
|
-
*
|
|
2108
|
-
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
2109
|
-
* @throws {@link SdkError} `UNKNOWN_LOCALE`: the requested locale is not a configured target locale.
|
|
2110
|
-
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
2111
|
-
* cannot be combined, or the locale has no valid path spelling under that style.
|
|
2112
|
-
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
2113
|
-
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
2114
|
-
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
2115
|
-
* @throws {@link SdkError} `UNKNOWN_KEY`: the key is not present in the source resource.
|
|
2116
|
-
* @throws {@link SdkError} `PROVIDER_CONSTRUCTION_FAILED`: the provider could not be constructed,
|
|
2117
|
-
* most often because its API key environment variable is unset.
|
|
2118
|
-
* @throws {@link SdkError} `LOCK_CONTENDED`: the locale's write lock could not be acquired before
|
|
2119
|
-
* the timeout elapsed.
|
|
2120
|
-
* @throws {@link SdkError} `TARGET_UNWRITABLE`: the target locale file could not be written because
|
|
2121
|
-
* of a file-system failure. The message names the target file and the file-system code, never the
|
|
2122
|
-
* internal temporary file.
|
|
2123
|
-
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
2124
|
-
* unsupported version.
|
|
2125
|
-
* @throws `AdapterError`: the adapter itself refused the target locale file, on the read because it
|
|
2126
|
-
* is malformed or on the write because the entries cannot be represented in the configured format.
|
|
2127
|
-
* Its own code is preserved rather than remapped onto an {@link SdkErrorCode}.
|
|
2128
|
-
* @throws `ProviderError` `INVALID_RESPONSE`: the provider returned no value for the key. Provider
|
|
2129
|
-
* transport and rate-limit failures propagate as `ProviderError` too, since a single-key call has
|
|
2130
|
-
* no per-locale summary to record them on.
|
|
2131
|
-
*/
|
|
2132
|
-
declare function retranslateEntry(input: RetranslateEntryInput, deps?: RetranslateEntryDeps): Promise<RetranslateEntryResult>;
|
|
2133
|
-
|
|
2134
|
-
/**
|
|
2135
|
-
* Conditions the SDK itself reports on a locale, as opposed to the {@link ProviderNotice} codes a
|
|
2136
|
-
* provider raises. Each marks a run that completed but did something a caller may want to know
|
|
2137
|
-
* about.
|
|
2138
|
-
*
|
|
2139
|
-
* - `PLURAL_CATEGORIES_INCOMPLETE`: plural generation could not produce every category the target
|
|
2140
|
-
* language requires, so the entry is written with the categories that were produced.
|
|
2141
|
-
* - `SUB_BATCH_FAILED`: one sub-batch of a locale failed while others succeeded. The locale is
|
|
2142
|
-
* reported as `partial` rather than failed.
|
|
2143
|
-
* - `BLANK_ROW_BASELINE_RETAINED`: an imported handoff row was blank, so the existing translation
|
|
2144
|
-
* and its lock-file baseline were kept rather than being erased.
|
|
2145
|
-
* - `BUDGET_TOKENS_EXCEEDED`: the configured token budget was passed. Under `warn` the run
|
|
2146
|
-
* continues; under `stop` the remaining keys are withheld.
|
|
2147
|
-
* - `CACHE_VERSION_UNRECOGNIZED`: the translation memory is at a version this release does not
|
|
2148
|
-
* understand, so it was ignored rather than trusted.
|
|
2149
|
-
*/
|
|
2150
|
-
type SdkNoticeCode = "PLURAL_CATEGORIES_INCOMPLETE" | "SUB_BATCH_FAILED" | "BLANK_ROW_BASELINE_RETAINED" | "BUDGET_TOKENS_EXCEEDED" | "CACHE_VERSION_UNRECOGNIZED";
|
|
2151
|
-
/**
|
|
2152
|
-
* Token usage as reported by the provider. Absent when the provider does not report usage, which is
|
|
2153
|
-
* the case for machine-translation APIs such as DeepL that do not bill in tokens.
|
|
2154
|
-
*/
|
|
2155
|
-
interface UsageSummary {
|
|
2156
|
-
/** Tokens consumed by the prompts sent to the provider. */
|
|
2157
|
-
readonly inputTokens: number;
|
|
2158
|
-
/** Tokens consumed by the provider's responses. */
|
|
2159
|
-
readonly outputTokens: number;
|
|
2160
|
-
}
|
|
2161
|
-
/**
|
|
2162
|
-
* What a run does when it passes its token budget: `warn` finishes the work and reports the
|
|
2163
|
-
* overrun, `stop` withholds the remaining keys.
|
|
2164
|
-
*/
|
|
2165
|
-
type BudgetBehavior = "warn" | "stop";
|
|
2166
|
-
/** The token budget in force for a run, and how much of it was actually consumed. */
|
|
2167
|
-
interface RunBudget {
|
|
2168
|
-
/** The configured ceiling, in tokens. */
|
|
2169
|
-
readonly maxTokens: number;
|
|
2170
|
-
/** Whether passing the ceiling warns or stops the run. */
|
|
2171
|
-
readonly behavior: BudgetBehavior;
|
|
4024
|
+
readonly locale?: string;
|
|
2172
4025
|
/**
|
|
2173
|
-
*
|
|
2174
|
-
*
|
|
4026
|
+
* Directory the pseudolocale file is written under, relative to `cwd`. Defaults to
|
|
4027
|
+
* `.verbatra-local/pseudo`, which `verbatra init` already adds to `.gitignore`. The configured
|
|
4028
|
+
* `files.pattern` is expanded inside it, so the file keeps the layout the application expects.
|
|
4029
|
+
*
|
|
4030
|
+
* It must name a directory inside `cwd`: an absolute path, one that climbs out with `..`, and
|
|
4031
|
+
* `cwd` itself are all refused, as is any directory that already holds a configured locale file,
|
|
4032
|
+
* so a generated pseudolocale never lands outside the project or beside the real translations
|
|
4033
|
+
* where nothing ignores it.
|
|
2175
4034
|
*/
|
|
2176
|
-
readonly
|
|
2177
|
-
/** Tokens consumed across the whole run so far. */
|
|
2178
|
-
readonly tokensUsed: number;
|
|
2179
|
-
/** True once `tokensUsed` passed `maxTokens`. */
|
|
2180
|
-
readonly exceeded: boolean;
|
|
2181
|
-
}
|
|
2182
|
-
/** A condition the SDK reported on a locale. See {@link SdkNoticeCode}. */
|
|
2183
|
-
interface SdkNotice {
|
|
2184
|
-
/** The stable notice code. Branch on this, not on the message. */
|
|
2185
|
-
readonly code: SdkNoticeCode;
|
|
2186
|
-
/** A human-readable description of the condition. */
|
|
2187
|
-
readonly message: string;
|
|
4035
|
+
readonly out?: string;
|
|
2188
4036
|
}
|
|
2189
|
-
/**
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
* A translated key the provider layer flagged as worth a human look. The translation was still
|
|
2196
|
-
* written; these are advisory quality signals, not rejections.
|
|
2197
|
-
*/
|
|
2198
|
-
interface NeedsReviewEntry {
|
|
2199
|
-
/** The key that was flagged. */
|
|
2200
|
-
readonly key: string;
|
|
2201
|
-
/** Why it was flagged. A key can carry more than one reason. */
|
|
2202
|
-
readonly reasons: readonly ReviewReasonCode[];
|
|
4037
|
+
/** Injectable dependencies for {@link pseudolocalize}. Every field has a working default. */
|
|
4038
|
+
interface PseudolocalizeDeps {
|
|
4039
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
4040
|
+
readonly adapterRegistry?: AdapterRegistry;
|
|
4041
|
+
/** File-system port. Defaults to the real file system. */
|
|
4042
|
+
readonly fs?: SdkFs;
|
|
2203
4043
|
}
|
|
2204
|
-
/**
|
|
2205
|
-
interface
|
|
2206
|
-
/** The
|
|
2207
|
-
readonly
|
|
4044
|
+
/** The result of {@link pseudolocalize}: what was generated and where it landed. */
|
|
4045
|
+
interface PseudolocalizeResult {
|
|
4046
|
+
/** The pseudolocale that was generated. */
|
|
4047
|
+
readonly locale: string;
|
|
4048
|
+
/** Absolute path of the file that was written, or would have been written on a changed run. */
|
|
4049
|
+
readonly path: string;
|
|
4050
|
+
/** How many source entries were processed. */
|
|
4051
|
+
readonly entries: number;
|
|
4052
|
+
/** How many entries carry a pseudolocalized value. */
|
|
4053
|
+
readonly transformed: number;
|
|
2208
4054
|
/**
|
|
2209
|
-
*
|
|
2210
|
-
*
|
|
4055
|
+
* Keys whose source value was copied verbatim because a pseudolocalized value would not have
|
|
4056
|
+
* passed the same integrity gate a translation must pass. Never a failure: the entry is still
|
|
4057
|
+
* present and still renders.
|
|
2211
4058
|
*/
|
|
2212
|
-
readonly
|
|
2213
|
-
/**
|
|
2214
|
-
readonly
|
|
2215
|
-
}
|
|
2216
|
-
/**
|
|
2217
|
-
* A key that appeared more than once in an imported handoff. The first occurrence wins and the rest
|
|
2218
|
-
* are reported here, so a translator who duplicated a row learns which value was actually used.
|
|
2219
|
-
*/
|
|
2220
|
-
interface DuplicateKeyReport {
|
|
2221
|
-
/** The key that appeared more than once. */
|
|
2222
|
-
readonly key: string;
|
|
2223
|
-
/** The 1-based row index of the ignored occurrence. */
|
|
2224
|
-
readonly row: number;
|
|
2225
|
-
/** The 1-based physical line of the ignored occurrence, for delimited formats. Absent for `.xlsx`. */
|
|
2226
|
-
readonly line?: number;
|
|
4059
|
+
readonly copied: readonly string[];
|
|
4060
|
+
/** False when the file on disk already matched, so nothing was rewritten. */
|
|
4061
|
+
readonly written: boolean;
|
|
2227
4062
|
}
|
|
2228
4063
|
/**
|
|
2229
|
-
*
|
|
2230
|
-
*
|
|
4064
|
+
* Generates a pseudolocale from the source strings alone: every value is accented, expanded by
|
|
4065
|
+
* roughly a third of its translatable length, and wrapped in `[` and `]` boundary markers, so a
|
|
4066
|
+
* truncated or concatenated string is obvious on screen and an untranslated hardcoded string stands
|
|
4067
|
+
* out for having escaped the transform.
|
|
4068
|
+
*
|
|
4069
|
+
* It constructs no provider, reads no API key and makes no network request, so it runs on a fresh
|
|
4070
|
+
* checkout before any key exists or any budget is approved.
|
|
4071
|
+
*
|
|
4072
|
+
* Placeholders survive untouched in every syntax the shipped adapters recognise, and every
|
|
4073
|
+
* generated value is held to the same integrity gate a provider's translation must pass. A value
|
|
4074
|
+
* that would not pass it is copied from the source verbatim and reported in
|
|
4075
|
+
* {@link PseudolocalizeResult.copied} rather than written in a broken form.
|
|
4076
|
+
*
|
|
4077
|
+
* The output is deliberately kept out of the project's real locale files: it is written under
|
|
4078
|
+
* `.verbatra-local/pseudo` by default, which `verbatra init` already adds to `.gitignore`, and the
|
|
4079
|
+
* pseudolocale is refused when it names a configured locale, when the output directory already
|
|
4080
|
+
* holds a configured locale file, and when it is directed at the project root or outside the
|
|
4081
|
+
* working directory. Because it lives outside `files.pattern`, {@link translate} never spends on it and
|
|
4082
|
+
* {@link check} and {@link diff} never report it as drifted.
|
|
4083
|
+
*
|
|
4084
|
+
* The transform is deterministic, so a second run over unchanged source rewrites nothing and
|
|
4085
|
+
* reports {@link PseudolocalizeResult.written} as false. For `xliff` and `apple-xcstrings`, whose
|
|
4086
|
+
* writers only patch units already present in the destination, the output is re-copied from the
|
|
4087
|
+
* source on every write, so a key added to the source after the first run still reaches the
|
|
4088
|
+
* pseudolocale; a copied XLIFF has its target-language attribute rewritten to the pseudolocale, so
|
|
4089
|
+
* the file never misdescribes what it holds.
|
|
4090
|
+
*
|
|
4091
|
+
* @param input - The config, the pseudolocale code, and the output directory.
|
|
4092
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
4093
|
+
* @returns Where the pseudolocale landed, how many entries it carries, and whether it changed.
|
|
2231
4094
|
*
|
|
2232
|
-
*
|
|
2233
|
-
*
|
|
2234
|
-
*
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
* Keys whose source text is not valid ICU. They are skipped rather than sent to the provider,
|
|
2254
|
-
* because a broken source message cannot yield a sound translation.
|
|
2255
|
-
*/
|
|
2256
|
-
readonly invalidIcuSource: readonly string[];
|
|
2257
|
-
/** Keys served from the translation memory instead of the provider, and so not paid for. */
|
|
2258
|
-
readonly cacheHits: readonly string[];
|
|
2259
|
-
/**
|
|
2260
|
-
* Keys whose translation was refused by the integrity gate, for instance because it dropped a
|
|
2261
|
-
* placeholder. The previous translation, if any, is left untouched.
|
|
2262
|
-
*/
|
|
2263
|
-
readonly integrityMismatches: readonly string[];
|
|
2264
|
-
/** Keys the provider failed to translate, for instance because their sub-batch errored. */
|
|
2265
|
-
readonly providerFailures: readonly string[];
|
|
2266
|
-
/** Keys whose plural categories were generated for the target language rather than translated one by one. */
|
|
2267
|
-
readonly generated: readonly string[];
|
|
2268
|
-
/** Keys not translated because the token budget was exhausted under `stop` behavior. */
|
|
2269
|
-
readonly budgetWithheld: readonly string[];
|
|
2270
|
-
/** Token usage for this locale. Absent when the provider does not report usage. */
|
|
2271
|
-
readonly usage?: UsageSummary;
|
|
2272
|
-
/** Provider and SDK notices raised while running this locale. Always present, possibly empty. */
|
|
2273
|
-
readonly notices: readonly LocaleNotice[];
|
|
2274
|
-
/** Translated keys flagged as worth a human look. The translations were still written. */
|
|
2275
|
-
readonly needsReview: readonly NeedsReviewEntry[];
|
|
2276
|
-
/** Keys left with no translation after the run, whatever the cause. */
|
|
2277
|
-
readonly unfilled: readonly string[];
|
|
2278
|
-
/** Unreadable rows from an imported handoff. Always empty for a {@link translate} run. */
|
|
2279
|
-
readonly malformedRows: readonly MalformedRowReport[];
|
|
2280
|
-
/** Repeated keys from an imported handoff. Always empty for a {@link translate} run. */
|
|
2281
|
-
readonly duplicateKeys: readonly DuplicateKeyReport[];
|
|
2282
|
-
/**
|
|
2283
|
-
* Why this locale failed, when the failure came from a thrown error. The `code` is the failure's
|
|
2284
|
-
* own code where it has one, and `LOCALE_FAILED` otherwise. Never carries a secret.
|
|
2285
|
-
*
|
|
2286
|
-
* Absent unless `status` is `failed`, but a `failed` locale does not always carry it: a locale
|
|
2287
|
-
* whose every key was withheld by the integrity gate, a provider failure, or the token budget is
|
|
2288
|
-
* `failed` with nothing thrown, so this stays undefined and the withheld keys are the account of
|
|
2289
|
-
* what went wrong. Treat it as an optional detail on a failure, never as the failure test.
|
|
2290
|
-
*/
|
|
2291
|
-
readonly error?: {
|
|
2292
|
-
/** The failure's own code where it had one, and `LOCALE_FAILED` otherwise. */
|
|
2293
|
-
readonly code: string;
|
|
2294
|
-
/** A human-readable description of the failure. Never contains a secret. */
|
|
2295
|
-
readonly message: string;
|
|
2296
|
-
};
|
|
2297
|
-
}
|
|
4095
|
+
* @example
|
|
4096
|
+
* ```ts
|
|
4097
|
+
* const result = await pseudolocalize({ config });
|
|
4098
|
+
* console.log(`${result.transformed} of ${result.entries} entries in ${result.path}`);
|
|
4099
|
+
* ```
|
|
4100
|
+
*
|
|
4101
|
+
* @throws {@link SdkError} `PSEUDO_OUTPUT_CONFLICT`: the pseudolocale names a configured locale, the
|
|
4102
|
+
* output directory already holds a configured locale file, or it is not a relative path naming a
|
|
4103
|
+
* directory inside `cwd`.
|
|
4104
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
4105
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
4106
|
+
* cannot be combined, or the pseudolocale has no valid path spelling under that style.
|
|
4107
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
4108
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
4109
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed, or, for a
|
|
4110
|
+
* format whose writer only patches an existing document (`xliff` and `apple-xcstrings`), the
|
|
4111
|
+
* source file could not be copied to seed the output.
|
|
4112
|
+
* @throws {@link SdkError} `TARGET_UNWRITABLE`: the pseudolocale file could not be written.
|
|
4113
|
+
*/
|
|
4114
|
+
declare function pseudolocalize(input: PseudolocalizeInput, deps?: PseudolocalizeDeps): Promise<PseudolocalizeResult>;
|
|
4115
|
+
|
|
2298
4116
|
/**
|
|
2299
|
-
*
|
|
2300
|
-
* {@link
|
|
4117
|
+
* Builds a {@link TranslationProvider} from the config's `provider` block. Passed as
|
|
4118
|
+
* `deps.createProvider` to {@link translate}, {@link watch}, and {@link retranslateEntry}, it is the
|
|
4119
|
+
* seam for injecting a stub in a test or a provider the SDK does not ship.
|
|
2301
4120
|
*
|
|
2302
|
-
*
|
|
2303
|
-
*
|
|
2304
|
-
* or an unreadable source file, throw an {@link SdkError}.
|
|
4121
|
+
* The default implementation dispatches on the provider ID and reads the API key from the
|
|
4122
|
+
* environment. A factory that throws is reported as `PROVIDER_CONSTRUCTION_FAILED`.
|
|
2305
4123
|
*/
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
4124
|
+
type CreateProvider = (config: ProviderConfig) => TranslationProvider;
|
|
4125
|
+
|
|
4126
|
+
/** Input for {@link retranslateEntry}. */
|
|
4127
|
+
interface RetranslateEntryInput {
|
|
4128
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
4129
|
+
readonly config: VerbatraConfig;
|
|
4130
|
+
/** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
|
|
4131
|
+
readonly cwd?: string;
|
|
4132
|
+
/** The target locale to retranslate into. Must be a configured target locale. */
|
|
4133
|
+
readonly locale: string;
|
|
4134
|
+
/** The key to retranslate. Must exist in the source resource. */
|
|
4135
|
+
readonly key: string;
|
|
4136
|
+
}
|
|
4137
|
+
/** Injectable dependencies for {@link retranslateEntry}. Every field has a working default. */
|
|
4138
|
+
interface RetranslateEntryDeps {
|
|
4139
|
+
/** Format-adapter registry to resolve the configured format. Defaults to the built-in registry. */
|
|
4140
|
+
readonly adapterRegistry?: AdapterRegistry;
|
|
4141
|
+
/** Provider factory. Defaults to constructing the provider named in the config. */
|
|
4142
|
+
readonly createProvider?: CreateProvider;
|
|
4143
|
+
/** File-system port. Defaults to the real file system. */
|
|
4144
|
+
readonly fs?: SdkFs;
|
|
4145
|
+
}
|
|
4146
|
+
/**
|
|
4147
|
+
* The outcome of {@link retranslateEntry}. As with {@link EditEntryResult}, a value refused by the
|
|
4148
|
+
* integrity gate is reported as data rather than thrown, because it is an expected outcome of
|
|
4149
|
+
* asking a provider for a fresh translation.
|
|
4150
|
+
*/
|
|
4151
|
+
type RetranslateEntryResult = {
|
|
4152
|
+
/** The provider's value passed the integrity gate and was written. */
|
|
4153
|
+
readonly accepted: true;
|
|
4154
|
+
/** The newly translated value now stored for the key. */
|
|
4155
|
+
readonly value: string;
|
|
2309
4156
|
/**
|
|
2310
|
-
*
|
|
2311
|
-
*
|
|
2312
|
-
*
|
|
2313
|
-
* order the handoff yielded them, then appends the configured locales the handoff had nothing
|
|
2314
|
-
* for. Match an entry on its `locale` rather than on its position.
|
|
4157
|
+
* Quality signals the provider layer raised for this value, such as a length-ratio outlier or
|
|
4158
|
+
* a value identical to the source. Empty when nothing was flagged. The value is written
|
|
4159
|
+
* either way; these are advisory.
|
|
2315
4160
|
*/
|
|
2316
|
-
readonly
|
|
2317
|
-
|
|
2318
|
-
|
|
4161
|
+
readonly reviewReasons: readonly ReviewReasonCode[];
|
|
4162
|
+
} | {
|
|
4163
|
+
/** The value was refused; nothing was written and the previous translation is intact. */
|
|
4164
|
+
readonly accepted: false;
|
|
4165
|
+
/** Which integrity rule the provider's value broke. */
|
|
4166
|
+
readonly reason: IntegrityGateReason;
|
|
2319
4167
|
/**
|
|
2320
|
-
*
|
|
2321
|
-
*
|
|
2322
|
-
*
|
|
4168
|
+
* The specific tags behind a `markup` refusal, each prefixed with `-` for one the source had
|
|
4169
|
+
* and the candidate dropped or `+` for one the candidate invented. A candidate carrying more
|
|
4170
|
+
* than twice its source's inline tags and constructs is named as `+more than N inline tags`
|
|
4171
|
+
* instead, where N is at least 256. Absent when no single tag is at fault, such as markup
|
|
4172
|
+
* that came back mis-nested, and absent for every other reason.
|
|
2323
4173
|
*/
|
|
2324
|
-
readonly
|
|
2325
|
-
/**
|
|
2326
|
-
readonly
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
4174
|
+
readonly details?: readonly string[];
|
|
4175
|
+
/** The rejected value, echoed back so a UI can show what was refused. */
|
|
4176
|
+
readonly value: string;
|
|
4177
|
+
};
|
|
4178
|
+
/**
|
|
4179
|
+
* Re-runs the configured provider for a single key and saves the result. This is the paid
|
|
4180
|
+
* counterpart to {@link editEntry}: it calls the provider and therefore spends tokens, which is why
|
|
4181
|
+
* a UI should gate it behind an explicit user action.
|
|
4182
|
+
*
|
|
4183
|
+
* The returned value goes through the same integrity gate as a full run, so a translation that
|
|
4184
|
+
* loses a placeholder or breaks ICU syntax is refused and nothing is written. Provider quality
|
|
4185
|
+
* signals are surfaced on an accepted result as `reviewReasons` rather than blocking the write.
|
|
4186
|
+
*
|
|
4187
|
+
* The locale's write lock is taken before the provider is called and held across it, covering the
|
|
4188
|
+
* whole read-translate-write cycle, so a slow provider keeps the lock for the length of its call
|
|
4189
|
+
* and a concurrent {@link translate} run on that locale waits. The lock is held for a refused
|
|
4190
|
+
* translation too, since the gate runs inside it. An accepted value then updates the lock-file
|
|
4191
|
+
* baseline and feeds the translation memory, so a later {@link translate} run sees the key as up
|
|
4192
|
+
* to date.
|
|
4193
|
+
*
|
|
4194
|
+
* Note that the target locale file surfaces the adapter's own error and code rather than a wrapped
|
|
4195
|
+
* {@link SdkError}, on the write as well as on the read, because only the source read is wrapped.
|
|
4196
|
+
* A malformed target file fails the read with a message naming the offending locale and the
|
|
4197
|
+
* resolved path. The write raises the adapter's error too, when the entries cannot be represented
|
|
4198
|
+
* in the configured format or the existing destination file cannot be read back to be updated in
|
|
4199
|
+
* place, and that error is re-thrown unchanged so its own code survives for a caller that maps
|
|
4200
|
+
* adapter codes to its own copy. A caller that maps SDK codes should be ready for an unrecognized
|
|
4201
|
+
* error from a target file on either path.
|
|
4202
|
+
*
|
|
4203
|
+
* It spends outside the token budget. `maxTokens` and `budgetBehavior` bound a {@link translate}
|
|
4204
|
+
* run, and this is its own single-key path with its own provider, so a configured ceiling neither
|
|
4205
|
+
* withholds this call nor counts it. A caller that exposes retranslation to users, as the Studio
|
|
4206
|
+
* dashboard and the agent tools do, has to bound that spend itself.
|
|
4207
|
+
*
|
|
4208
|
+
* @param input - The config, locale, and key to retranslate.
|
|
4209
|
+
* @param deps - Optional adapter registry, provider factory, and file-system overrides.
|
|
4210
|
+
* @returns Whether the new value was accepted, with review reasons or the rejection reason.
|
|
4211
|
+
*
|
|
4212
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
4213
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE`: the requested locale is not a configured target locale.
|
|
4214
|
+
* @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
|
|
4215
|
+
* cannot be combined, or the locale has no valid path spelling under that style.
|
|
4216
|
+
* @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
|
|
4217
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
4218
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be parsed.
|
|
4219
|
+
* @throws {@link SdkError} `UNKNOWN_KEY`: the key is not present in the source resource.
|
|
4220
|
+
* @throws {@link SdkError} `PROVIDER_CONSTRUCTION_FAILED`: the provider could not be constructed,
|
|
4221
|
+
* most often because its API key environment variable is unset.
|
|
4222
|
+
* @throws {@link SdkError} `LOCK_CONTENDED`: the locale's write lock could not be acquired before
|
|
4223
|
+
* the timeout elapsed.
|
|
4224
|
+
* @throws {@link SdkError} `TARGET_UNWRITABLE`: the target locale file could not be written because
|
|
4225
|
+
* of a file-system failure. The message names the target file and the file-system code, never the
|
|
4226
|
+
* internal temporary file.
|
|
4227
|
+
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
|
|
4228
|
+
* unsupported version.
|
|
4229
|
+
* @throws `AdapterError`: the adapter itself refused the target locale file, on the read because it
|
|
4230
|
+
* is malformed or on the write because the entries cannot be represented in the configured format.
|
|
4231
|
+
* Its own code is preserved rather than remapped onto an {@link SdkErrorCode}.
|
|
4232
|
+
* @throws `ProviderError` `INVALID_RESPONSE`: the provider returned no value for the key. Provider
|
|
4233
|
+
* transport and rate-limit failures propagate as `ProviderError` too, since a single-key call has
|
|
4234
|
+
* no per-locale summary to record them on.
|
|
4235
|
+
*/
|
|
4236
|
+
declare function retranslateEntry(input: RetranslateEntryInput, deps?: RetranslateEntryDeps): Promise<RetranslateEntryResult>;
|
|
2332
4237
|
|
|
2333
4238
|
/**
|
|
2334
4239
|
* One locale's entry in a persisted {@link RunStatusFile}. It is a deliberately narrow projection
|
|
@@ -2342,6 +4247,14 @@ interface RunStatusLocale {
|
|
|
2342
4247
|
readonly status: "succeeded" | "partial" | "failed";
|
|
2343
4248
|
/** Keys the run flagged as worth a human look. */
|
|
2344
4249
|
readonly needsReview: readonly NeedsReviewEntry[];
|
|
4250
|
+
/**
|
|
4251
|
+
* Translations reused for a source string that had changed, with the score and the earlier
|
|
4252
|
+
* source behind each one. Every key listed here also carries `FUZZY_CACHE_REUSE` in
|
|
4253
|
+
* {@link needsReview}; this is the evidence a reviewer needs to judge the reuse without
|
|
4254
|
+
* re-running. Absent for a locale that reused nothing, and for a file written before the field
|
|
4255
|
+
* existed.
|
|
4256
|
+
*/
|
|
4257
|
+
readonly fuzzyHits?: readonly FuzzyCacheHit[];
|
|
2345
4258
|
/** Token usage for this locale. Absent when the provider does not report usage. */
|
|
2346
4259
|
readonly usage?: UsageSummary;
|
|
2347
4260
|
}
|
|
@@ -2360,7 +4273,11 @@ interface RunStatusFile {
|
|
|
2360
4273
|
readonly generatedAt: string;
|
|
2361
4274
|
/** Token usage summed across every locale. Absent when the provider does not report usage. */
|
|
2362
4275
|
readonly usage?: UsageSummary;
|
|
2363
|
-
/**
|
|
4276
|
+
/**
|
|
4277
|
+
* The token budget in force during the run, present only when one was configured. A snapshot
|
|
4278
|
+
* written before the budget was enforced, when `supported: false` meant nothing was counted at
|
|
4279
|
+
* all, has such a budget left out rather than read as a count of zero.
|
|
4280
|
+
*/
|
|
2364
4281
|
readonly budget?: RunBudget;
|
|
2365
4282
|
/** Per-locale outcomes from the recorded run. */
|
|
2366
4283
|
readonly locales: readonly RunStatusLocale[];
|
|
@@ -2407,6 +4324,314 @@ type RunStatusResult = {
|
|
|
2407
4324
|
*/
|
|
2408
4325
|
declare function runStatus(input?: RunStatusInput, deps?: RunStatusDeps): Promise<RunStatusResult>;
|
|
2409
4326
|
|
|
4327
|
+
/** Default output path for a TMX export, used when {@link ExportTmxInput.out} is omitted. */
|
|
4328
|
+
declare const DEFAULT_TMX_PATH = "verbatra-memory.tmx";
|
|
4329
|
+
/** How many translation units one locale contributed to the exported file. */
|
|
4330
|
+
interface ExportTmxLocaleCount {
|
|
4331
|
+
/** The configured target locale. */
|
|
4332
|
+
readonly locale: string;
|
|
4333
|
+
/** How many of its memory entries were written. */
|
|
4334
|
+
readonly units: number;
|
|
4335
|
+
}
|
|
4336
|
+
/** What {@link exportTmx} wrote. */
|
|
4337
|
+
interface ExportTmxResult {
|
|
4338
|
+
/** The resolved path the file was written to. */
|
|
4339
|
+
readonly path: string;
|
|
4340
|
+
/** How many `tu` elements the file holds, one per distinct source string. */
|
|
4341
|
+
readonly units: number;
|
|
4342
|
+
/** Per-locale contribution to that total. */
|
|
4343
|
+
readonly locales: readonly ExportTmxLocaleCount[];
|
|
4344
|
+
/**
|
|
4345
|
+
* Translations left out because the memory holds no source text for their content hash, counted
|
|
4346
|
+
* once per locale and hash rather than once per distinct source string. A cache carried forward
|
|
4347
|
+
* from an older schema stores translations without their source, and TMX cannot represent a unit
|
|
4348
|
+
* with no source segment. Those entries fill in as later runs touch the same strings.
|
|
4349
|
+
*/
|
|
4350
|
+
readonly withoutSource: number;
|
|
4351
|
+
/**
|
|
4352
|
+
* Characters removed from segment text because XML 1.0 cannot represent them at all, such as a
|
|
4353
|
+
* control character, a lone surrogate or a noncharacter, counted across every written segment.
|
|
4354
|
+
* Writing them would produce a file no conformant parser accepts, so they are left out and counted
|
|
4355
|
+
* rather than dropped silently.
|
|
4356
|
+
*/
|
|
4357
|
+
readonly illegalCharactersRemoved: number;
|
|
4358
|
+
}
|
|
4359
|
+
/** Input for {@link exportTmx}. */
|
|
4360
|
+
interface ExportTmxInput {
|
|
4361
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
4362
|
+
readonly config: VerbatraConfig;
|
|
4363
|
+
/** Where to write the file. Defaults to {@link DEFAULT_TMX_PATH}, resolved against `cwd`. */
|
|
4364
|
+
readonly out?: string;
|
|
4365
|
+
/** Directory the output path and the memory are resolved against. Defaults to the process working directory. */
|
|
4366
|
+
readonly cwd?: string;
|
|
4367
|
+
/** Subset of configured target locales to export. Defaults to all of them. */
|
|
4368
|
+
readonly locales?: readonly string[];
|
|
4369
|
+
/** Value for the TMX header's `creationtoolversion`. Defaults to `unknown`. */
|
|
4370
|
+
readonly toolVersion?: string;
|
|
4371
|
+
}
|
|
4372
|
+
/** Injectable dependencies for {@link exportTmx}. Every field has a working default. */
|
|
4373
|
+
interface ExportTmxDeps {
|
|
4374
|
+
/** File-system port. Defaults to the real file system. */
|
|
4375
|
+
readonly fs?: SdkFs;
|
|
4376
|
+
}
|
|
4377
|
+
/**
|
|
4378
|
+
* Writes the project's translation memory out as a TMX 1.4b file, the format every mainstream
|
|
4379
|
+
* translation platform can read. It calls no provider and needs no API key: every value comes from
|
|
4380
|
+
* the memory already on disk.
|
|
4381
|
+
*
|
|
4382
|
+
* Only entries stored under the project's current configuration fingerprint are written, which is
|
|
4383
|
+
* the same set a run would reuse, so the file describes the memory as it is actually being used
|
|
4384
|
+
* rather than every translation the project has ever produced. One `tu` element is written per
|
|
4385
|
+
* distinct source string, carrying the source segment and one target segment per exported locale.
|
|
4386
|
+
* An empty memory produces a valid, empty TMX file rather than an error.
|
|
4387
|
+
*
|
|
4388
|
+
* @param input - The config, the output path, the locale subset, and the tool version to stamp.
|
|
4389
|
+
* @param deps - Optional file-system override.
|
|
4390
|
+
* @returns The path written, the unit count, and what was left out.
|
|
4391
|
+
*
|
|
4392
|
+
* It reads the memory and writes a file; it never changes the memory, which is why the CLI refuses
|
|
4393
|
+
* `--dry-run` and `--overwrite` on this direction rather than accepting and ignoring them.
|
|
4394
|
+
*
|
|
4395
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE`: a requested locale is not a configured target locale.
|
|
4396
|
+
* @throws {@link SdkError} `CONFIG_INVALID`: the source locale and one of the target locales are the
|
|
4397
|
+
* same language tag once case and separators are normalized. Writing that file would produce two
|
|
4398
|
+
* language attributes {@link importTmx} could not tell apart, so the file this project exported
|
|
4399
|
+
* would be one it refuses to read back.
|
|
4400
|
+
*
|
|
4401
|
+
* @example
|
|
4402
|
+
* ```ts
|
|
4403
|
+
* const result = await exportTmx({ config, out: "memory.tmx" });
|
|
4404
|
+
* console.log(`${result.units} units written to ${result.path}`);
|
|
4405
|
+
* ```
|
|
4406
|
+
*/
|
|
4407
|
+
declare function exportTmx(input: ExportTmxInput, deps?: ExportTmxDeps): Promise<ExportTmxResult>;
|
|
4408
|
+
|
|
4409
|
+
/**
|
|
4410
|
+
* The two delimiter-separated interchange formats. They are one implementation with one parameter, the
|
|
4411
|
+
* field delimiter: `tsv` is not a second format, it is `csv` with a tab. The member is also the file
|
|
4412
|
+
* extension an export writes and an import reads back.
|
|
4413
|
+
*/
|
|
4414
|
+
type DelimitedFormat = "csv" | "tsv";
|
|
4415
|
+
/** Where in an interchange file a refusal happened. */
|
|
4416
|
+
interface ExchangeErrorLocation {
|
|
4417
|
+
/** The 1-based line. */
|
|
4418
|
+
readonly line: number;
|
|
4419
|
+
/** The 1-based column on that line. */
|
|
4420
|
+
readonly column: number;
|
|
4421
|
+
/** The 1-based ordinal of the translation unit the problem sits in, when it sits in one. */
|
|
4422
|
+
readonly unit?: number;
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
/**
|
|
4426
|
+
* Why one translation unit in an imported TMX file was refused. The first five are the shared
|
|
4427
|
+
* integrity gate's own reasons, so an imported unit is held to exactly the standard a provider's
|
|
4428
|
+
* output and a filled translator handoff already face. `sourceBlank` is the one reason particular
|
|
4429
|
+
* to interchange: a unit whose source segment is blank identifies no string, so it could never be
|
|
4430
|
+
* matched and is never stored.
|
|
4431
|
+
*/
|
|
4432
|
+
type TmxRejectionReason = IntegrityGateReason | "sourceBlank";
|
|
4433
|
+
/**
|
|
4434
|
+
* Where in a TMX file a `SOURCE_INVALID` refusal from {@link importTmx} happened: the 1-based `line`
|
|
4435
|
+
* and `column`, and the 1-based `unit` ordinal when the problem sits inside a translation unit. Read
|
|
4436
|
+
* it from a caught error with {@link tmxErrorLocation}; the same place is named in the message.
|
|
4437
|
+
*/
|
|
4438
|
+
type TmxErrorLocation = ExchangeErrorLocation;
|
|
4439
|
+
/**
|
|
4440
|
+
* Reads where in a TMX file a refusal from {@link importTmx} happened, without casting the error or
|
|
4441
|
+
* its `cause`.
|
|
4442
|
+
*
|
|
4443
|
+
* @param error - Whatever was caught from `importTmx`.
|
|
4444
|
+
* @returns The {@link TmxErrorLocation} when `error` is an {@link SdkError} wrapping a TMX parse
|
|
4445
|
+
* failure that has a place in the file; `undefined` for any other error, including a refusal with
|
|
4446
|
+
* no place in the file such as a missing or oversized file.
|
|
4447
|
+
*
|
|
4448
|
+
* @example
|
|
4449
|
+
* ```ts
|
|
4450
|
+
* try {
|
|
4451
|
+
* await importTmx({ config, file: "legacy.tmx" });
|
|
4452
|
+
* } catch (error) {
|
|
4453
|
+
* const at = tmxErrorLocation(error);
|
|
4454
|
+
* if (at !== undefined) console.error(`line ${at.line}, column ${at.column}`);
|
|
4455
|
+
* }
|
|
4456
|
+
* ```
|
|
4457
|
+
*/
|
|
4458
|
+
declare function tmxErrorLocation(error: unknown): TmxErrorLocation | undefined;
|
|
4459
|
+
/** How many units were refused, by reason. See {@link TmxRejectionReason}. */
|
|
4460
|
+
type TmxRejectionCounts = Readonly<Record<TmxRejectionReason, number>>;
|
|
4461
|
+
/** What an import did to the memory for one target locale. */
|
|
4462
|
+
interface ImportTmxLocaleResult {
|
|
4463
|
+
/** The configured target locale, spelled as the config spells it. */
|
|
4464
|
+
readonly locale: string;
|
|
4465
|
+
/** Units stored for the first time. */
|
|
4466
|
+
readonly added: number;
|
|
4467
|
+
/** Units the memory already held with the same translation, so nothing changed. */
|
|
4468
|
+
readonly unchanged: number;
|
|
4469
|
+
/** Units that replaced a different existing translation, which needs `overwrite`. */
|
|
4470
|
+
readonly overwritten: number;
|
|
4471
|
+
/** Units refused because the memory already held a different translation and `overwrite` was off. */
|
|
4472
|
+
readonly kept: number;
|
|
4473
|
+
/** Later units in the same file repeating a source the file had already translated. */
|
|
4474
|
+
readonly duplicates: number;
|
|
4475
|
+
/**
|
|
4476
|
+
* Units carrying two segments of equal standing for this locale with different values, such as
|
|
4477
|
+
* `de-CH` and `de-AT` in a project configured for `de`. Which one is right cannot be known, so
|
|
4478
|
+
* neither is stored. An exact tag always outranks one that only reaches the locale by subtag
|
|
4479
|
+
* prefix, and two segments carrying the same value are not a conflict.
|
|
4480
|
+
*/
|
|
4481
|
+
readonly conflicting: number;
|
|
4482
|
+
/** Units refused before they could be stored, by reason. */
|
|
4483
|
+
readonly rejected: TmxRejectionCounts;
|
|
4484
|
+
}
|
|
4485
|
+
/** A language tag in the file that no configured locale could be resolved to. */
|
|
4486
|
+
interface TmxLanguageReport {
|
|
4487
|
+
/** The tag exactly as the file spelled it. */
|
|
4488
|
+
readonly language: string;
|
|
4489
|
+
/** How many units carried a segment in it. */
|
|
4490
|
+
readonly units: number;
|
|
4491
|
+
}
|
|
4492
|
+
/** What {@link importTmx} read and what it wrote. */
|
|
4493
|
+
interface ImportTmxResult {
|
|
4494
|
+
/** Whether the run was a dry run, in which case the memory file was not written. */
|
|
4495
|
+
readonly dryRun: boolean;
|
|
4496
|
+
/** The resolved path the file was read from. */
|
|
4497
|
+
readonly file: string;
|
|
4498
|
+
/** The `srclang` the file's header declared, or undefined if it declared none. */
|
|
4499
|
+
readonly sourceLanguage: string | undefined;
|
|
4500
|
+
/** How many translation units the file held. */
|
|
4501
|
+
readonly units: number;
|
|
4502
|
+
/** Per-target-locale account of what was stored. */
|
|
4503
|
+
readonly locales: readonly ImportTmxLocaleResult[];
|
|
4504
|
+
/** Units the reader could not use at all, such as a unit with no segment. */
|
|
4505
|
+
readonly skippedUnits: number;
|
|
4506
|
+
/** `tu` elements outside the file's first `body`, which the reader never walked. */
|
|
4507
|
+
readonly unreachableUnits: number;
|
|
4508
|
+
/**
|
|
4509
|
+
* The header's `srclang` when it does not resolve to the configured source locale, so a file
|
|
4510
|
+
* exported from a project with a different source language is visible rather than silently
|
|
4511
|
+
* yielding nothing. Undefined when it matches, when the header declares none, or when it is the
|
|
4512
|
+
* TMX `*all*` wildcard, which declares that the source varies per unit.
|
|
4513
|
+
*/
|
|
4514
|
+
readonly sourceLanguageMismatch: string | undefined;
|
|
4515
|
+
/** Units carrying no segment in the configured source locale, so nothing could be keyed. */
|
|
4516
|
+
readonly unmatchedSourceUnits: number;
|
|
4517
|
+
/**
|
|
4518
|
+
* Units carrying two segments of equal standing that resolve to the configured source locale with
|
|
4519
|
+
* different values, such as an `en-US` "Color" and an `en-GB` "Colour" segment in a project
|
|
4520
|
+
* configured for `en`. An exact tag outranks a prefix match and identical values agree, so neither
|
|
4521
|
+
* counts here. Which value the translations belong to cannot be known, so the unit is refused
|
|
4522
|
+
* rather than attributed to whichever came last.
|
|
4523
|
+
*/
|
|
4524
|
+
readonly conflictingSourceUnits: number;
|
|
4525
|
+
/** Units whose segments carried inline markup, which is flattened to its text. */
|
|
4526
|
+
readonly markupStrippedUnits: number;
|
|
4527
|
+
/**
|
|
4528
|
+
* Units whose inline markup carried a `sub` element, a sub-flow such as a tooltip or alternative
|
|
4529
|
+
* text embedded in the markup. Its text is not part of the segment's own string, so it is left out
|
|
4530
|
+
* of the segment rather than flattened into it.
|
|
4531
|
+
*/
|
|
4532
|
+
readonly subflowDroppedUnits: number;
|
|
4533
|
+
/** Language tags that resolved to no configured locale, with how many units carried each. */
|
|
4534
|
+
readonly unmatchedLanguages: readonly TmxLanguageReport[];
|
|
4535
|
+
/** Language tags that two or more configured locales could claim, so none was chosen. */
|
|
4536
|
+
readonly ambiguousLanguages: readonly TmxLanguageReport[];
|
|
4537
|
+
/**
|
|
4538
|
+
* Segments that resolved cleanly to a configured target locale this run left out, because
|
|
4539
|
+
* {@link ImportTmxInput.locales} narrowed it. Nothing is wrong with them; they are reported so a
|
|
4540
|
+
* narrowed run does not look like a file that held less than it did.
|
|
4541
|
+
*/
|
|
4542
|
+
readonly notImported: readonly TmxLanguageReport[];
|
|
4543
|
+
/**
|
|
4544
|
+
* Whether the memory file could be written. False when the project's cache was written by a newer
|
|
4545
|
+
* build, which is left untouched rather than downgraded.
|
|
4546
|
+
*/
|
|
4547
|
+
readonly memoryWritable: boolean;
|
|
4548
|
+
}
|
|
4549
|
+
/** Input for {@link importTmx}. */
|
|
4550
|
+
interface ImportTmxInput {
|
|
4551
|
+
/** The resolved project config, normally from {@link loadConfig}. */
|
|
4552
|
+
readonly config: VerbatraConfig;
|
|
4553
|
+
/** Path to the TMX file, resolved against `cwd`. */
|
|
4554
|
+
readonly file: string;
|
|
4555
|
+
/** Directory the file and the memory are resolved against. Defaults to the process working directory. */
|
|
4556
|
+
readonly cwd?: string;
|
|
4557
|
+
/** Read and validate the file but write nothing. Defaults to false. */
|
|
4558
|
+
readonly dryRun?: boolean;
|
|
4559
|
+
/**
|
|
4560
|
+
* Let an imported unit replace a translation the memory already holds for the same source and
|
|
4561
|
+
* locale. Off by default, so the project's own memory wins a collision.
|
|
4562
|
+
*/
|
|
4563
|
+
readonly overwrite?: boolean;
|
|
4564
|
+
/** Subset of configured target locales to import. Defaults to all of them. */
|
|
4565
|
+
readonly locales?: readonly string[];
|
|
4566
|
+
}
|
|
4567
|
+
/** Injectable dependencies for {@link importTmx}. Every field has a working default. */
|
|
4568
|
+
interface ImportTmxDeps {
|
|
4569
|
+
/** Format-adapter registry, used for placeholder extraction and ICU validation. Defaults to the built-in registry. */
|
|
4570
|
+
readonly adapterRegistry?: AdapterRegistry;
|
|
4571
|
+
/** File-system port. Defaults to the real file system. */
|
|
4572
|
+
readonly fs?: SdkFs;
|
|
4573
|
+
}
|
|
4574
|
+
/**
|
|
4575
|
+
* Reads a TMX translation memory into the project's own memory, so a team arriving with years of
|
|
4576
|
+
* accumulated translations does not start cold. It calls no provider and needs no API key: every
|
|
4577
|
+
* value comes from the file.
|
|
4578
|
+
*
|
|
4579
|
+
* Nothing from the file is trusted. The XML is parsed under the interchange package's hardening
|
|
4580
|
+
* (entity declarations refused, size and unit bounds enforced), a language tag is resolved to a
|
|
4581
|
+
* configured locale by an explicit rule rather than guessed at, and every candidate translation
|
|
4582
|
+
* must pass the same placeholder, inline markup, ICU, degeneracy and emptiness gate that a
|
|
4583
|
+
* provider's output and a filled translator handoff already face before it is stored. A unit that
|
|
4584
|
+
* fails is counted, never written, so it can never later be served as a cache hit that was never
|
|
4585
|
+
* validated.
|
|
4586
|
+
*
|
|
4587
|
+
* Units are stored under the project's current configuration fingerprint, the same key a real run
|
|
4588
|
+
* writes. That is deliberate: an imported memory is reused exactly when the configuration that
|
|
4589
|
+
* would consume it matches, and stops matching when the provider, model, tone or glossary changes,
|
|
4590
|
+
* which is the protection the fingerprint layer exists to give. Re-import after such a change.
|
|
4591
|
+
*
|
|
4592
|
+
* A collision is decided in favour of what the project already has: if the memory already holds a
|
|
4593
|
+
* different translation for the same source and locale, the imported one is refused and counted as
|
|
4594
|
+
* `kept` unless {@link ImportTmxInput.overwrite} is set. Importing the same file twice therefore
|
|
4595
|
+
* changes nothing the second time, and writes no file at all.
|
|
4596
|
+
*
|
|
4597
|
+
* One consequence is worth stating plainly, because no import-time check can police it. An accepted
|
|
4598
|
+
* unit's source text is written into the memory's source index, which is the same index fuzzy reuse
|
|
4599
|
+
* scores a changed string against. An imported source that DIFFERS from a string in the project can
|
|
4600
|
+
* therefore be served for that string by fuzzy reuse, which is what resemblance means.
|
|
4601
|
+
*
|
|
4602
|
+
* An imported source that is identical to a project string but hashes differently is not reachable
|
|
4603
|
+
* at all. Fuzzy reuse discards any candidate whose normalized source equals the query, so a project
|
|
4604
|
+
* entry that carries a description, a meaning, or a plural flag the unit cannot carry falls through
|
|
4605
|
+
* to the provider rather than being served: the hashes differ, and the identical text disqualifies
|
|
4606
|
+
* the fuzzy candidate.
|
|
4607
|
+
*
|
|
4608
|
+
* A fuzzy reuse is still held to the integrity gate against the real entry and is reported as a
|
|
4609
|
+
* `FUZZY_CACHE_REUSE` review flag on the run summary, so it is visible rather than silent. A project
|
|
4610
|
+
* that does not want an imported memory reachable that way should leave `fuzzyCache` out of its
|
|
4611
|
+
* config.
|
|
4612
|
+
*
|
|
4613
|
+
* @param input - The config, the file path, and the dry-run, overwrite and locale-subset switches.
|
|
4614
|
+
* @param deps - Optional adapter registry and file-system overrides.
|
|
4615
|
+
* @returns What was read, what was stored, and everything that was skipped or refused.
|
|
4616
|
+
*
|
|
4617
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
4618
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE`: a requested locale is not a configured target locale.
|
|
4619
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: no file exists at the given path.
|
|
4620
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the file is oversized, malformed, not a TMX document,
|
|
4621
|
+
* or declares an XML entity. When the problem has a place in the file, the message names its line,
|
|
4622
|
+
* column and, inside a translation unit, the unit's 1-based ordinal, and `cause` is the interchange
|
|
4623
|
+
* reader's error carrying the same as a structured `location`.
|
|
4624
|
+
*
|
|
4625
|
+
* @example
|
|
4626
|
+
* ```ts
|
|
4627
|
+
* const result = await importTmx({ config, file: "legacy-memory.tmx" });
|
|
4628
|
+
* for (const locale of result.locales) {
|
|
4629
|
+
* console.log(`${locale.locale}: ${locale.added} added, ${locale.kept} kept`);
|
|
4630
|
+
* }
|
|
4631
|
+
* ```
|
|
4632
|
+
*/
|
|
4633
|
+
declare function importTmx(input: ImportTmxInput, deps?: ImportTmxDeps): Promise<ImportTmxResult>;
|
|
4634
|
+
|
|
2410
4635
|
/**
|
|
2411
4636
|
* Whatever could be read about the process currently holding a write lock. Both fields are optional
|
|
2412
4637
|
* because a lock file left behind by a killed process may be truncated or unreadable, and reporting
|
|
@@ -2526,6 +4751,20 @@ interface TranslateInput {
|
|
|
2526
4751
|
* preview work without spending anything. Defaults to false.
|
|
2527
4752
|
*/
|
|
2528
4753
|
readonly dryRun?: boolean;
|
|
4754
|
+
/**
|
|
4755
|
+
* Compute a pre-run cost estimate and return it on {@link RunSummary.estimate}. Implies
|
|
4756
|
+
* `dryRun`: an estimate constructs no provider, reads no API key, makes no network call, and
|
|
4757
|
+
* writes no file, so it is safe to run anywhere. Defaults to false.
|
|
4758
|
+
*
|
|
4759
|
+
* The figure bounds the plan rather than the invoice: every provider call a live run schedules is
|
|
4760
|
+
* counted, plural generation included, and the prompt is measured from the request payload that
|
|
4761
|
+
* would be sent. Provider-side retries, a repair round, and a language that expands past the
|
|
4762
|
+
* allowance can each push a live run above it; {@link EstimateCaveatCode} names them all. It
|
|
4763
|
+
* carries a currency amount only when the config supplies a `rates` block covering the configured
|
|
4764
|
+
* provider and model; otherwise it reports the quantity and says why the money is missing. See
|
|
4765
|
+
* {@link RunEstimate}.
|
|
4766
|
+
*/
|
|
4767
|
+
readonly estimate?: boolean;
|
|
2529
4768
|
/**
|
|
2530
4769
|
* Remove keys that no longer exist in the source. Defaults to the config's `prune`, then to
|
|
2531
4770
|
* false, so orphaned keys are reported but kept unless removal is asked for explicitly.
|
|
@@ -2547,8 +4786,9 @@ interface TranslateInput {
|
|
|
2547
4786
|
readonly lockAcquireTimeoutMs?: number;
|
|
2548
4787
|
/**
|
|
2549
4788
|
* How many locales to run at once. Must be an integer of at least 1; defaults to 1. On a live
|
|
2550
|
-
* run it cannot be combined with a configured token budget
|
|
2551
|
-
*
|
|
4789
|
+
* run it cannot be combined with a configured token budget: the ceiling would still hold, but
|
|
4790
|
+
* which locale loses its remaining work would depend on the order the locales interleave, so the
|
|
4791
|
+
* run would not be reproducible.
|
|
2552
4792
|
*/
|
|
2553
4793
|
readonly concurrency?: number;
|
|
2554
4794
|
/**
|
|
@@ -2566,6 +4806,29 @@ interface TranslateDeps {
|
|
|
2566
4806
|
/** File-system port. Defaults to the real file system. */
|
|
2567
4807
|
readonly fs?: SdkFs;
|
|
2568
4808
|
}
|
|
4809
|
+
/**
|
|
4810
|
+
* Whether a set of run options resolves to a dry run. `estimate` implies `dryRun`, and this is the
|
|
4811
|
+
* one place that implication is decided: {@link translate} calls it, and so should any caller that
|
|
4812
|
+
* has to know before the run starts whether anything will be written or spent, rather than
|
|
4813
|
+
* re-deriving the rule and drifting from it.
|
|
4814
|
+
*
|
|
4815
|
+
* @param input - The `dryRun` and `estimate` options as the caller received them.
|
|
4816
|
+
* @returns True when the run will construct no provider, write no file, and spend nothing.
|
|
4817
|
+
*
|
|
4818
|
+
* @example
|
|
4819
|
+
* ```ts
|
|
4820
|
+
* import { resolveDryRun, translate } from "@verbatra/sdk";
|
|
4821
|
+
*
|
|
4822
|
+
* if (!resolveDryRun(options)) {
|
|
4823
|
+
* await prepareWorkspaceForWrites();
|
|
4824
|
+
* }
|
|
4825
|
+
* const summary = await translate({ config, ...options });
|
|
4826
|
+
* ```
|
|
4827
|
+
*/
|
|
4828
|
+
declare function resolveDryRun(input: {
|
|
4829
|
+
readonly dryRun?: boolean | undefined;
|
|
4830
|
+
readonly estimate?: boolean | undefined;
|
|
4831
|
+
}): boolean;
|
|
2569
4832
|
/**
|
|
2570
4833
|
* Runs the one-shot translation flow over every configured target locale, or over the subset named
|
|
2571
4834
|
* by `locales`: read the source, diff
|
|
@@ -2640,13 +4903,6 @@ interface TranslateDeps {
|
|
|
2640
4903
|
*/
|
|
2641
4904
|
declare function translate(input: TranslateInput, deps?: TranslateDeps): Promise<RunSummary>;
|
|
2642
4905
|
|
|
2643
|
-
/**
|
|
2644
|
-
* The two delimiter-separated interchange formats. They are one implementation with one parameter, the
|
|
2645
|
-
* field delimiter: `tsv` is not a second format, it is `csv` with a tab. The member is also the file
|
|
2646
|
-
* extension an export writes and an import reads back.
|
|
2647
|
-
*/
|
|
2648
|
-
type DelimitedFormat = "csv" | "tsv";
|
|
2649
|
-
|
|
2650
4906
|
/**
|
|
2651
4907
|
* The file shape of a translator handoff: a single styled `.xlsx` workbook with one sheet per
|
|
2652
4908
|
* locale, or one plain `.csv` or `.tsv` file per locale.
|
|
@@ -2862,7 +5118,7 @@ interface LocalePathResolverConfig {
|
|
|
2862
5118
|
* shared-catalogue format (one holding every locale in a single file, such as `apple-xcstrings`),
|
|
2863
5119
|
* which maps every locale to the same path.
|
|
2864
5120
|
*/
|
|
2865
|
-
readonly format:
|
|
5121
|
+
readonly format: FormatId$1;
|
|
2866
5122
|
/** The file layout. */
|
|
2867
5123
|
readonly files: {
|
|
2868
5124
|
/** The path pattern, which must contain the `{locale}` token. */
|
|
@@ -3042,7 +5298,7 @@ declare const scaffoldingMetadata: {
|
|
|
3042
5298
|
readonly gemini: "maxOutputTokens";
|
|
3043
5299
|
};
|
|
3044
5300
|
/** Every i18n file format the SDK can read and write. */
|
|
3045
|
-
readonly supportedFormats: readonly ["i18next-json", "vue-i18n-json", "next-intl-json", "ngx-translate-json", "xliff", "yaml", "arb", "properties", "apple-strings", "apple-xcstrings", "android-xml", "gettext-po"];
|
|
5301
|
+
readonly supportedFormats: readonly ["i18next-json", "vue-i18n-json", "next-intl-json", "ngx-translate-json", "xliff", "yaml", "arb", "properties", "apple-strings", "apple-xcstrings", "android-xml", "gettext-po", "ini", "resx"];
|
|
3046
5302
|
};
|
|
3047
5303
|
|
|
3048
5304
|
/**
|
|
@@ -3187,4 +5443,4 @@ interface WatchController {
|
|
|
3187
5443
|
*/
|
|
3188
5444
|
declare function watch(input: WatchInput, deps?: WatchDeps): Promise<WatchController>;
|
|
3189
5445
|
|
|
3190
|
-
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 KeyValuePair, 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 LocaleValues, type LocaleValuesDeps, type LocaleValuesInput, 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, localeValues, lockState, readGlossaryFile, readLocaleFileSnapshot, redact, retranslateEntry, runStatus, scaffoldingMetadata, translate, updateGlossaryTerm, verbatraConfigSchema, watch };
|
|
5446
|
+
export { AdapterError, type AdapterErrorCode, type AdapterFs, AdapterRegistry, type AdapterResolution, type AddedKey, type BillingUnit, type BoundedReadOutcome, type BudgetBehavior, type BudgetStanding, type BuildWriteTree, CACHE_FILE_NAME, type CharacterRate, type CharacterRunQuantity, type CheckDeps, type CheckInput, type CheckSummary, type ComparePlaceholders, type ComputeInvalidIcuKeys, type ConfigSource, type CreateProvider, type CreateWatcher, type CustomFormatId$1 as CustomFormatId, DEFAULT_DELIMITED_PATH, DEFAULT_EXCHANGE_FORMAT, DEFAULT_TMX_PATH, DEFAULT_TYPES_PATH, DEFAULT_WORKBOOK_PATH, type DeriveDescriptions, type DeriveEntry, type DiffDeps, type DiffInput, type DiffSummary, type DirectoryEntry, type DoctorCheck, type DoctorCheckId, type DoctorCheckStatus, type DoctorDeps, type DoctorInput, type DoctorResult, type DuplicateKeyReport, EXCHANGE_FORMATS, type EditEntryDeps, type EditEntryInput, type EditEntryResult, type EstimateCaveatCode, type EstimateIdentity, type EstimatePricing, type ExchangeFormat, type ExportTmxDeps, type ExportTmxInput, type ExportTmxLocaleCount, type ExportTmxResult, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type ExtractDeps, type ExtractInput, type ExtractPlaceholders, type ExtractResult, type ExtractionConfig, type FlatFileAdapterOptions, type FlatParseOutcome, type FlatParseResult, type FormatAdapter, type FormatId$1 as FormatId, type FuzzyCacheHit, type GenerateTypesDeps, type GenerateTypesInput, type GenerateTypesResult, type GlossaryFileDeps, type GlossaryFileInput, type GlossaryProvenance, INTEGRITY_GATE_REASONS, type ImportTmxDeps, type ImportTmxInput, type ImportTmxLocaleResult, type ImportTmxResult, type ImportWorkbookDeps, type ImportWorkbookInput, type InconsistencyGroup, type InconsistentTranslation, type IntegrityGateReason, type JsonLeaf, type JsonRecord, type JsonTree, type KeyConflict, type KeyIntegrityDeps, type KeyIntegrityEntry, type KeyIntegrityInput, type KeyMode, type KeyValueDeps, type KeyValueInput, type KeyValuePair, type KeyValueResult, LOCK_FILE_NAME, type LiteralFinding, type LiteralScan, type LiteralSuppressionReason, type LoadConfigOptions, type LoadLockFileDeps, type LoadLockFileInput, type LoadedConfig, type LocaleCheckSummary, type LocaleDiff, type LocaleEstimate, type LocaleEstimateQuantity, type LocaleFileSnapshot, type LocaleFinishedEvent, type LocaleKeyIntegrity, type LocaleNotice, type LocalePathResolver, type LocalePathResolverConfig, type LocaleResource$1 as LocaleResource, type LocaleSnapshotDelta, type LocaleStartedEvent, type LocaleStyle, type LocaleSummary, type LocaleValues, type LocaleValuesDeps, type LocaleValuesInput, type LockFile, type LockHolder, type LockLocaleState, type LockStateDeps, type LockStateInput, type LockStateResult, type LockWaitEvent, type LockWaitListener, type MalformedRowReport, type ModelRate, type NeedsReviewEntry, type OrderedRecord, type OrderedValue, type PlaceholderIntegrityResult$1 as PlaceholderIntegrityResult, type PossiblyDynamicKey, type PricedLocaleEstimate, type PricedRunEstimate, type ProgressEvent, type ProgressListener, type ProviderBilling, type ProviderConfig, type ProviderId, type PseudolocalizeDeps, type PseudolocalizeInput, type PseudolocalizeResult, REVIEW_REASON_CODES, type RateCard, type ReadLocaleFileSnapshotDeps, type ReadLocaleFileSnapshotInput, type ReadResult, type ResolveOptions, type RetranslateEntryDeps, type RetranslateEntryInput, type RetranslateEntryResult, type ReviewReasonCode, type RunBudget, type RunEstimate, type RunEstimateQuantity, type RunFinishedEvent, type RunStatusDeps, type RunStatusFile, type RunStatusInput, type RunStatusLocale, type RunStatusResult, type RunSummary, type RunTranslate, type ScaffoldableProviderId, type ScanDiagnostic, type ScanDiagnosticReason, SdkError, type SdkErrorCode, type SdkFs, type SdkNotice, type SdkNoticeCode, type Sniff, type SourceFramework, type SourceLocation, type SubBatchProgressEvent, type SupportedFormat$1 as SupportedFormat, type SuppressedLiteral, type TmxErrorLocation, type TmxLanguageReport, type TmxRejectionCounts, type TmxRejectionReason, type TokenRate, type TokenRunQuantity, type TranslateDeps, type TranslateInput, type TranslationEntry$1 as TranslationEntry, type TranslationMemory, type TreeFileAdapterOptions, type UnpricedLocaleEstimate, type UnpricedRunEstimate, type UnresolvedArgumentReason, type UnresolvedMessage, type UnusedKey, type UnusedKeysNotRun, type UnusedKeysNotRunReason, type UnusedKeysPrefixSite, type UnusedKeysReport, type UnusedKeysScan, type UnusedKeysSite, type UnusedKeysUnreliability, type UnusedKeysUnreliableReason, type UpdateGlossaryTermInput, type UsageSummary, type ValidateMessage, type ValidateTree, type VerbatraConfig, type VerbatraConfigInput, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, budgetStanding, check, createDefaultRegistry, createFlatFileAdapter, createLocalePathResolver, createTreeFileAdapter, defineConfig, diff, diffLocaleSnapshots, doctor, editEntry, exportTmx, exportWorkbook, extract, generateTypes, importTmx, importWorkbook, isCustomFormatId, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, localeValues, lockState, nodeAdapterFs, pseudolocalize, readGlossaryFile, readLocaleFileSnapshot, redact, resolveDryRun, retranslateEntry, runStatus, scaffoldingMetadata, tmxErrorLocation, translate, updateGlossaryTerm, verbatraConfigSchema, watch };
|