@verbatra/sdk 0.6.1 → 0.6.2

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.ts CHANGED
@@ -231,10 +231,81 @@ declare const supportedFormatSchema$1: z.ZodEnum<{
231
231
  xliff: "xliff";
232
232
  yaml: "yaml";
233
233
  arb: "arb";
234
+ properties: "properties";
234
235
  }>;
235
236
  /** One of the supported source formats; a member of {@link SUPPORTED_FORMATS}. */
236
237
  type SupportedFormat$1 = z.infer<typeof supportedFormatSchema$1>;
237
238
 
239
+ /** Outcome of a bounded read: the content, or why it could not be read in bounds. */
240
+ type BoundedFileRead = {
241
+ readonly kind: "ok";
242
+ readonly content: string;
243
+ } | {
244
+ readonly kind: "missing";
245
+ } | {
246
+ readonly kind: "too-large";
247
+ };
248
+ /** Outcome of a bounded binary read: the bytes, or why they could not be read in bounds. */
249
+ type BoundedBytesRead = {
250
+ readonly kind: "ok";
251
+ readonly bytes: Uint8Array;
252
+ } | {
253
+ readonly kind: "missing";
254
+ } | {
255
+ readonly kind: "too-large";
256
+ };
257
+ /**
258
+ * The minimal file-system surface the SDK needs. Reads are bounded and writes are atomic. Injectable so
259
+ * tests stay deterministic; the format adapters do their own file IO and bypass this seam.
260
+ */
261
+ interface SdkFs {
262
+ /** Whether a readable file exists at the path. */
263
+ fileExists(path: string): Promise<boolean>;
264
+ /**
265
+ * Read a file as UTF-8 through a single handle, bounded to maxBytes. TOCTOU-safe: the handle is
266
+ * fstat'd and the read never advances past the sized length, so swapping in a larger file cannot
267
+ * bypass the cap. A missing or unreadable path is "missing"; a file over the cap is "too-large".
268
+ */
269
+ readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
270
+ /** Read a file as raw bytes with the same TOCTOU-safe, bounded discipline as {@link readFileBounded}. */
271
+ readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
272
+ /** Write atomically: a temp file in the same directory, then rename over the target. */
273
+ writeFile(path: string, data: string): Promise<void>;
274
+ /** Write raw bytes atomically (temp file, then rename over the target). Used for the workbook. */
275
+ writeBytes(path: string, data: Uint8Array): Promise<void>;
276
+ /**
277
+ * Atomically create a file only if it does not already exist (O_EXCL semantics), creating any
278
+ * missing parent directory first. Returns true if this call created the file, false if a file
279
+ * was already present (no write performed). The backbone of {@link withLocaleWriteLock}'s
280
+ * exclusive-create lock: the parent-directory creation lives here so that primitive never touches
281
+ * `node:fs` directly and a fake `SdkFs` in tests never needs a real directory to exist.
282
+ */
283
+ createExclusive(path: string, data: string): Promise<boolean>;
284
+ /** Delete a file if present; a no-op if it is already absent. */
285
+ deleteFile(path: string): Promise<void>;
286
+ }
287
+
288
+ /**
289
+ * The content-addressed translation-memory (TM) cache: a local, gitignored, regenerable sidecar to
290
+ * the lock file (`verbatra.cache.json`). It lets a translation whose source content is unchanged be
291
+ * reused for free, even under a renamed key or across two keys with byte-identical source text,
292
+ * instead of being re-sent to the provider.
293
+ *
294
+ * Nesting is `entries[fingerprint][targetLocale][contentHash] = value`. The fingerprint scopes every
295
+ * entry to one translation context (provider, model, tone, glossary), so a tone or glossary change
296
+ * never serves a stale value. Unlike the lock file, a corrupt or wrong-version cache degrades to an
297
+ * empty cache and never fails a run: it is a cost optimization, not a correctness record.
298
+ */
299
+ interface TranslationMemory {
300
+ /** The cache schema version; a value this build does not recognize degrades the whole file to empty. */
301
+ readonly version: number;
302
+ /** Cached values, nested fingerprint to target locale to source content hash to translated value. */
303
+ readonly entries: Readonly<Record<string, Readonly<Record<string, Readonly<Record<string, string>>>>>>;
304
+ }
305
+
306
+ /** The cache file's name, a sibling of the lock file and obviously JSON. */
307
+ declare const CACHE_FILE_NAME = "verbatra.cache.json";
308
+
238
309
  /**
239
310
  * The provider section of the config: a discriminated union over the provider id, reusing each
240
311
  * provider's own config schema. There is no key field anywhere in this union except
@@ -299,6 +370,7 @@ declare const verbatraConfigSchema: z.ZodObject<{
299
370
  xliff: "xliff";
300
371
  yaml: "yaml";
301
372
  arb: "arb";
373
+ properties: "properties";
302
374
  }>;
303
375
  files: z.ZodObject<{
304
376
  pattern: z.ZodString;
@@ -445,55 +517,6 @@ declare function defineConfig(config: AuthoringConfigFor<"gemini">): VerbatraCon
445
517
  declare function defineConfig(config: AuthoringConfigFor<"deepl">): VerbatraConfigInput;
446
518
  declare function defineConfig(config: AuthoringConfig): VerbatraConfigInput;
447
519
 
448
- /** Outcome of a bounded read: the content, or why it could not be read in bounds. */
449
- type BoundedFileRead = {
450
- readonly kind: "ok";
451
- readonly content: string;
452
- } | {
453
- readonly kind: "missing";
454
- } | {
455
- readonly kind: "too-large";
456
- };
457
- /** Outcome of a bounded binary read: the bytes, or why they could not be read in bounds. */
458
- type BoundedBytesRead = {
459
- readonly kind: "ok";
460
- readonly bytes: Uint8Array;
461
- } | {
462
- readonly kind: "missing";
463
- } | {
464
- readonly kind: "too-large";
465
- };
466
- /**
467
- * The minimal file-system surface the SDK needs. Reads are bounded and writes are atomic. Injectable so
468
- * tests stay deterministic; the format adapters do their own file IO and bypass this seam.
469
- */
470
- interface SdkFs {
471
- /** Whether a readable file exists at the path. */
472
- fileExists(path: string): Promise<boolean>;
473
- /**
474
- * Read a file as UTF-8 through a single handle, bounded to maxBytes. TOCTOU-safe: the handle is
475
- * fstat'd and the read never advances past the sized length, so swapping in a larger file cannot
476
- * bypass the cap. A missing or unreadable path is "missing"; a file over the cap is "too-large".
477
- */
478
- readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
479
- /** Read a file as raw bytes with the same TOCTOU-safe, bounded discipline as {@link readFileBounded}. */
480
- readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
481
- /** Write atomically: a temp file in the same directory, then rename over the target. */
482
- writeFile(path: string, data: string): Promise<void>;
483
- /** Write raw bytes atomically (temp file, then rename over the target). Used for the workbook. */
484
- writeBytes(path: string, data: Uint8Array): Promise<void>;
485
- /**
486
- * Atomically create a file only if it does not already exist (O_EXCL semantics), creating any
487
- * missing parent directory first. Returns true if this call created the file, false if a file
488
- * was already present (no write performed). The backbone of {@link withLocaleWriteLock}'s
489
- * exclusive-create lock: the parent-directory creation lives here so that primitive never touches
490
- * `node:fs` directly and a fake `SdkFs` in tests never needs a real directory to exist.
491
- */
492
- createExclusive(path: string, data: string): Promise<boolean>;
493
- /** Delete a file if present; a no-op if it is already absent. */
494
- deleteFile(path: string): Promise<void>;
495
- }
496
-
497
520
  /** Where a config's resolved glossary came from: absent, inline, or a file at the recorded path. */
