@verbatra/sdk 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -115,7 +115,10 @@ interface TranslateRequest {
115
115
  */
116
116
  readonly signal?: AbortSignal;
117
117
  }
118
- /** Token usage, when the provider reports it. Absent for providers without tokens (DeepL). */
118
+ /**
119
+ * Token usage, when the provider reports it. Absent for the providers that do not bill by token
120
+ * (DeepL and Google Cloud Translation).
121
+ */
119
122
  interface Usage {
120
123
  /** Tokens consumed by the request, summed across the initial call and any repair round. */
121
124
  readonly inputTokens: number;
@@ -126,10 +129,10 @@ interface Usage {
126
129
  * Stable codes for a provider's graceful-degradation notices. These are returned DATA on a
127
130
  * successful result, NOT thrown:
128
131
  *
129
- * - `FORMALITY_DOWNGRADED`: a requested formality was not applied (DeepL's free tier does not
130
- * support it).
132
+ * - `FORMALITY_DOWNGRADED`: a requested `formal` or `informal` tone was not applied (DeepL's free
133
+ * tier does not support formality, and Google Cloud Translation Basic has no formality control).
131
134
  * - `GLOSSARY_IGNORED`: a supplied generic glossary term map was not applied (DeepL only applies a
132
- * native glossary id, never a term map).
135
+ * native glossary id, never a term map, and Google Cloud Translation Basic supports no glossary).
133
136
  * - `PLACEHOLDER_UNSUPPORTED`: at least one placeholder- or ICU-bearing entry was left untranslated
134
137
  * because the provider cannot preserve those tokens; such entries are withheld (absent from the
135
138
  * result maps) rather than sent to the provider and mangled.
@@ -228,8 +231,8 @@ interface TranslateResult {
228
231
  readonly usage?: Usage;
229
232
  /**
230
233
  * Graceful-degradation notices for this batch. Every provider populates this as a present array:
231
- * DeepL reports real notices (for example `GLOSSARY_IGNORED`); an LLM provider with nothing to
232
- * report returns an empty array rather than omitting the field.
234
+ * DeepL and Google Cloud Translation report real notices (for example `GLOSSARY_IGNORED`); an LLM
235
+ * provider with nothing to report returns an empty array rather than omitting the field.
233
236
  */
234
237
  readonly notices?: readonly ProviderNotice[];
235
238
  /**
@@ -349,8 +352,9 @@ declare const supportedFormatSchema$1: z.ZodEnum<{
349
352
  type SupportedFormat$1 = z.infer<typeof supportedFormatSchema$1>;
350
353
  /**
351
354
  * 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`.
355
+ * `custom:` prefix followed by a name of lowercase ASCII letters and digits in hyphen-separated
356
+ * segments, for example `custom:my-format`. The type itself accepts any text after the prefix;
357
+ * {@link isCustomFormatId} and adapter registration enforce the name's shape at runtime.
354
358
  *
355
359
  * No built-in format name contains a colon, so a third-party identifier can never shadow a
356
360
  * built-in one.
@@ -361,7 +365,9 @@ type CustomFormatId$1 = `custom:${string}`;
361
365
  *
362
366
  * @param format - The identifier to classify.
363
367
  * @returns True when the identifier is a well-formed `custom:` identifier. A built-in format
364
- * name always returns false.
368
+ * name always returns false, and so does a `custom:` identifier whose name is empty, holds any
369
+ * character other than a lowercase ASCII letter, a digit, or a hyphen, or has a leading,
370
+ * trailing, or doubled hyphen.
365
371
  *
366
372
  * @example
367
373
  * ```ts
@@ -478,6 +484,10 @@ interface PlaceholderIntegrityResult$1 {
478
484
  readonly reordered: boolean;
479
485
  }
480
486
 
487
+ /**
488
+ * Every translation framework a source scan can target, as a tuple; {@link SourceFramework} is
489
+ * derived from it. It holds `i18next` alone today, the one framework an extractor ships for.
490
+ */
481
491
  declare const SOURCE_FRAMEWORKS: readonly ["i18next"];
482
492
  /**
483
493
  * The translation framework whose call sites a source scan looks for. The set is closed: a
@@ -635,12 +645,26 @@ interface KeyConflict {
635
645
  /** Where each of those values was found, index-aligned with `values`. */
636
646
  readonly locations: readonly SourceLocation[];
637
647
  }
648
+ /**
649
+ * Why a {@link ScanDiagnostic} was reported.
650
+ *
651
+ * - `unreadable`: the file could not be opened, or the path is not a regular file.
652
+ * - `too-large`: the file is larger than 2,000,000 bytes, so it was skipped without being read.
653
+ * - `unparseable`: the file was read, but the scan either failed on it outright, in which case
654
+ * nothing from it is reported, or could not follow it to its end, in which case what was found
655
+ * before that point is still reported.
656
+ * - `unreadable-directory`: a directory under a scan root could not be listed, so nothing beneath
657
+ * it was scanned.
658
+ */
638
659
  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. */
660
+ /**
661
+ * One file or directory the scan could not read, or could not read to its end, reported as data so
662
+ * one bad file never aborts a run.
663
+ */
640
664
  interface ScanDiagnostic {
641
665
  /** The file or directory, relative to the run's working directory, with forward slashes. */
642
666
  readonly file: string;
643
- /** Why it was skipped. */
667
+ /** Why it was skipped or only partly scanned. */
644
668
  readonly reason: ScanDiagnosticReason;
645
669
  }
646
670
 
@@ -736,8 +760,9 @@ declare const supportedFormatSchema: z.ZodEnum<{
736
760
  type SupportedFormat = z.infer<typeof supportedFormatSchema>;
737
761
  /**
738
762
  * 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`.
763
+ * `custom:` prefix followed by a name of lowercase ASCII letters and digits in hyphen-separated
764
+ * segments, for example `custom:my-format`. The type itself accepts any text after the prefix;
765
+ * {@link isCustomFormatId} and adapter registration enforce the name's shape at runtime.
741
766
  *
742
767
  * No built-in format name contains a colon, so a third-party identifier can never shadow a
743
768
  * built-in one.
@@ -935,16 +960,22 @@ interface FormatAdapter {
935
960
  }
936
961
 
937
962
  /**
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.
963
+ * The outcome of a size-bounded read through the {@link AdapterFs} port. A path that exists but is
964
+ * not a regular file, or a file larger than the caller's limit, is reported as a state rather than
965
+ * thrown, so an adapter can treat "not a file" and "too large to trust" as ordinary branches. A
966
+ * missing path is not one of these states: the port rejects instead (see
967
+ * {@link AdapterFs.readBounded}).
941
968
  */
942
969
  type BoundedReadOutcome = {
970
+ /** The file was read in full. */
943
971
  readonly kind: "ok";
972
+ /** The file's content, decoded as UTF-8. */
944
973
  readonly content: string;
945
974
  } | {
975
+ /** The path exists but is not a regular file, a directory for example, so nothing was read. */
946
976
  readonly kind: "not-a-file";
947
977
  } | {
978
+ /** The file is larger than the requested byte limit, so nothing was read. */
948
979
  readonly kind: "too-large";
949
980
  };
950
981
  /**
@@ -963,6 +994,10 @@ interface AdapterFs {
963
994
  * @param path - The file to read.
964
995
  * @param maxBytes - The largest file size to read; a larger file yields `too-large` and is not read.
965
996
  * @returns The file's content, or the state that prevented reading it.
997
+ * @throws An `Error` whose `code` is `"ENOENT"` when nothing exists at `path`, as `node:fs` does.
998
+ * Several adapters re-read an existing destination before writing so they can preserve its
999
+ * layout, and they rely on exactly that code to mean "no file yet, write a fresh one"; a port
1000
+ * that reports a missing file any other way fails those writes.
966
1001
  */
967
1002
  readBounded(path: string, maxBytes: number): Promise<BoundedReadOutcome>;
968
1003
  /**
@@ -1050,8 +1085,10 @@ declare class AdapterRegistry {
1050
1085
  * can silently shadow what is already there.
1051
1086
  *
1052
1087
  * 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.
1088
+ * throw from any of its contract methods surfaces as an {@link AdapterError} with code
1089
+ * `ADAPTER_FAILED` naming the format. An `AdapterError` the adapter raises itself, and a Node.js
1090
+ * system error carrying an errno-style code such as `ENOENT`, pass through unchanged. Built-in
1091
+ * adapters are registered as they are.
1055
1092
  *
1056
1093
  * @param adapter - The adapter to add.
1057
1094
  * @returns This registry, for chaining.
@@ -1208,7 +1245,10 @@ type FlatParseOutcome = Map<string, TranslationEntry> | FlatParseResult;
1208
1245
  interface FlatFileAdapterOptions {
1209
1246
  /** The format this adapter claims: a built-in name, or a `custom:` identifier of your own. */
1210
1247
  readonly format: FormatId;
1211
- /** The lowercase file extensions this format owns, each including the leading dot. */
1248
+ /**
1249
+ * The file extensions this format owns, each including the leading dot. Matched against a path's
1250
+ * extension case-insensitively.
1251
+ */
1212
1252
  readonly extensions: readonly string[];
1213
1253
  /**
1214
1254
  * Optional content check, consulted when a sample is available, so this adapter does not claim
@@ -1246,6 +1286,10 @@ interface FlatFileAdapterOptions {
1246
1286
  * supplies the bounded read, the atomic write, extension detection and the structured error
1247
1287
  * handling; you supply only the format's own parsing and serialization.
1248
1288
  *
1289
+ * The returned adapter's `read` refuses a file larger than 16 MiB with `INPUT_TOO_LARGE` and a path
1290
+ * that is not a regular file with `INVALID_STRUCTURE`, strips a leading byte order mark before
1291
+ * parsing, and uses the file's base name without its extension as the namespace.
1292
+ *
1249
1293
  * @param options - The format's parsing, serialization and detection behaviour.
1250
1294
  * @returns A complete adapter, ready to register on an `AdapterRegistry`.
1251
1295
  * @throws Nothing itself. The returned adapter's `read` and `write` raise `AdapterError` for content
@@ -1292,14 +1336,21 @@ type DeriveDescriptions = (content: string) => ReadonlyMap<string, string>;
1292
1336
  interface TreeFileAdapterOptions {
1293
1337
  /** The format this adapter claims: a built-in name, or a `custom:` identifier of your own. */
1294
1338
  readonly format: FormatId;
1295
- /** The lowercase file extensions this format owns, each including the leading dot. */
1339
+ /**
1340
+ * The file extensions this format owns, each including the leading dot. Matched against a path's
1341
+ * extension case-insensitively.
1342
+ */
1296
1343
  readonly extensions: readonly string[];
1297
1344
  /**
1298
1345
  * Optional content check, consulted when a sample is available, so this adapter does not claim
1299
1346
  * every file that merely shares one of its extensions.
1300
1347
  */
1301
1348
  readonly sniff?: Sniff;
1302
- /** Parse one file's text into a nested tree, preserving key order at every level. */
1349
+ /**
1350
+ * Parse one file's text into a nested tree, preserving key order at every level. Throw an
1351
+ * `AdapterError` for content this format cannot represent; anything else thrown is reported as a
1352
+ * structural failure.
1353
+ */
1303
1354
  readonly parse: (content: string) => JsonRecord;
1304
1355
  /** Render a nested tree back to the format's text, preserving key order at every level. */
1305
1356
  readonly serialize: (tree: OrderedRecord) => string;
@@ -1330,6 +1381,10 @@ interface TreeFileAdapterOptions {
1330
1381
  * write, tree flattening and unflattening, extension detection and the structured error handling;
1331
1382
  * you supply only the format's own parsing, serialization and per-leaf facts.
1332
1383
  *
1384
+ * The returned adapter's `read` refuses a file larger than 16 MiB with `INPUT_TOO_LARGE` and a path
1385
+ * that is not a regular file with `INVALID_STRUCTURE`, strips a leading byte order mark before
1386
+ * parsing, and uses the file's base name without its extension as the namespace.
1387
+ *
1333
1388
  * @param options - The format's parsing, serialization, detection and per-entry behaviour.
1334
1389
  * @returns A complete adapter, ready to register on an `AdapterRegistry`.
1335
1390
  * @throws Nothing itself. The returned adapter's `read` and `write` raise `AdapterError` for content
@@ -1398,11 +1453,14 @@ interface DirectoryEntry {
1398
1453
  * `deps.fs` option redirects that I/O, which is how the SDK's tests avoid touching disk and how an
1399
1454
  * embedding application can back part of a project with something other than a local disk.
1400
1455
  *
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.
1456
+ * The seam carries every file the SDK touches once a config is loaded: the run-status file, the
1457
+ * lock-file and the write locks, the translation-memory cache, the config glossary, workbook and TMX
1458
+ * I/O, generated declaration and pseudolocale files, the source scan, and the locale files
1459
+ * themselves, which the adapters read and write through a port built from this one. Two exceptions
1460
+ * remain. {@link loadConfig} finds and loads the config file itself directly from disk; only its
1461
+ * glossary goes through the port. And a caller-supplied `deps.adapterRegistry` holds adapters the
1462
+ * caller constructed, so their file access is whatever the caller wired into them, and supplying
1463
+ * both means the caller owns that wiring.
1406
1464
  *
1407
1465
  * Reads are size-bounded by contract so that a hostile or accidentally huge file cannot exhaust
1408
1466
  * memory. Writes are expected to be atomic: the default implementation writes to a temporary file
@@ -1411,7 +1469,7 @@ interface DirectoryEntry {
1411
1469
  * directory tree must also implement `mkdir`.
1412
1470
  */
1413
1471
  interface SdkFs {
1414
- /** Reports whether a readable file exists at the path. */
1472
+ /** Reports whether a file exists at the path. The default implementation checks existence only. */
1415
1473
  fileExists(path: string): Promise<boolean>;
1416
1474
  /** Reads a file as UTF-8 text, refusing to read more than `maxBytes`. */
1417
1475
  readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
@@ -1531,8 +1589,9 @@ declare const providerConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1531
1589
  * No variant has a field for an API key. Keys are read from the environment by the provider itself
1532
1590
  * (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `DEEPL_API_KEY`, or
1533
1591
  * `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.
1592
+ * control. The `openai-compatible` variant reads `OPENAI_COMPATIBLE_API_KEY` when it is set and
1593
+ * sends no real key otherwise, since a local server usually needs none; naming a different variable
1594
+ * through `apiKeyEnvVar` makes that variable required. It still never holds the key itself.
1536
1595
  */
1537
1596
  type ProviderConfig = z.infer<typeof providerConfigSchema>;
1538
1597
  /**
@@ -1553,6 +1612,11 @@ type ProviderId = ProviderConfig["id"];
1553
1612
  * optional `$schema` key, accepted so a JSON or YAML config can point an editor at the JSON Schema
1554
1613
  * document the package ships as `@verbatra/sdk/config-schema.json`.
1555
1614
  *
1615
+ * `format` accepts a built-in format name or a `custom:` identifier such as `custom:toml`. A
1616
+ * `custom:` identifier is checked for shape only; the adapter behind it is supplied through a
1617
+ * flow's `adapterRegistry` dependency, and a run whose registry holds none fails with
1618
+ * `UNKNOWN_FORMAT`.
1619
+ *
1556
1620
  * `fuzzyCache` is off unless `enabled` is set. With it on, a source string whose earlier form is in
1557
1621
  * the translation memory close enough to clear `threshold` reuses that translation instead of
1558
1622
  * paying the provider for it. `threshold` is a similarity ratio from `0.5` to `1` and defaults to `0.9`.
@@ -2005,7 +2069,9 @@ interface LoadConfigOptions {
2005
2069
  type ConfigSource = {
2006
2070
  /**
2007
2071
  * `search` means cosmiconfig found the file by walking up from the working directory, up to
2008
- * the nearest `.git` directory; `explicit` means the caller named it through `configPath`.
2072
+ * the nearest `.git` directory or, failing that, the home directory or the working directory
2073
+ * itself (see {@link loadConfigWithMeta}); `explicit` means the caller named it through
2074
+ * `configPath`.
2009
2075
  */
2010
2076
  readonly kind: "search" | "explicit";
2011
2077
  /** The absolute path of the config file that was loaded. */
@@ -2029,8 +2095,8 @@ interface LoadedConfig {
2029
2095
  * config file is in effect, or to distinguish an inline glossary from a glossary file.
2030
2096
  *
2031
2097
  * Resolution order is: an explicit `configOverride`, then an explicit `configPath`, then a
2032
- * cosmiconfig search upward from `cwd` across `verbatra.config.ts`, the `.verbatrarc` family, and a
2033
- * `verbatra` property in `package.json`. The upward search stops at the nearest ancestor directory
2098
+ * cosmiconfig search upward from `cwd` across `verbatra.config.ts`, `.js` and `.cjs`, the
2099
+ * `.verbatrarc` family, and a `verbatra` property in `package.json`. The upward search stops at the nearest ancestor directory
2034
2100
  * containing a `.git` entry; if none is found, it stops at the user's home directory when that is an
2035
2101
  * ancestor of `cwd`, and otherwise it does not search above `cwd` at all, so a nested workspace
2036
2102
  * package finds a config at its monorepo root without wandering above it, and a project outside the
@@ -2107,19 +2173,36 @@ interface ProviderBilling {
2107
2173
  readonly billedByApi: boolean;
2108
2174
  }
2109
2175
 
2176
+ /**
2177
+ * The zod schema for one token-billed rate entry: an input and an output price per million
2178
+ * tokens, each at most `MAX_RATE` currency units.
2179
+ */
2110
2180
  declare const tokenRateSchema: z.ZodObject<{
2111
2181
  inputPerMillionTokens: z.ZodNumber;
2112
2182
  outputPerMillionTokens: z.ZodNumber;
2113
2183
  }, z.core.$strict>;
2184
+ /**
2185
+ * The zod schema for one character-billed rate entry (DeepL, Google Cloud Translation): a
2186
+ * price per million source characters.
2187
+ */
2114
2188
  declare const characterRateSchema: z.ZodObject<{
2115
2189
  perMillionCharacters: z.ZodNumber;
2116
2190
  }, z.core.$strict>;
2191
+ /**
2192
+ * The zod schema for one `rates.table` entry: either {@link tokenRateSchema} or
2193
+ * {@link characterRateSchema}, never a mix of the two shapes.
2194
+ */
2117
2195
  declare const modelRateSchema: z.ZodUnion<readonly [z.ZodObject<{
2118
2196
  inputPerMillionTokens: z.ZodNumber;
2119
2197
  outputPerMillionTokens: z.ZodNumber;
2120
2198
  }, z.core.$strict>, z.ZodObject<{
2121
2199
  perMillionCharacters: z.ZodNumber;
2122
2200
  }, z.core.$strict>]>;
2201
+ /**
2202
+ * The zod schema for the optional `rates` block: the `asOf` date and `currency` printed beside
2203
+ * every priced estimate, plus a `table` of {@link modelRateSchema} entries keyed by rate key
2204
+ * (`provider/model`, or the bare provider id for a provider without a model).
2205
+ */
2123
2206
  declare const rateCardSchema: z.ZodObject<{
2124
2207
  asOf: z.ZodISODate;
2125
2208
  currency: z.ZodString;
@@ -2167,17 +2250,20 @@ type RateCard = z.infer<typeof rateCardSchema>;
2167
2250
  * file could not be resolved or parsed. Thrown by {@link loadConfig} and
2168
2251
  * {@link loadConfigWithMeta}, by {@link readGlossaryFile}, and by {@link updateGlossaryTerm},
2169
2252
  * which additionally throws it for a blank term or translation and for an edit whose result would
2170
- * exceed the glossary file size limit. {@link importWorkbook} does not throw it: when a handoff
2171
- * sheet or file names a locale that is not a configured target locale, it records this code on
2172
- * that locale's {@link LocaleSummary} instead.
2253
+ * exceed the glossary file size limit. {@link importTmx} and {@link exportTmx} throw it when the
2254
+ * source locale and a target locale are the same language tag once case and separators are
2255
+ * normalized, since a TMX segment could not be attributed to either. {@link importWorkbook} does
2256
+ * not throw it: when a handoff sheet or file names a locale that is not a configured target
2257
+ * locale, it records this code on that locale's {@link LocaleSummary} instead.
2173
2258
  * - `UNKNOWN_FORMAT`: no adapter is registered for the configured format. Thrown by every entry
2174
2259
  * point that selects an adapter, before any file is read. {@link doctor} is the exception: it
2175
2260
  * reports an unresolvable format as a failed `format-adapter` check instead, since reporting that
2176
2261
  * is the command's job.
2177
2262
  * - `UNKNOWN_LOCALE`: a requested locale is not among the configured target locales. Thrown through
2178
2263
  * the shared locale selection by {@link translate}, {@link watch}, {@link check}, {@link diff},
2179
- * {@link keyIntegrity}, {@link lockState}, {@link exportWorkbook}, {@link keyValue},
2180
- * {@link editEntry}, and {@link retranslateEntry}. {@link translate} throws it before anything is
2264
+ * {@link keyIntegrity}, {@link lockState}, {@link localeValues}, {@link exportWorkbook},
2265
+ * {@link exportTmx}, {@link importTmx}, {@link keyValue}, {@link editEntry}, and
2266
+ * {@link retranslateEntry}. {@link translate} throws it before anything is
2181
2267
  * read or spent, and {@link watch} once at startup, before any watching begins.
2182
2268
  * - `UNKNOWN_KEY`: the requested key is not present in the source resource. Thrown by
2183
2269
  * {@link keyValue}, {@link editEntry}, and {@link retranslateEntry}.
@@ -2186,9 +2272,12 @@ type RateCard = z.infer<typeof rateCardSchema>;
2186
2272
  * {@link translate} and by {@link retranslateEntry}.
2187
2273
  * - `SOURCE_UNREADABLE`: the source locale file is absent. Thrown by every entry point that reads
2188
2274
  * the source, including {@link importWorkbook}, and by {@link watch} at startup.
2189
- * {@link importWorkbook} additionally throws it when the handoff file itself is missing.
2275
+ * {@link importWorkbook} and {@link importTmx} additionally throw it when the handoff or TMX file
2276
+ * itself is missing.
2190
2277
  * - `SOURCE_INVALID`: the source locale file, or an interchange file, could not be parsed. Wraps
2191
- * the adapter or reader error.
2278
+ * the adapter or reader error. {@link pseudolocalize} also throws it when, for a format whose
2279
+ * writer only patches an existing document, the source file could not be copied to seed the
2280
+ * output.
2192
2281
  * - `LOCK_FILE_INVALID`: the lock-file exists but is corrupt, oversized, or at an unsupported
2193
2282
  * version. Thrown wherever the lock-file is read or updated: {@link translate}, {@link check},
2194
2283
  * {@link diff}, {@link keyIntegrity}, {@link lockState}, {@link loadLockFile},
@@ -2226,7 +2315,7 @@ type RateCard = z.infer<typeof rateCardSchema>;
2226
2315
  * writable, does not exist, is read-only, or is out of space. The message names the target file
2227
2316
  * relative to `cwd` and the underlying file-system code, never the internal temporary file the
2228
2317
  * atomic write uses. Thrown by {@link editEntry} and {@link retranslateEntry}, which act on one
2229
- * locale. {@link translate} and {@link importWorkbook} do not throw it: they record it on that
2318
+ * locale, and by {@link pseudolocalize} for the pseudolocale file. {@link translate} and {@link importWorkbook} do not throw it: they record it on that
2230
2319
  * locale's {@link LocaleSummary} and carry on with the other locales.
2231
2320
  * - `PSEUDO_OUTPUT_CONFLICT`: {@link pseudolocalize} was asked to generate a pseudolocale that
2232
2321
  * names a configured locale, or to write one onto a configured locale file. Refused before
@@ -2257,7 +2346,8 @@ type RateCard = z.infer<typeof rateCardSchema>;
2257
2346
  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
2347
  /**
2259
2348
  * 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
2349
+ * carrying a stable {@link SdkErrorCode}, except the few a flow's `@throws` names as passed through
2350
+ * unwrapped (a failed workbook write, a watcher factory that throws); per-locale failures, provider notices, and integrity
2261
2351
  * findings are reported as data on the {@link RunSummary} instead of being thrown.
2262
2352
  *
2263
2353
  * An `SdkError` never carries a secret in its message.
@@ -2282,17 +2372,19 @@ declare class SdkError extends Error {
2282
2372
  * provider raises. Each marks a run that completed but did something a caller may want to know
2283
2373
  * about.
2284
2374
  *
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.
2375
+ * - `PLURAL_CATEGORIES_INCOMPLETE`: `i18next-json` only. The target language needs CLDR plural
2376
+ * categories the source does not supply and that plural generation did not produce (it is off,
2377
+ * the provider is not an LLM, or a generated form was withheld), so the missing forms have to be
2378
+ * added by hand.
2379
+ * - `SUB_BATCH_FAILED`: one provider sub-batch of a locale failed. Its keys are withheld and retried
2380
+ * on the next run, while the results of the other sub-batches are kept.
2289
2381
  * - `BLANK_ROW_BASELINE_RETAINED`: an imported handoff row was blank, so the existing translation
2290
2382
  * and its lock-file baseline were kept rather than being erased.
2291
2383
  * - `BUDGET_TOKENS_EXCEEDED`: the configured token budget was reached. Under `warn` the run
2292
2384
  * continues and the overrun is reported; under `stop` the request that would have crossed the
2293
2385
  * ceiling is withheld before it is sent, and so is every request after it.
2294
2386
  * - `CACHE_VERSION_UNRECOGNIZED`: the translation memory is at a version this release does not
2295
- * understand, so it was ignored rather than trusted.
2387
+ * understand, so the run used no cache and left the file untouched.
2296
2388
  */
2297
2389
  type SdkNoticeCode = "PLURAL_CATEGORIES_INCOMPLETE" | "SUB_BATCH_FAILED" | "BLANK_ROW_BASELINE_RETAINED" | "BUDGET_TOKENS_EXCEEDED" | "CACHE_VERSION_UNRECOGNIZED";
2298
2390
  /**
@@ -2344,7 +2436,8 @@ interface RunBudget {
2344
2436
  * Whether {@link tokensUsed} is entirely the provider's own reported usage. False as soon as one
2345
2437
  * counted request came back without a usable figure, so the total is partly verbatra's own
2346
2438
  * 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.
2439
+ * reports none either. Also false when the run sent no request at all, a dry run included, in
2440
+ * which case nothing was counted. The budget is enforced either way.
2348
2441
  */
2349
2442
  readonly supported: boolean;
2350
2443
  /**
@@ -2372,6 +2465,7 @@ type EstimatePricing = "priced" | "no-rate-on-file" | "rate-unit-mismatch" | "no
2372
2465
  /**
2373
2466
  * What a pre-run estimate deliberately leaves out. Each code names a way the real run can land
2374
2467
  * below the figure, above it, or away from it, so an estimate is never mistaken for an invoice.
2468
+ * The first three apply to every estimate; the last three only to a token-billed provider.
2375
2469
  *
2376
2470
  * - `CACHE_NOT_CONSULTED`: the estimate does not read the translation memory, so keys a live run
2377
2471
  * would serve from cache are still counted.
@@ -2478,6 +2572,7 @@ interface CharacterRunQuantity extends EstimateIdentity {
2478
2572
  * and the other one is never readable as a zero.
2479
2573
  */
2480
2574
  type RunEstimateQuantity = TokenRunQuantity | CharacterRunQuantity;
2575
+ /** The money half of a {@link PricedRunEstimate}: the figure, its currency, and its date. */
2481
2576
  interface PricedEstimateMoney {
2482
2577
  /** Always `priced`: a rate was found and applied. */
2483
2578
  readonly pricing: "priced";
@@ -2493,6 +2588,7 @@ interface PricedEstimateMoney {
2493
2588
  /** The estimated total, in {@link currency}. */
2494
2589
  readonly cost: number;
2495
2590
  }
2591
+ /** The money half of an {@link UnpricedRunEstimate}: why there is no figure, and no figure. */
2496
2592
  interface UnpricedEstimateMoney {
2497
2593
  /** Why no currency figure is present. See {@link EstimatePricing}. */
2498
2594
  readonly pricing: Exclude<EstimatePricing, "priced">;
@@ -2505,12 +2601,16 @@ interface UnpricedEstimateMoney {
2505
2601
  /** Never present: an absent rate is reported as absent, never as a cost of zero. */
2506
2602
  readonly cost?: undefined;
2507
2603
  }
2604
+ /** A priced estimate for a token-billed provider: prompt and completion tokens, and their cost. */
2508
2605
  interface TokenPricedRunEstimate extends TokenRunQuantity, PricedEstimateMoney {
2509
2606
  }
2607
+ /** A priced estimate for a character-billed provider: source characters, and their cost. */
2510
2608
  interface CharacterPricedRunEstimate extends CharacterRunQuantity, PricedEstimateMoney {
2511
2609
  }
2610
+ /** An unpriced estimate for a token-billed provider: prompt and completion tokens, no cost. */
2512
2611
  interface TokenUnpricedRunEstimate extends TokenRunQuantity, UnpricedEstimateMoney {
2513
2612
  }
2613
+ /** An unpriced estimate for a character-billed provider: source characters, no cost. */
2514
2614
  interface CharacterUnpricedRunEstimate extends CharacterRunQuantity, UnpricedEstimateMoney {
2515
2615
  }
2516
2616
  /**
@@ -2562,8 +2662,9 @@ interface SdkNotice {
2562
2662
  */
2563
2663
  type LocaleNotice = ProviderNotice | SdkNotice;
2564
2664
  /**
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.
2665
+ * A key written in this run that was flagged as worth a human look, whether by the provider layer's
2666
+ * quality heuristics, a configured `maxLength` budget, or a fuzzy translation-memory reuse. The
2667
+ * value was still written; these are advisory quality signals, not rejections.
2567
2668
  */
2568
2669
  interface NeedsReviewEntry {
2569
2670
  /** The key that was flagged. */
@@ -2591,8 +2692,9 @@ interface FuzzyCacheHit {
2591
2692
  /** The key's source text at the time this translation was produced. */
2592
2693
  readonly previousSource: string;
2593
2694
  /**
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.
2695
+ * How alike {@link previousSource} and the current source are, from `0` to `1`: one minus the
2696
+ * character edit distance divided by the longer string's length, after Unicode normalization.
2697
+ * Always at or above the configured `fuzzyCache.threshold`.
2596
2698
  */
2597
2699
  readonly similarity: number;
2598
2700
  }
@@ -2632,11 +2734,18 @@ interface LocaleSummary {
2632
2734
  /** The target locale this summary describes. */
2633
2735
  readonly locale: string;
2634
2736
  /**
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.
2737
+ * `succeeded` when no key was withheld by the integrity gate, a provider failure, or the token
2738
+ * budget; `partial` when some keys were withheld and others landed; `failed` when keys were
2739
+ * withheld and none landed, or when the locale threw. Keys skipped for invalid ICU source and
2740
+ * handoff rows left blank do not change the status.
2637
2741
  */
2638
2742
  readonly status: "succeeded" | "partial" | "failed";
2639
- /** Keys newly translated by the provider in this run. */
2743
+ /**
2744
+ * Keys that received a new value in this run: from the provider for {@link translate}, keys
2745
+ * sharing a translated key's source text included, or from the handoff for
2746
+ * {@link importWorkbook}. On a dry run, the keys a live run would send; cache hits are not
2747
+ * separated out, because a dry run does not read the translation memory.
2748
+ */
2640
2749
  readonly translated: readonly string[];
2641
2750
  /** Keys already up to date against the lock-file baseline, so no provider call was made. */
2642
2751
  readonly unchanged: readonly string[];
@@ -2683,7 +2792,10 @@ interface LocaleSummary {
2683
2792
  readonly notices: readonly LocaleNotice[];
2684
2793
  /** Translated keys flagged as worth a human look. The translations were still written. */
2685
2794
  readonly needsReview: readonly NeedsReviewEntry[];
2686
- /** Keys left with no translation after the run, whatever the cause. */
2795
+ /**
2796
+ * Keys an imported handoff left blank while they were missing or out of date in this locale.
2797
+ * Always empty for a {@link translate} run.
2798
+ */
2687
2799
  readonly unfilled: readonly string[];
2688
2800
  /** Unreadable rows from an imported handoff. Always empty for a {@link translate} run. */
2689
2801
  readonly malformedRows: readonly MalformedRowReport[];
@@ -2757,6 +2869,9 @@ type BudgetStanding = "within" | "stopped-before-ceiling" | "reached";
2757
2869
  /**
2758
2870
  * Classifies a {@link RunBudget} into its {@link BudgetStanding}, so every surface that reports a
2759
2871
  * budget tells a `stop` run halted short of its ceiling apart from one that actually reached it.
2872
+ *
2873
+ * @param budget - The budget from {@link RunSummary.budget} or {@link RunStatusFile.budget}.
2874
+ * @returns The standing. Pure; throws nothing.
2760
2875
  */
2761
2876
  declare function budgetStanding(budget: RunBudget): BudgetStanding;
2762
2877
 
@@ -3049,8 +3164,8 @@ interface DiffDeps {
3049
3164
  * head could reach as `possiblyDynamic`, never as `unused`. The report is `complete` only when the
3050
3165
  * scan can bound every key the source reaches; otherwise it is `unreliable` and names each reason
3051
3166
  * 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).
3167
+ * trustworthy to judge by (no `extract` block, a file system with no `readDirectory`, a format
3168
+ * whose runtime is not modeled, no source file, or no reference anywhere in the scanned files).
3054
3169
  *
3055
3170
  * Note that a malformed target locale file surfaces the adapter's own error and code rather than a
3056
3171
  * wrapped {@link SdkError}, because only source reads are wrapped. Its message names the offending
@@ -3122,8 +3237,9 @@ interface DoctorResult {
3122
3237
  readonly checks: readonly DoctorCheck[];
3123
3238
  /**
3124
3239
  * 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.
3240
+ * ran; absent when the config could not be loaded, no `extract` block is configured, the file
3241
+ * system cannot list directories, or the scan failed to start, each of which fails the
3242
+ * `untranslated-literals` check instead.
3127
3243
  */
3128
3244
  readonly literals?: LiteralScan;
3129
3245
  }
@@ -3147,8 +3263,8 @@ interface DoctorDeps {
3147
3263
  readonly adapterRegistry?: AdapterRegistry;
3148
3264
  /**
3149
3265
  * File-system port. Threaded into the config loader, so it backs the glossary-file read the
3150
- * `config` check performs, and used to read and parse the source locale file. Defaults to the
3151
- * real file system.
3266
+ * `config` check performs, and used to read and parse the source locale file. On a literal run it
3267
+ * backs the source scan, which needs its `readDirectory` member. Defaults to the real file system.
3152
3268
  */
3153
3269
  readonly fs?: SdkFs;
3154
3270
  /** Config loader. Defaults to {@link loadConfigWithMeta}. */
@@ -3217,8 +3333,10 @@ declare function doctor(input?: DoctorInput, deps?: DoctorDeps): Promise<DoctorR
3217
3333
 
3218
3334
  /**
3219
3335
  * Every reason a candidate translation can be refused before it is written. The same gate guards
3220
- * {@link editEntry} and {@link retranslateEntry}, so a hand-typed value and a provider-produced one
3221
- * are held to identical standards.
3336
+ * every write path: a provider translation, a cache or duplicate-content reuse, a generated plural
3337
+ * form, a pseudolocalized value, a workbook or TMX import, {@link editEntry}, and
3338
+ * {@link retranslateEntry}, so a hand-typed value and a provider-produced one are held to identical
3339
+ * standards.
3222
3340
  *
3223
3341
  * A rejected value is never written to the locale file and never recorded in the lock-file, so the
3224
3342
  * previous translation stays intact.
@@ -3347,8 +3465,8 @@ type EditEntryResult = {
3347
3465
  * a human corrects one string and it is persisted without re-running the whole project.
3348
3466
  *
3349
3467
  * The edit is held to the same integrity gate as a provider translation, so a value that drops a
3350
- * placeholder, breaks ICU syntax, runs away into a hugely oversized or repetition-dominated value,
3351
- * or blanks a non-empty string is refused. A refusal comes back as `accepted: false` rather than as
3468
+ * placeholder, breaks the source's inline markup, breaks ICU syntax, runs away into a hugely
3469
+ * oversized or repetition-dominated value, or blanks a non-empty string is refused. A refusal comes back as `accepted: false` rather than as
3352
3470
  * a thrown error, and nothing is written.
3353
3471
  *
3354
3472
  * The locale's write lock is taken for the whole read-modify-write, so an edit cannot interleave
@@ -3356,7 +3474,8 @@ type EditEntryResult = {
3356
3474
  * refused edit takes and releases the lock as well and can therefore fail with `LOCK_CONTENDED`
3357
3475
  * even though it would have written nothing. An accepted edit then updates the lock-file baseline
3358
3476
  * and feeds the translation memory, which means a later run treats the key as up to date and
3359
- * reuses the edited text rather than paying the provider to translate it again.
3477
+ * reuses the edited text rather than paying the provider to translate it again. No review reason
3478
+ * is computed for a hand-edited value, so a configured `maxLength` budget is not checked here.
3360
3479
  *
3361
3480
  * Note that the target locale file surfaces the adapter's own error and code rather than a wrapped
3362
3481
  * {@link SdkError}, on the write as well as on the read, because only the source read is wrapped.
@@ -3398,9 +3517,9 @@ interface AddedKey {
3398
3517
  readonly key: string;
3399
3518
  /** The value written into the catalog: the call site's default, or the empty string when it had none. */
3400
3519
  readonly value: string;
3401
- /** The source file the key was found in, relative to the run's working directory. */
3520
+ /** The source file of the key's first call site, relative to the run's working directory. */
3402
3521
  readonly file: string;
3403
- /** The one-based line the key was found on. */
3522
+ /** The one-based line of the key's first call site. */
3404
3523
  readonly line: number;
3405
3524
  }
3406
3525
  /**
@@ -3495,7 +3614,16 @@ interface ExtractDeps {
3495
3614
  */
3496
3615
  declare function extract(input: ExtractInput, deps?: ExtractDeps): Promise<ExtractResult>;
3497
3616
 
3498
- /** Why verbatra declined to describe a message's arguments rather than guessing at them. */
3617
+ /**
3618
+ * Why verbatra declined to describe a message's arguments rather than guessing at them.
3619
+ *
3620
+ * - `invalid-message-syntax`: the message does not parse as ICU under a format that reads its
3621
+ * arguments that way, or the adapter reports it as invalid.
3622
+ * - `mixed-argument-styles`: the message combines named arguments with numbered or unnumbered
3623
+ * ones, or numbered with unnumbered ones, so no single call signature fits it.
3624
+ * - `argument-index-out-of-range`: a numbered argument addresses a zero-based position below 0 or
3625
+ * of 64 or more (such as `%0$s` or `{64}`).
3626
+ */
3499
3627
  type UnresolvedArgumentReason = "invalid-message-syntax" | "mixed-argument-styles" | "argument-index-out-of-range";
3500
3628
 
3501
3629
  /**
@@ -3566,7 +3694,10 @@ interface GenerateTypesResult {
3566
3694
  readonly excluded: readonly string[];
3567
3695
  /** Keys the adapter marked as carrying plural forms. Each sibling is declared on its own. */
3568
3696
  readonly plural: readonly string[];
3569
- /** Whether the declaration file was written. Always false in `check` mode. */
3697
+ /**
3698
+ * Whether the declaration file was written. False in `check` mode, and false when the file on
3699
+ * disk already matched, so an unchanged catalog rewrites nothing.
3700
+ */
3570
3701
  readonly written: boolean;
3571
3702
  /** Whether the file on disk differed from the freshly generated declaration when the run started. */
3572
3703
  readonly stale: boolean;
@@ -3594,12 +3725,14 @@ interface GenerateTypesResult {
3594
3725
  * What it will not claim is as important as what it will. A message whose placeholders name their
3595
3726
  * arguments gets an object shape; one whose placeholders are numbered or anonymous gets a readonly
3596
3727
  * 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.
3728
+ * message whose syntax the adapter reported as invalid, one that mixes named, numbered and
3729
+ * anonymous arguments, and one that numbers an argument past the 64th are declared with
3730
+ * {@link GenerateTypesResult.unresolved} recording why, rather than being silently declared as
3731
+ * taking nothing. Argument types come only from what the catalog actually records: `number` for a
3732
+ * numeric annotation, printf conversion or ICU `plural`, `string` for a text conversion or ICU
3733
+ * `select`, `Date | number` for a date or time argument, and a `string | number` alias everywhere
3734
+ * else, never a permissive `any`. A name used with several types is declared as the union of what
3735
+ * each use accepts.
3603
3736
  *
3604
3737
  * With `check` set, nothing is written: the run reports whether the committed file still matches
3605
3738
  * what a fresh generation would produce, which is the shape a CI gate wants.
@@ -3628,7 +3761,7 @@ interface GenerateTypesResult {
3628
3761
  */
3629
3762
  declare function generateTypes(input: GenerateTypesInput, deps?: GenerateTypesDeps): Promise<GenerateTypesResult>;
3630
3763
 
3631
- /** One key's placeholder and ICU verdict in a {@link LocaleKeyIntegrity} report. */
3764
+ /** One key's placeholder, ICU, and inline-markup verdict in a {@link LocaleKeyIntegrity} report. */
3632
3765
  interface KeyIntegrityEntry {
3633
3766
  /** The key this verdict describes. */
3634
3767
  readonly key: string;
@@ -3672,7 +3805,11 @@ interface KeyIntegrityInput {
3672
3805
  readonly cwd?: string;
3673
3806
  /** Restrict the report to these target locales. Defaults to every configured target locale. */
3674
3807
  readonly locales?: readonly string[];
3675
- /** Restrict the report to these keys. Defaults to every key the diff reports as changed. */
3808
+ /**
3809
+ * Restrict the report to these keys. Only keys the diff reports as changed are ever judged, so a
3810
+ * requested key that is missing, orphaned, or up to date is left out rather than reported.
3811
+ * Defaults to every changed key.
3812
+ */
3676
3813
  readonly keys?: readonly string[];
3677
3814
  }
3678
3815
  /** Injectable dependencies for {@link keyIntegrity}. Every field has a working default. */
@@ -3684,15 +3821,17 @@ interface KeyIntegrityDeps {
3684
3821
  }
3685
3822
  /**
3686
3823
  * Reports, per changed key, whether the existing translation still carries the source's
3687
- * placeholders and still parses as valid ICU. It writes nothing and calls no provider.
3824
+ * placeholders and inline markup and still parses as valid ICU. It writes nothing and calls no
3825
+ * provider.
3688
3826
  *
3689
3827
  * The scope is deliberately the changed keys rather than every key: a key whose source text has not
3690
3828
  * moved was already gated when it was written, so re-reporting it would bury the keys that a source
3691
3829
  * edit may have just invalidated. Only keys present in both the source and the target are judged,
3692
3830
  * since a missing translation has no placeholders to compare.
3693
3831
  *
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
3832
+ * This is the read-only counterpart to the placeholder, ICU and inline-markup checks of the
3833
+ * write-time integrity gate that {@link translate}, {@link editEntry}, {@link retranslateEntry},
3834
+ * and the workbook and TMX imports enforce, and the data behind a
3696
3835
  * review dashboard's per-key integrity indicator. Reporting markup here is what makes drift that
3697
3836
  * predates the gate visible at all: a translation written before the check existed, edited outside
3698
3837
  * verbatra, or produced by a path that never crossed the gate is judged here by the same rule.
@@ -4019,7 +4158,8 @@ interface PseudolocalizeInput {
4019
4158
  readonly cwd?: string;
4020
4159
  /**
4021
4160
  * 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.
4161
+ * configured target locale, compared case-insensitively, so a pseudolocale can never stand in for
4162
+ * a real translation.
4023
4163
  */
4024
4164
  readonly locale?: string;
4025
4165
  /**
@@ -4028,9 +4168,9 @@ interface PseudolocalizeInput {
4028
4168
  * `files.pattern` is expanded inside it, so the file keeps the layout the application expects.
4029
4169
  *
4030
4170
  * 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.
4171
+ * `cwd` itself are all refused, as is a directory under which the expanded pattern would place
4172
+ * the pseudolocale file beside a configured locale file, so a generated pseudolocale never lands
4173
+ * outside the project or beside the real translations where nothing ignores it.
4034
4174
  */
4035
4175
  readonly out?: string;
4036
4176
  }
@@ -4076,8 +4216,8 @@ interface PseudolocalizeResult {
4076
4216
  *
4077
4217
  * The output is deliberately kept out of the project's real locale files: it is written under
4078
4218
  * `.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
4219
+ * pseudolocale is refused when it names a configured locale, when its file would land in the same
4220
+ * directory as a configured locale file, and when it is directed at the project root or outside the
4081
4221
  * working directory. Because it lives outside `files.pattern`, {@link translate} never spends on it and
4082
4222
  * {@link check} and {@link diff} never report it as drifted.
4083
4223
  *
@@ -4098,9 +4238,9 @@ interface PseudolocalizeResult {
4098
4238
  * console.log(`${result.transformed} of ${result.entries} entries in ${result.path}`);
4099
4239
  * ```
4100
4240
  *
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`.
4241
+ * @throws {@link SdkError} `PSEUDO_OUTPUT_CONFLICT`: the pseudolocale names a configured locale, its
4242
+ * file would land in the same directory as a configured locale file, or the output directory is not
4243
+ * a relative path naming a directory inside `cwd`.
4104
4244
  * @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
4105
4245
  * @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the `files.pattern` and `files.localeStyle`
4106
4246
  * cannot be combined, or the pseudolocale has no valid path spelling under that style.
@@ -4154,9 +4294,11 @@ type RetranslateEntryResult = {
4154
4294
  /** The newly translated value now stored for the key. */
4155
4295
  readonly value: string;
4156
4296
  /**
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.
4297
+ * Quality signals the provider layer raised for this value, such as a length-ratio outlier,
4298
+ * a value identical to the source, or `MAX_LENGTH_EXCEEDED` for a value over the key's
4299
+ * configured `maxLength` budget. Never `FUZZY_CACHE_REUSE`, since this path always calls the
4300
+ * provider and never consults the translation memory. Empty when nothing was flagged. The
4301
+ * value is written either way; these are advisory.
4160
4302
  */
4161
4303
  readonly reviewReasons: readonly ReviewReasonCode[];
4162
4304
  } | {
@@ -4181,7 +4323,8 @@ type RetranslateEntryResult = {
4181
4323
  * a UI should gate it behind an explicit user action.
4182
4324
  *
4183
4325
  * 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
4326
+ * loses a placeholder, breaks the source's inline markup, breaks ICU syntax, degenerates into
4327
+ * runaway output, or comes back blank is refused and nothing is written. Provider quality
4185
4328
  * signals are surfaced on an accepted result as `reviewReasons` rather than blocking the write.
4186
4329
  *
4187
4330
  * The locale's write lock is taken before the provider is called and held across it, covering the
@@ -4383,20 +4526,22 @@ interface ExportTmxDeps {
4383
4526
  * the same set a run would reuse, so the file describes the memory as it is actually being used
4384
4527
  * rather than every translation the project has ever produced. One `tu` element is written per
4385
4528
  * 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.
4529
+ * An empty memory, including a missing, unparsable or newer-version cache file, produces a valid,
4530
+ * empty TMX file rather than an error. An existing file at the output path is replaced.
4531
+ *
4532
+ * It reads the memory and writes a file; it never changes the memory, which is why the CLI refuses
4533
+ * `--dry-run` and `--overwrite` on this direction rather than accepting and ignoring them.
4387
4534
  *
4388
4535
  * @param input - The config, the output path, the locale subset, and the tool version to stamp.
4389
4536
  * @param deps - Optional file-system override.
4390
4537
  * @returns The path written, the unit count, and what was left out.
4391
4538
  *
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
4539
  * @throws {@link SdkError} `UNKNOWN_LOCALE`: a requested locale is not a configured target locale.
4396
4540
  * @throws {@link SdkError} `CONFIG_INVALID`: the source locale and one of the target locales are the
4397
4541
  * same language tag once case and separators are normalized. Writing that file would produce two
4398
4542
  * language attributes {@link importTmx} could not tell apart, so the file this project exported
4399
4543
  * would be one it refuses to read back.
4544
+ * @throws The underlying file-system error, unwrapped, when the output file could not be written.
4400
4545
  *
4401
4546
  * @example
4402
4547
  * ```ts
@@ -4615,6 +4760,9 @@ interface ImportTmxDeps {
4615
4760
  * @returns What was read, what was stored, and everything that was skipped or refused.
4616
4761
  *
4617
4762
  * @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
4763
+ * @throws {@link SdkError} `CONFIG_INVALID`: the source locale and one of the target locales are the
4764
+ * same language tag once case and separators are normalized, so a segment could not be attributed
4765
+ * to either.
4618
4766
  * @throws {@link SdkError} `UNKNOWN_LOCALE`: a requested locale is not a configured target locale.
4619
4767
  * @throws {@link SdkError} `SOURCE_UNREADABLE`: no file exists at the given path.
4620
4768
  * @throws {@link SdkError} `SOURCE_INVALID`: the file is oversized, malformed, not a TMX document,
@@ -4690,6 +4838,10 @@ interface LocaleStartedEvent {
4690
4838
  * has already stopped the run still emits, and no provider call follows it. That is deliberate, so
4691
4839
  * that `batchIndex` always advances to `totalBatches` and a progress bar reaches its end instead of
4692
4840
  * stalling at the point the budget tripped.
4841
+ *
4842
+ * Only the translation batches emit it. A dry run sends nothing and emits none, and neither do the
4843
+ * plural-generation requests, so `totalBatches` counts translation batches alone. Keys served from
4844
+ * the translation memory are not batched, so a fully cached locale emits none either.
4693
4845
  */
4694
4846
  interface SubBatchProgressEvent {
4695
4847
  /** Discriminant for {@link ProgressEvent}. */
@@ -4707,14 +4859,17 @@ interface LocaleFinishedEvent {
4707
4859
  readonly type: "locale-finished";
4708
4860
  /** The locale that finished. */
4709
4861
  readonly locale: string;
4710
- /** How many keys were newly translated for this locale. */
4862
+ /** The length of the locale's {@link LocaleSummary.translated} list. */
4711
4863
  readonly translated: number;
4712
4864
  /** This locale's 0-based position among the run's locales. */
4713
4865
  readonly localeIndex: number;
4714
4866
  /** How many locales the run covers in total. */
4715
4867
  readonly totalLocales: number;
4716
4868
  }
4717
- /** Emitted once, after every locale has finished. */
4869
+ /**
4870
+ * Emitted once, after every locale has finished. Not emitted when the run rejects part way, as it
4871
+ * does on a corrupt lock-file.
4872
+ */
4718
4873
  interface RunFinishedEvent {
4719
4874
  /** Discriminant for {@link ProgressEvent}. */
4720
4875
  readonly type: "run-finished";
@@ -4731,7 +4886,10 @@ type ProgressListener = (event: ProgressEvent) => void;
4731
4886
  interface TranslateInput {
4732
4887
  /** The resolved project config, normally from {@link loadConfig}. */
4733
4888
  readonly config: VerbatraConfig;
4734
- /** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
4889
+ /**
4890
+ * Directory the `files.pattern` is resolved against, and where the lock-file, the translation
4891
+ * memory, and the run-status file live. Defaults to the process working directory.
4892
+ */
4735
4893
  readonly cwd?: string;
4736
4894
  /**
4737
4895
  * Restrict the run to a subset of the configured target locales, which is how you translate one
@@ -4772,7 +4930,8 @@ interface TranslateInput {
4772
4930
  readonly prune?: boolean;
4773
4931
  /**
4774
4932
  * Generate the plural categories a target language requires rather than translating each
4775
- * category separately. Defaults to the config's `generatePlurals`, then to false.
4933
+ * category separately. Defaults to the config's `generatePlurals`, then to false. Takes effect
4934
+ * only with an LLM provider; a machine-translation provider generates nothing.
4776
4935
  */
4777
4936
  readonly generatePlurals?: boolean;
4778
4937
  /**
@@ -4782,7 +4941,10 @@ interface TranslateInput {
4782
4941
  readonly onLockWait?: LockWaitListener;
4783
4942
  /** Called as locales and sub-batches start and finish, for progress reporting. */
4784
4943
  readonly onProgress?: ProgressListener;
4785
- /** How long to wait for a locale's write lock before failing with `LOCK_CONTENDED`. */
4944
+ /**
4945
+ * How long, in milliseconds, to wait for a locale's write lock before that locale fails with
4946
+ * `LOCK_CONTENDED`. Defaults to ten minutes. Not used on a dry run, which takes no lock.
4947
+ */
4786
4948
  readonly lockAcquireTimeoutMs?: number;
4787
4949
  /**
4788
4950
  * How many locales to run at once. Must be an integer of at least 1; defaults to 1. On a live
@@ -4792,8 +4954,9 @@ interface TranslateInput {
4792
4954
  */
4793
4955
  readonly concurrency?: number;
4794
4956
  /**
4795
- * Consult and update the translation memory. Defaults to true. Turning it off forces every key
4796
- * through the provider, which is what to do when you want to re-pay for a fresh translation.
4957
+ * Consult and update the translation memory, fuzzy reuse included. Defaults to true. Turning it
4958
+ * off forces every key through the provider, which is what to do when you want to re-pay for a
4959
+ * fresh translation. A dry run never reads the memory, whatever this says.
4797
4960
  */
4798
4961
  readonly cache?: boolean;
4799
4962
  }
@@ -4826,14 +4989,18 @@ interface TranslateDeps {
4826
4989
  * ```
4827
4990
  */
4828
4991
  declare function resolveDryRun(input: {
4992
+ /** The caller's {@link TranslateInput.dryRun}. */
4829
4993
  readonly dryRun?: boolean | undefined;
4994
+ /** The caller's {@link TranslateInput.estimate}, which implies a dry run. */
4830
4995
  readonly estimate?: boolean | undefined;
4831
4996
  }): boolean;
4832
4997
  /**
4833
4998
  * Runs the one-shot translation flow over every configured target locale, or over the subset named
4834
4999
  * by `locales`: read the source, diff
4835
5000
  * each locale against the lock-file baseline, translate what is missing or stale, verify placeholder
4836
- * and ICU integrity, write the locale files, and update the lock-file and translation memory.
5001
+ * and ICU integrity, write the locale files, and update the lock-file and translation memory. A
5002
+ * live run also records its summary in the run-status file that {@link runStatus} reads; a failure
5003
+ * to write that file or the translation memory is swallowed rather than failing the run.
4837
5004
  *
4838
5005
  * Failure handling is the contract worth understanding. Whole-run problems, such as an unreadable
4839
5006
  * source file or a provider that cannot be constructed, throw an {@link SdkError} before any locale
@@ -4963,7 +5130,10 @@ interface ExportWorkbookDeps {
4963
5130
  }
4964
5131
  /** What {@link exportWorkbook} wrote. */
4965
5132
  interface ExportWorkbookResult {
4966
- /** The absolute path written, or the shared base path when one file per locale was written. */
5133
+ /**
5134
+ * The absolute path written: the workbook file for `xlsx`, or the directory the per-locale files
5135
+ * were written into for a delimited format.
5136
+ */
4967
5137
  readonly path: string;
4968
5138
  /** Row counts per exported locale. */
4969
5139
  readonly locales: readonly {
@@ -4975,7 +5145,10 @@ interface ExportWorkbookResult {
4975
5145
  }
4976
5146
  /**
4977
5147
  * Writes the strings awaiting translation to a handoff a human translator can work in: a styled
4978
- * `.xlsx` workbook with one sheet per locale, or one `.csv` or `.tsv` file per locale.
5148
+ * `.xlsx` workbook with one sheet per locale, or one `.csv` or `.tsv` file per locale. A delimited
5149
+ * export also writes a `.verbatra-export-<format>.json` manifest into the output directory naming
5150
+ * the locales it exported, which {@link importWorkbook} uses to tell a leftover file from an earlier
5151
+ * export apart from a current one.
4979
5152
  *
4980
5153
  * By default only missing and stale keys are exported, which is what makes the handoff a work list
4981
5154
  * rather than a dump of the whole project. Each row carries the source text alongside any existing
@@ -5051,9 +5224,11 @@ interface ImportWorkbookDeps {
5051
5224
  * handoff.
5052
5225
  *
5053
5226
  * Imported values are held to the same integrity gate as provider output, so a translator who drops
5054
- * a placeholder or breaks ICU syntax has that row refused rather than written. Each locale takes
5055
- * its write lock, and the lock-file and translation memory are updated exactly as in a
5056
- * {@link translate} run, so an imported translation counts as up to date afterwards.
5227
+ * a placeholder, breaks inline markup or breaks ICU syntax has that row refused rather than
5228
+ * written, and a row whose source text changed since the export is refused the same way. A row
5229
+ * holding exactly `[[CLEAR]]` empties that key's translation. Each locale takes its write lock, and
5230
+ * the lock-file and translation memory are updated exactly as in a {@link translate} run, so an
5231
+ * imported translation counts as up to date afterwards.
5057
5232
  *
5058
5233
  * Damage is contained rather than fatal: a blank row keeps the existing translation and its
5059
5234
  * baseline, an unreadable row is reported as a {@link MalformedRowReport}, and a repeated key is
@@ -5061,9 +5236,12 @@ interface ImportWorkbookDeps {
5061
5236
  * the returned {@link RunSummary} rather than aborting the import. A sheet or file naming a locale
5062
5237
  * that is not configured is contained the same way: that locale fails with `CONFIG_INVALID` on its
5063
5238
  * own {@link LocaleSummary}, so nothing is written to an unmanaged path and the configured locales
5064
- * still import. Once the handoff has been read, every per-locale failure is isolated this way, so
5065
- * callers should inspect {@link RunSummary.failed} and {@link RunSummary.partial} rather than
5066
- * relying on a thrown error. A corrupt lock-file is the one exception, because it is a single
5239
+ * still import. A configured target locale the handoff carries no sheet or file for fails with
5240
+ * `WORKBOOK_SHEET_MISSING`, a delimited file the directory's export manifest does not list fails
5241
+ * with `HANDOFF_FILE_STALE` without being applied, and a row naming a key neither the source nor
5242
+ * the target holds fails its locale. Once the handoff has been read, every per-locale failure is
5243
+ * isolated this way, so callers should inspect {@link RunSummary.failed} and
5244
+ * {@link RunSummary.partial} rather than relying on a thrown error. A corrupt lock-file is the one exception, because it is a single
5067
5245
  * shared file rather than a per-locale one: it aborts the whole run even when it is discovered
5068
5246
  * after a locale has been applied, so the locales still to come are not written at all.
5069
5247
  *
@@ -5116,7 +5294,8 @@ interface LocalePathResolverConfig {
5116
5294
  /**
5117
5295
  * The configured format. Every format maps each locale to a distinct path except a
5118
5296
  * shared-catalogue format (one holding every locale in a single file, such as `apple-xcstrings`),
5119
- * which maps every locale to the same path.
5297
+ * which maps every locale to the same path. A `custom:` format is always treated as one file per
5298
+ * locale.
5120
5299
  */
5121
5300
  readonly format: FormatId$1;
5122
5301
  /** The file layout. */
@@ -5132,12 +5311,15 @@ interface LocalePathResolver {
5132
5311
  /**
5133
5312
  * The absolute path of a locale's file. Accepts any locale string, including ones outside the
5134
5313
  * configured set, so it can be used for exploratory reads.
5314
+ *
5315
+ * @throws {@link SdkError} `LOCALE_LAYOUT_INVALID`: the locale is outside the configured set and
5316
+ * has no valid single-segment spelling under the configured style.
5135
5317
  */
5136
5318
  pathFor(locale: string): string;
5137
5319
  /**
5138
5320
  * The configured locale owning an absolute path, or `undefined` when the path belongs to no
5139
- * configured locale. The path is normalized before lookup, so a relative or non-canonical path
5140
- * still resolves.
5321
+ * configured locale. The path is resolved against the resolver's `cwd` and normalized before
5322
+ * lookup, so a relative or non-canonical path still resolves.
5141
5323
  *
5142
5324
  * Always `undefined` for a shared-catalogue format: every configured locale resolves to the same
5143
5325
  * path there, so no single locale owns it.
@@ -5297,7 +5479,10 @@ declare const scaffoldingMetadata: {
5297
5479
  readonly openai: "maxOutputTokens";
5298
5480
  readonly gemini: "maxOutputTokens";
5299
5481
  };
5300
- /** Every i18n file format the SDK can read and write. */
5482
+ /**
5483
+ * Every built-in i18n file format the SDK can read and write. A `custom:` format is never listed,
5484
+ * since its adapter ships outside verbatra.
5485
+ */
5301
5486
  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"];
5302
5487
  };
5303
5488
 
@@ -5340,7 +5525,10 @@ type WatchRunResult = {
5340
5525
  interface WatchInput {
5341
5526
  /** The resolved project config, normally from {@link loadConfig}. */
5342
5527
  readonly config: VerbatraConfig;
5343
- /** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
5528
+ /**
5529
+ * Directory the `files.pattern` is resolved against, and where each run's lock-file, translation
5530
+ * memory, and run-status file live. Defaults to the process working directory.
5531
+ */
5344
5532
  readonly cwd?: string;
5345
5533
  /**
5346
5534
  * Restrict every run of the session to a subset of the configured target locales. Validated once
@@ -5359,11 +5547,17 @@ interface WatchInput {
5359
5547
  readonly onLockWait?: LockWaitListener;
5360
5548
  /** Called as locales and sub-batches start and finish, for progress reporting. */
5361
5549
  readonly onProgress?: ProgressListener;
5362
- /** How long to wait for a locale's write lock before that run fails. */
5550
+ /**
5551
+ * How long, in milliseconds, to wait for a locale's write lock before that locale fails with
5552
+ * `LOCK_CONTENDED` on the run's summary. Defaults to ten minutes.
5553
+ */
5363
5554
  readonly lockAcquireTimeoutMs?: number;
5364
- /** How many locales to run at once. Validated once when watching starts, and applied to every run. */
5555
+ /**
5556
+ * How many locales to run at once. Defaults to 1. Validated once when watching starts, and
5557
+ * applied to every run.
5558
+ */
5365
5559
  readonly concurrency?: number;
5366
- /** Consult and update the translation memory. Defaults to true. */
5560
+ /** Consult and update the translation memory, fuzzy reuse included. Defaults to true. */
5367
5561
  readonly cache?: boolean;
5368
5562
  }
5369
5563
  /** Injectable dependencies for {@link watch}. Every field has a working default. */