498
521
  type GlossaryProvenance = {
499
522
  readonly source: "none";
@@ -620,11 +643,18 @@ declare function loadConfig(options?: LoadConfigOptions): Promise<VerbatraConfig
620
643
  * its timeout elapsed, because another process is holding it (or an orphaned lock file was left
621
644
  * behind by a killed process); the message names the lock file's path (thrown by `translate`,
622
645
  * `importWorkbook`, `editEntry`, and `retranslateEntry`).
646
+ * - `CONCURRENCY_INVALID`: the `concurrency` input is not an integer of at least 1 (thrown by
647
+ * `translate` and, per run, by `watch`, before any locale runs).
648
+ * - `CONCURRENCY_BUDGET_CONFLICT`: a live run requested `concurrency` greater than 1 while a token
649
+ * budget (`maxTokens`) is configured; the two are mutually exclusive because concurrency makes the
650
+ * budget's stop guarantee nondeterministic (thrown by `translate` before any provider call; a dry
651
+ * run is exempt, since it never folds usage into or consults the budget tracker, so the conflict
652
+ * cannot arise).
623
653
  * - `LOCALE_FAILED` (NOT thrown): the fallback `code` recorded on a failed `LocaleSummary` when a
624
654
  * per-locale failure carries no string code of its own. See the surfaced-not-thrown distinction
625
655
  * on `translate`.
626
656
  */
627
- type SdkErrorCode = "CONFIG_NOT_FOUND" | "CONFIG_INVALID" | "UNKNOWN_FORMAT" | "UNKNOWN_LOCALE" | "UNKNOWN_KEY" | "PROVIDER_CONSTRUCTION_FAILED" | "SOURCE_UNREADABLE" | "SOURCE_INVALID" | "LOCK_FILE_INVALID" | "LOCK_CONTENDED" | "LOCALE_FAILED";
657
+ 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" | "CONCURRENCY_INVALID" | "CONCURRENCY_BUDGET_CONFLICT" | "LOCALE_FAILED";
628
658
  /** The single structured error the SDK throws or records. Never carries a secret. */
629
659
  declare class SdkError extends Error {
630
660
  /** The stable {@link SdkErrorCode} for this failure; branch on this, not the message. */
@@ -645,6 +675,7 @@ declare const supportedFormatSchema: z.ZodEnum<{
645
675
  xliff: "xliff";
646
676
  yaml: "yaml";
647
677
  arb: "arb";
678
+ properties: "properties";
648
679
  }>;
649
680
  /** One of the supported source formats; a member of {@link SUPPORTED_FORMATS}. */
650
681
  type SupportedFormat = z.infer<typeof supportedFormatSchema>;
@@ -981,7 +1012,10 @@ type EditEntryResult = {
981
1012
  * locale file (merging just this one key into its current entries, every other key untouched) and
982
1013
  * updates the lock entry for this key only (see {@link updateLockFileLocale}'s `"merge"` mode); on
983
1014
  * rejection, writes nothing. Never calls a provider: {@link EditEntryDeps} has no provider seam,
984
- * so there is no way to construct or call one.
1015
+ * so there is no way to construct or call one. On acceptance it also feeds the accepted value into
1016
+ * the local translation-memory cache (best-effort, keyed by the source content hash and the config
1017
+ * fingerprint), so a later run can reuse it for free; a cache failure is swallowed and never fails
1018
+ * the edit.
985
1019
  *
986
1020
  * `locale` and `key` are resolved fresh on every call, never from a cache: `locale` against
987
1021
  * `config.targetLocales`, `key` against the source resource read fresh from disk.
@@ -1272,6 +1306,9 @@ type RetranslateEntryResult = {
1272
1306
  * the lock entry for this key only (see {@link updateLockFileLocale}'s `"merge"` mode); on rejection,
1273
1307
  * writes nothing. Reuses exactly the building blocks `translate()` already uses
1274
1308
  * (`selectProvider`/`provider.translateBatch`) and nothing else: no bespoke per-provider plumbing.
1309
+ * Always calls the provider (it never consults the cache for a hit, since its whole purpose is a
1310
+ * fresh translation), then feeds the accepted value into the translation-memory cache best-effort so
1311
+ * a later run can reuse it; a cache failure is swallowed and never fails the retranslate.
1275
1312
  *
1276
1313
  * `locale` and `key` are resolved fresh on every call, never cached: `locale` against
1277
1314
  * `config.targetLocales` (`UNKNOWN_LOCALE`), `key` against the source resource's own keys, read
@@ -1357,6 +1394,26 @@ interface NeedsReviewEntry {
1357
1394
  /** Every {@link ReviewReasonCode} that applies to this key. */
1358
1395
  readonly reasons: readonly ReviewReasonCode[];
1359
1396
  }
1397
+ /**
1398
+ * One row a workbook import could not read: the 1-based worksheet row number and the header label of
1399
+ * the offending column. Carries no cell content, so untrusted workbook text never reaches the summary.
1400
+ */
1401
+ interface MalformedRowReport {
1402
+ /** The 1-based worksheet row number of the malformed row. */
1403
+ readonly row: number;
1404
+ /** The header label of the column the row was rejected on (for example "Status"). */
1405
+ readonly column: string;
1406
+ }
1407
+ /**
1408
+ * One duplicate-key conflict a workbook import found in a locale's sheet: the duplicated key and the
1409
+ * 1-based worksheet row number of the later (losing) occurrence. The first occurrence is the winner.
1410
+ */
1411
+ interface DuplicateKeyReport {
1412
+ /** The key that appeared more than once in the locale's sheet. */
1413
+ readonly key: string;
1414
+ /** The 1-based worksheet row number of the later occurrence that lost to the first. */
1415
+ readonly row: number;
1416
+ }
1360
1417
  /** Structured outcome for one target locale; surfaced as data on the run, never thrown. */
1361
1418
  interface LocaleSummary {
1362
1419
  /** The target locale this summary is for. */
@@ -1370,6 +1427,12 @@ interface LocaleSummary {
1370
1427
  * {@link LocaleSummary.budgetWithheld} non-empty). `"failed"` when the locale had candidate keys
1371
1428
  * but accepted nothing at all (neither translated nor generated) while withholding at least one.
1372
1429
  * Withheld keys keep their prior lock hash and retry next run.
1430
+ *
1431
+ * A workbook import's structural findings, {@link LocaleSummary.unfilled},
1432
+ * {@link LocaleSummary.malformedRows}, and {@link LocaleSummary.duplicateKeys}, deliberately do NOT
1433
+ * feed this status: they are surfaced in their own lists (and the CLI) but are not withholdings of
1434
+ * an attempted translation, so a locale that dropped a malformed row, collapsed a duplicate key, or
1435
+ * left a `changed` row unfilled while accepting its other rows can still be `"succeeded"`.
1373
1436
  */
1374
1437
  readonly status: "succeeded" | "partial" | "failed";
1375
1438
  /**
@@ -1389,6 +1452,15 @@ interface LocaleSummary {
1389
1452
  readonly pruned: readonly string[];
1390
1453
  /** Source keys flagged invalid-ICU that were skipped for translation this run. */
1391
1454
  readonly invalidIcuSource: readonly string[];
1455
+ /**
1456
+ * Keys served from the local translation-memory cache instead of the provider: their source content
1457
+ * was unchanged (possibly under a renamed key or shared with another key) and the cached value passed
1458
+ * the integrity gate, so no provider request was made for them. They are written and lock-advanced
1459
+ * exactly like a translated key, but kept distinct from {@link LocaleSummary.translated} because they
1460
+ * cost no provider usage. Always present, empty by default; empty in a dry-run and when the cache is
1461
+ * bypassed. Sorted by key.
1462
+ */
1463
+ readonly cacheHits: readonly string[];
1392
1464
  /**
1393
1465
  * Translated keys that failed the placeholder-integrity check and were withheld. Never includes a
1394
1466
  * key withheld because the provider call itself failed; see {@link LocaleSummary.providerFailures}
@@ -1440,6 +1512,27 @@ interface LocaleSummary {
1440
1512
  * flags on its own path).
1441
1513
  */
1442
1514
  readonly needsReview: readonly NeedsReviewEntry[];
1515
+ /**
1516
+ * Keys the workbook exported as `changed` (needing an updated translation) that the translator left
1517
+ * blank, drifted or not, sorted by key. Pending work made visible: the row is skipped (nothing is
1518
+ * written and the prior lock baseline is kept), but the key is surfaced so an unfinished import is
1519
+ * never silently reported as done. Only a workbook import populates this; the provider path, which
1520
+ * never leaves a candidate key unfilled by a person, always leaves it empty.
1521
+ */
1522
+ readonly unfilled: readonly string[];
1523
+ /**
1524
+ * Rows a workbook import could not read for this locale, each by worksheet row number and offending
1525
+ * column, in row order. The sheet's other rows still import; a malformed row is reported, not
1526
+ * written. Only a workbook import populates this; the provider path always leaves it empty.
1527
+ */
1528
+ readonly malformedRows: readonly MalformedRowReport[];
1529
+ /**
1530
+ * Duplicate-key conflicts a workbook import found in this locale's sheet, in row order. The rule is
1531
+ * first occurrence wins: the first row for a key is judged and its later occurrences are reported
1532
+ * here and otherwise ignored. Only a workbook import populates this; the provider path always leaves
1533
+ * it empty.
1534
+ */
1535
+ readonly duplicateKeys: readonly DuplicateKeyReport[];
1443
1536
  /**
1444
1537
  * A structured, secret-free error for a locale that threw (an adapter/lock/provider-construction
1445
1538
  * failure isolated as data): present only on a "failed" locale, and only that throw path sets it.
@@ -1564,6 +1657,63 @@ interface LockWaitEvent {
1564
1657
  /** Called while an acquire is blocked, to surface wait progress; the lock module itself writes no output. */
1565
1658
  type LockWaitListener = (event: LockWaitEvent) => void;
1566
1659
 
1660
+ /**
1661
+ * One structured progress notification emitted while a run advances through its locales and their
1662
+ * provider sub-batches. Emitted to a caller-supplied {@link ProgressListener}; the SDK itself writes
1663
+ * nothing to any stream, so a caller (the CLI) is free to render these to stderr and keep stdout a
1664
+ * clean summary or NDJSON stream. Discriminated on `type`, mirroring the lock-wait event contract.
1665
+ */
1666
+ type ProgressEvent = LocaleStartedEvent | SubBatchProgressEvent | LocaleFinishedEvent | RunFinishedEvent;
1667
+ /** A locale is about to be processed: its position in the run's target-locale order. */
1668
+ interface LocaleStartedEvent {
1669
+ readonly type: "locale-started";
1670
+ /** The target locale about to run. */
1671
+ readonly locale: string;
1672
+ /** Zero-based index of this locale in the run's target-locale order. */
1673
+ readonly localeIndex: number;
1674
+ /** Total number of target locales in this run. */
1675
+ readonly totalLocales: number;
1676
+ }
1677
+ /**
1678
+ * One sub-batch of a locale's main translation loop has been reached. Emitted once per loop
1679
+ * iteration, in order, so a caller can render a "batch N of M" line. Fired at the top of the
1680
+ * iteration regardless of whether the batch then makes a provider call: a budget-withheld batch
1681
+ * (the run's token budget already tripped) still emits this event, which keeps `batchIndex` 1-based
1682
+ * and contiguous against the total. A dry-run never reaches this loop and so never emits it.
1683
+ */
1684
+ interface SubBatchProgressEvent {
1685
+ readonly type: "sub-batch";
1686
+ /** The locale whose entries this sub-batch carries. */
1687
+ readonly locale: string;
1688
+ /** One-based index of this sub-batch within the locale. */
1689
+ readonly batchIndex: number;
1690
+ /** Total number of sub-batches this locale's main translation loop runs. */
1691
+ readonly totalBatches: number;
1692
+ }
1693
+ /** A locale finished processing: how many keys it accepted (translated). */
1694
+ interface LocaleFinishedEvent {
1695
+ readonly type: "locale-finished";
1696
+ /** The target locale that finished. */
1697
+ readonly locale: string;
1698
+ /** Number of keys accepted (translated) for this locale. In a dry-run, the keys that would be translated. */
1699
+ readonly translated: number;
1700
+ }
1701
+ /** The run reached the end of its locale loop: how many locales it processed. */
1702
+ interface RunFinishedEvent {
1703
+ readonly type: "run-finished";
1704
+ /**
1705
+ * Number of locales the run processed (including failed locales: a per-locale failure is isolated
1706
+ * as a summary rather than thrown, so it is still counted here).
1707
+ */
1708
+ readonly localesCompleted: number;
1709
+ }
1710
+ /**
1711
+ * Called with each {@link ProgressEvent} as a run advances; the SDK emits no output of its own. A
1712
+ * caller (the CLI) uses this to render progress, always to stderr, so stdout stays byte-identical to
1713
+ * a run with no listener attached.
1714
+ */
1715
+ type ProgressListener = (event: ProgressEvent) => void;
1716
+
1567
1717
  /** Everything the one-shot run needs: the validated config and where/how to run it. */
1568
1718
  interface TranslateInput {
1569
1719
  /** The validated configuration (typically from {@link loadConfig}). */
@@ -1592,12 +1742,43 @@ interface TranslateInput {
1592
1742
  * is the only wait-progress signal (the CLI renders its "still waiting" line from it).
1593
1743
  */
1594
1744
  readonly onLockWait?: LockWaitListener;
1745
+ /**
1746
+ * Called as the run advances: once per locale before it starts and once after it finishes, once
1747
+ * per provider sub-batch within a locale, and once when the whole locale loop ends. Never fires a
1748
+ * sub-batch event on a dry-run (no provider call is made). The SDK writes no output; this is the
1749
+ * only progress signal (the CLI renders it to stderr, keeping stdout byte-identical).
1750
+ *
1751
+ * Pairing is not guaranteed when the run throws: a whole-run failure (for example a corrupt
1752
+ * lock-file surfacing as `LOCK_FILE_INVALID`, which re-throws instead of being isolated per locale)
1753
+ * can emit a `locale-started` event with no matching `locale-finished`, and no `run-finished` at
1754
+ * all. A per-locale failure that is isolated (not re-thrown) still emits both, and is counted in
1755
+ * `run-finished`.
1756
+ */
1757
+ readonly onProgress?: ProgressListener;
1595
1758
  /**
1596
1759
  * Override how long a locale's write lock keeps retrying before failing with `LOCK_CONTENDED`, in
1597
1760
  * milliseconds. Defaults to the lock's own 10-minute default when unset (surfaced to the CLI as
1598
1761
  * `--lock-timeout`).
1599
1762
  */
1600
1763
  readonly lockAcquireTimeoutMs?: number;
1764
+ /**
1765
+ * How many target locales may run at once, a positive integer (surfaced to the CLI as
1766
+ * `--concurrency`). Defaults to 1, which runs locales strictly in sequence and is byte-identical
1767
+ * to a run with this option unset. A value below 1 or a non-integer is rejected with a whole-run
1768
+ * `CONCURRENCY_INVALID` error. On a live run, a value greater than 1 is rejected with a whole-run
1769
+ * `CONCURRENCY_BUDGET_CONFLICT` error when the config sets a `maxTokens` budget, because
1770
+ * concurrency would make the budget's stop guarantee nondeterministic; a dry run is exempt. The
1771
+ * per-locale write locks already isolate concurrent locales on disk, so no extra locking is added.
1772
+ */
1773
+ readonly concurrency?: number;
1774
+ /**
1775
+ * Whether to use the local content-addressed translation-memory cache (`verbatra.cache.json`). On by
1776
+ * default; the CLI exposes it as `--no-cache`. When false, both the cache read and the end-of-run
1777
+ * cache write are skipped: the run makes exactly the provider calls it would with no cache present,
1778
+ * and leaves any existing cache file untouched. Ignored on a dry-run, which never reads or writes the
1779
+ * cache regardless. See the cache module for the fingerprint and degrade-to-empty contract.
1780
+ */
1781
+ readonly cache?: boolean;
1601
1782
  }
1602
1783
  /** Composition seam: inject a registry, a provider builder, and a file system for tests. */
1603
1784
  interface TranslateDeps {
@@ -1644,6 +1825,11 @@ interface TranslateDeps {
1644
1825
  * @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be read or parsed (wraps the
1645
1826
  * adapter read error).
1646
1827
  * @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is present but corrupt or oversized.
1828
+ * @throws {@link SdkError} `CONCURRENCY_INVALID`: `concurrency` is defined but not an integer of at
1829
+ * least 1; raised before any locale runs.
1830
+ * @throws {@link SdkError} `CONCURRENCY_BUDGET_CONFLICT`: a live run set `concurrency` greater than 1
1831
+ * while the config configures a `maxTokens` budget; raised before any provider call (a dry run is
1832
+ * exempt).
1647
1833
  * @example
1648
1834
  * ```ts
1649
1835
  * import { loadConfig, translate } from "@verbatra/sdk";
@@ -1735,10 +1921,13 @@ interface ImportWorkbookDeps {
1735
1921
  * identical to `translate`'s.
1736
1922
  *
1737
1923
  * Whole-run failures (unknown format, unreadable/invalid/oversized workbook, corrupt lock) throw a
1738
- * structured {@link SdkError}. A per-sheet failure (a locale not in config, a broken-round-trip key,
1739
- * a write failure) is isolated as that locale's `status: "failed"`, not a throw; per-row rejections
1740
- * are withheld and reported on the locale. Dry-run validates and reports without writing any locale or
1741
- * lock file, and skips lock acquisition (there is nothing to protect).
1924
+ * structured {@link SdkError}. A per-sheet failure (a sheet named for a locale not in config, a
1925
+ * broken-round-trip key, a write failure) is isolated as that locale's `status: "failed"`, not a
1926
+ * throw; per-row rejections are withheld and reported on the locale. A configured target locale with
1927
+ * no sheet at all (a deleted, renamed, or reordered tab) is reconciled after the sheet loop and
1928
+ * reported as that locale's `status: "failed"` (`WORKBOOK_SHEET_MISSING`) rather than silently
1929
+ * dropped. Dry-run validates and reports without writing any locale or lock file, and skips lock
1930
+ * acquisition (there is nothing to protect).
1742
1931
  *
1743
1932
  * The lock-file is read once, up front, for every sheet's diff baseline. On a non-dry-run, each
1744
1933
  * sheet's write-and-lock-update step then holds that locale's `withLocaleWriteLock` for its whole
@@ -1817,7 +2006,7 @@ declare const scaffoldingMetadata: {
1817
2006
  readonly gemini: "gemini-2.5-flash";
1818
2007
  };
1819
2008
  /** The closed set of source format ids. Owned by core. */
1820
- readonly supportedFormats: readonly ["i18next-json", "vue-i18n-json", "next-intl-json", "ngx-translate-json", "xliff", "yaml", "arb"];
2009
+ readonly supportedFormats: readonly ["i18next-json", "vue-i18n-json", "next-intl-json", "ngx-translate-json", "xliff", "yaml", "arb", "properties"];
1821
2010
  };
1822
2011
 
1823
2012
  /** A minimal source-change event source. Production wraps chokidar; tests inject a stub. */
@@ -1862,8 +2051,24 @@ interface WatchInput {
1862
2051
  * one-shot run does.
1863
2052
  */
1864
2053
  readonly onLockWait?: LockWaitListener;
2054
+ /**
2055
+ * Passed through to every run's {@link TranslateInput.onProgress}: fires per locale and per provider
2056
+ * sub-batch as each run advances, so a watch caller can surface the same progress a one-shot run does.
2057
+ */
2058
+ readonly onProgress?: ProgressListener;
1865
2059
  /** Passed through to every run's {@link TranslateInput.lockAcquireTimeoutMs}; the lock's 10-minute default when unset. */
1866
2060
  readonly lockAcquireTimeoutMs?: number;
2061
+ /**
2062
+ * Passed through to every run's {@link TranslateInput.concurrency}: how many locales each cycle may
2063
+ * run at once. Defaults to 1 (strictly serial). The per-cycle tracker is fresh, so a budgeted watch
2064
+ * with concurrency greater than 1 still fails each run with `CONCURRENCY_BUDGET_CONFLICT`.
2065
+ */
2066
+ readonly concurrency?: number;
2067
+ /**
2068
+ * Passed through to every run's {@link TranslateInput.cache}: when false, each run bypasses the
2069
+ * translation-memory cache (both read and write). On by default.
2070
+ */
2071
+ readonly cache?: boolean;
1867
2072
  }
1868
2073
  /** Composition seam: inject the watcher and the run for deterministic, offline tests. */
1869
2074
  interface WatchDeps {
@@ -1920,4 +2125,4 @@ interface WatchController {
1920
2125
  */
1921
2126
  declare function watch(input: WatchInput, deps?: WatchDeps): Promise<WatchController>;
1922
2127
 
1923
- export { type BudgetBehavior, type CheckDeps, type CheckInput, type CheckSummary, type ConfigSource, type CreateProvider, type CreateWatcher, DEFAULT_WORKBOOK_PATH, type DiffDeps, type DiffInput, type DiffSummary, type EditEntryDeps, type EditEntryInput, type EditEntryResult, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type GlossaryProvenance, type ImportWorkbookDeps, type ImportWorkbookInput, type IntegrityGateReason, type KeyIntegrityDeps, type KeyIntegrityEntry, type KeyIntegrityInput, type KeyValueDeps, type KeyValueInput, type KeyValueResult, LOCK_FILE_NAME, type LoadConfigOptions, type LoadLockFileDeps, type LoadLockFileInput, type LoadedConfig, type LocaleCheckSummary, type LocaleDiff, type LocaleFileSnapshot, type LocaleKeyIntegrity, type LocaleNotice, type LocaleSnapshotDelta, type LocaleSummary, type LockFile, type LockHolder, type LockLocaleState, type LockStateDeps, type LockStateInput, type LockStateResult, type LockWaitEvent, type LockWaitListener, type NeedsReviewEntry, type ProviderConfig, type ProviderId, type ReadLocaleFileSnapshotDeps, type ReadLocaleFileSnapshotInput, type RetranslateEntryDeps, type RetranslateEntryInput, type RetranslateEntryResult, type ReviewReasonCode, type RunBudget, 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 SupportedFormat$1 as SupportedFormat, type TranslateDeps, type TranslateInput, type UsageSummary, type VerbatraConfig, type VerbatraConfigInput, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, check, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate, verbatraConfigSchema, watch };
2128
+ export { type BudgetBehavior, CACHE_FILE_NAME, type CheckDeps, type CheckInput, type CheckSummary, type ConfigSource, type CreateProvider, type CreateWatcher, DEFAULT_WORKBOOK_PATH, type DiffDeps, type DiffInput, type DiffSummary, type DuplicateKeyReport, type EditEntryDeps, type EditEntryInput, type EditEntryResult, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type GlossaryProvenance, type ImportWorkbookDeps, type ImportWorkbookInput, type IntegrityGateReason, type KeyIntegrityDeps, type KeyIntegrityEntry, type KeyIntegrityInput, type KeyValueDeps, type KeyValueInput, type KeyValueResult, LOCK_FILE_NAME, type LoadConfigOptions, type LoadLockFileDeps, type LoadLockFileInput, type LoadedConfig, type LocaleCheckSummary, type LocaleDiff, type LocaleFileSnapshot, type LocaleFinishedEvent, type LocaleKeyIntegrity, type LocaleNotice, type LocaleSnapshotDelta, type LocaleStartedEvent, type LocaleSummary, type LockFile, type LockHolder, type LockLocaleState, type LockStateDeps, type LockStateInput, type LockStateResult, type LockWaitEvent, type LockWaitListener, type MalformedRowReport, type NeedsReviewEntry, type ProgressEvent, type ProgressListener, type ProviderConfig, type ProviderId, type ReadLocaleFileSnapshotDeps, type ReadLocaleFileSnapshotInput, type RetranslateEntryDeps, type RetranslateEntryInput, type RetranslateEntryResult, type ReviewReasonCode, type RunBudget, type RunFinishedEvent, type RunStatusDeps, type RunStatusFile, type RunStatusInput, type RunStatusLocale, type RunStatusResult, type RunSummary, type RunTranslate, type ScaffoldableProviderId, SdkError, type SdkErrorCode, type SdkFs, type SdkNotice, type SdkNoticeCode, type SubBatchProgressEvent, type SupportedFormat$1 as SupportedFormat, type TranslateDeps, type TranslateInput, type TranslationMemory, type UsageSummary, type VerbatraConfig, type VerbatraConfigInput, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, check, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate, verbatraConfigSchema, watch };