@verbatra/sdk 0.8.0 → 0.9.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.ts CHANGED
@@ -69,10 +69,11 @@ type Tone = "formal" | "informal" | "neutral";
69
69
  */
70
70
  type PlaceholderExtractor = (value: string) => readonly string[];
71
71
  /**
72
- * Compares a source and translated value's placeholders directly, branch-aware, instead of
73
- * independently extracting each side's flat placeholder list first. Supplied by the caller (the SDK, for
74
- * a format whose adapter defines one, for example ICU plural/select) so it matches the entries' format;
75
- * ai-providers never derives it itself and never parses a specific format's message syntax.
72
+ * Compares a source and translated value directly instead of independently extracting each side's flat
73
+ * placeholder list first, which lets it express checks the flat lists cannot (branch-aware ICU
74
+ * comparison, or a one-directional rule). Supplied by the caller (the SDK, for a format whose adapter
75
+ * defines one) so it matches the entries' format; ai-providers never derives it itself and never parses
76
+ * a specific format's message syntax.
76
77
  */
77
78
  type PlaceholderComparator = (source: string, translated: string) => PlaceholderIntegrityResult$1;
78
79
  /**
@@ -94,9 +95,11 @@ interface TranslateRequest {
94
95
  /** Mandatory placeholder extractor; the output integrity check runs against it. */
95
96
  readonly extractPlaceholders: PlaceholderExtractor;
96
97
  /**
97
- * Optional branch-aware placeholder comparator. When present, the output integrity check uses it
98
+ * Optional whole-value placeholder comparator. When present, the output integrity check uses it
98
99
  * instead of independently extracting each side's placeholders with {@link extractPlaceholders} and
99
- * diffing the flat lists. Absent for a format with no plural/select sub-message structure.
100
+ * diffing the flat lists. Supplied for a format whose adapter defines one, whether because the
101
+ * comparison is branch-aware or because it adds a check the flat lists cannot express. Absent
102
+ * otherwise.
100
103
  */
101
104
  readonly comparePlaceholders?: PlaceholderComparator;
102
105
  /**
@@ -310,11 +313,11 @@ type BoundedBytesRead = {
310
313
  * `deps.fs` option redirects that I/O, which is how the SDK's tests avoid touching disk and how an
311
314
  * embedding application can back part of a project with something other than a local disk.
312
315
  *
313
- * Know what the seam does and does not cover before relying on it. It carries the run-status file,
314
- * the lock-file, the config glossary, and workbook and interchange I/O. It does not carry the
315
- * locale files: those are read and written by `@verbatra/format-adapters`, which uses `node:fs`
316
- * directly and is given a path rather than this port. Supplying a `deps.fs` therefore does not make
317
- * a run fully in-memory, and a project's translations are still read from and written to real disk.
316
+ * The seam carries every file the SDK touches: the run-status file, the lock-file, the config
317
+ * glossary, workbook and interchange I/O, and the locale files themselves, which the adapters read
318
+ * and write through a port built from this one. The single exception is a caller-supplied
319
+ * `deps.adapterRegistry`: those adapters were constructed by the caller, so their file access is
320
+ * whatever the caller wired into them, and supplying both means the caller owns that wiring.
318
321
  *
319
322
  * Reads are size-bounded by contract so that a hostile or accidentally huge file cannot exhaust
320
323
  * memory. Writes are expected to be atomic: the default implementation writes to a temporary file
@@ -435,12 +438,17 @@ type ProviderId = ProviderConfig["id"];
435
438
  * own tooling.
436
439
  *
437
440
  * The object is strict, so an unrecognized key is an error rather than being silently ignored: a
438
- * typo in a config file is reported instead of quietly doing nothing. Beyond the per-field checks,
439
- * three whole-config rules are enforced: `targetLocales` must not contain the source locale, it
440
- * must not contain two locales that differ only in case (they would collide on a case-insensitive
441
- * file system), and `files.pattern` must contain the `{locale}` token.
441
+ * typo in a config file is reported instead of quietly doing nothing. The one exception is the
442
+ * optional `$schema` key, accepted so a JSON or YAML config can point an editor at the JSON Schema
443
+ * document the package ships as `@verbatra/sdk/config-schema.json`.
444
+ *
445
+ * Beyond the per-field checks, two whole-config rules are enforced: `targetLocales` must not
446
+ * contain the source locale, and it must not contain two locales that differ only in case (they
447
+ * would collide on a case-insensitive file system). The `{locale}` token requirement is a
448
+ * per-field check on `files.pattern`.
442
449
  */
443
450
  declare const verbatraConfigSchema: z.ZodObject<{
451
+ $schema: z.ZodOptional<z.ZodString>;
444
452
  sourceLocale: z.ZodString;
445
453
  targetLocales: z.ZodArray<z.ZodString>;
446
454
  format: z.ZodEnum<{
@@ -682,6 +690,90 @@ type GlossaryProvenance = {
682
690
  readonly path: string;
683
691
  };
684
692
 
693
+ /** Input for {@link readGlossaryFile} and {@link updateGlossaryTerm}. */
694
+ interface GlossaryFileInput {
695
+ /**
696
+ * Where the loaded config's glossary came from, as reported by {@link loadConfigWithMeta}. Only a
697
+ * `file` provenance is accepted; it also carries the path, which is why neither function takes one.
698
+ */
699
+ readonly glossary: GlossaryProvenance;
700
+ }
701
+ /** Input for {@link updateGlossaryTerm}. */
702
+ interface UpdateGlossaryTermInput extends GlossaryFileInput {
703
+ /** Directory the write lock is taken under. Defaults to the process working directory. */
704
+ readonly cwd?: string;
705
+ /** The source term to add, replace, or remove. Must not be blank. */
706
+ readonly term: string;
707
+ /** The translation to store for the term, or `null` to remove the term. Must not be blank. */
708
+ readonly translation: string | null;
709
+ }
710
+ /** Injectable dependencies for {@link readGlossaryFile} and {@link updateGlossaryTerm}. */
711
+ interface GlossaryFileDeps {
712
+ /** File-system port. Defaults to the real file system. */
713
+ readonly fs?: SdkFs;
714
+ }
715
+ /**
716
+ * Reads a file-backed glossary fresh from disk, under the same validation {@link loadConfig} applies
717
+ * when it resolves a glossary path. Use it when a long-running tool has to show the glossary as it
718
+ * is now rather than as it was when the config was loaded.
719
+ *
720
+ * The file to read comes from the provenance itself, so there is no way to point this at a file the
721
+ * loaded config does not name.
722
+ *
723
+ * @param input - The glossary provenance from a loaded config.
724
+ * @param deps - Optional file-system override.
725
+ * @returns The glossary as a flat term map.
726
+ *
727
+ * @throws {@link SdkError} `GLOSSARY_NOT_FILE_BACKED`: the config's glossary is inline or absent, so
728
+ * there is no file to read.
729
+ * @throws {@link SdkError} `CONFIG_INVALID`: the glossary file is missing, oversized, not UTF-8, not
730
+ * valid JSON, or not a flat object of string keys to string values.
731
+ */
732
+ declare function readGlossaryFile(input: GlossaryFileInput, deps?: GlossaryFileDeps): Promise<Readonly<Record<string, string>>>;
733
+ /**
734
+ * Adds, replaces, or removes one term in a file-backed glossary, rewriting the JSON file in place.
735
+ * This is the write path behind a glossary editor: one term changes and the rest of the file keeps
736
+ * its order and its indentation.
737
+ *
738
+ * Only a file-backed glossary can be edited. An inline glossary lives inside the config module,
739
+ * which is executable code this function will not rewrite, and no glossary at all has no file to
740
+ * write to; both are refused rather than converted.
741
+ *
742
+ * The whole read-modify-write runs under a project-wide glossary lock, so two concurrent edits are
743
+ * serialized rather than interleaved, and the file itself is replaced atomically. The result is
744
+ * validated to stay within the same size limit {@link loadConfig} enforces when it reads a glossary
745
+ * file back, so an accepted write is always re-readable.
746
+ *
747
+ * @param input - The glossary provenance, the term, and its new translation or `null` to remove it.
748
+ * @param deps - Optional file-system override.
749
+ * @returns The glossary as it now stands on disk.
750
+ *
751
+ * @throws {@link SdkError} `GLOSSARY_NOT_FILE_BACKED`: the config's glossary is inline or absent, so
752
+ * there is no file to write.
753
+ * @throws {@link SdkError} `CONFIG_INVALID`: the term or its translation is blank, the glossary file
754
+ * on disk is missing, oversized, not UTF-8, not valid JSON, or not a flat object of string keys to
755
+ * string values, or the updated glossary would exceed the maximum glossary file size.
756
+ * @throws {@link SdkError} `LOCK_CONTENDED`: the project's glossary write lock could not be acquired
757
+ * before the timeout elapsed.
758
+ * @throws {@link SdkError} `GLOSSARY_UNWRITABLE`: the glossary file could not be written.
759
+ *
760
+ * @example
761
+ * ```ts
762
+ * import { loadConfigWithMeta, updateGlossaryTerm } from "@verbatra/sdk";
763
+ *
764
+ * const loaded = await loadConfigWithMeta({ cwd: process.cwd() });
765
+ * const glossary = await updateGlossaryTerm({
766
+ * glossary: loaded.glossary,
767
+ * cwd: process.cwd(),
768
+ * term: "Verbatra",
769
+ * translation: "Verbatra",
770
+ * });
771
+ *
772
+ * console.log(Object.keys(glossary).length);
773
+ * ```
774
+ */
775
+ declare function updateGlossaryTerm(input: UpdateGlossaryTermInput, deps?: GlossaryFileDeps): Promise<Readonly<Record<string, string>>>;
776
+
685
777
  /** Options for {@link loadConfig} and {@link loadConfigWithMeta}. */
686
778
  interface LoadConfigOptions {
687
779
  /** Directory to search from, and the base for relative paths. Defaults to the process working directory. */
@@ -782,18 +874,25 @@ declare function loadConfig(options?: LoadConfigOptions): Promise<VerbatraConfig
782
874
  * wraps them.
783
875
  *
784
876
  * - `CONFIG_NOT_FOUND`: no config file was found by search, or an explicit `configPath` does not
785
- * exist. Thrown by {@link loadConfig} and {@link loadConfigWithMeta}.
877
+ * exist. Thrown by {@link loadConfig} and {@link loadConfigWithMeta}. {@link doctor} narrows it
878
+ * to the explicit-path case: a config that is only absent from the search is reported as a failed
879
+ * check instead, since reporting that is the command's job.
786
880
  * - `CONFIG_INVALID`: a config was found but is unparseable or fails validation, or its glossary
787
881
  * file could not be resolved or parsed. Thrown by {@link loadConfig} and
788
- * {@link loadConfigWithMeta}. {@link importWorkbook} does not throw it: when a handoff sheet or
789
- * file names a locale that is not a configured target locale, it records this code on that
790
- * locale's {@link LocaleSummary} instead.
882
+ * {@link loadConfigWithMeta}, by {@link readGlossaryFile}, and by {@link updateGlossaryTerm},
883
+ * which additionally throws it for a blank term or translation and for an edit whose result would
884
+ * exceed the glossary file size limit. {@link importWorkbook} does not throw it: when a handoff
885
+ * sheet or file names a locale that is not a configured target locale, it records this code on
886
+ * that locale's {@link LocaleSummary} instead.
791
887
  * - `UNKNOWN_FORMAT`: no adapter is registered for the configured format. Thrown by every entry
792
- * point that selects an adapter, before any file is read.
888
+ * point that selects an adapter, before any file is read. {@link doctor} is the exception: it
889
+ * reports an unresolvable format as a failed `format-adapter` check instead, since reporting that
890
+ * is the command's job.
793
891
  * - `UNKNOWN_LOCALE`: a requested locale is not among the configured target locales. Thrown through
794
- * the shared locale selection by {@link check}, {@link diff}, {@link keyIntegrity},
795
- * {@link lockState}, {@link exportWorkbook}, {@link keyValue}, {@link editEntry}, and
796
- * {@link retranslateEntry}.
892
+ * the shared locale selection by {@link translate}, {@link watch}, {@link check}, {@link diff},
893
+ * {@link keyIntegrity}, {@link lockState}, {@link exportWorkbook}, {@link keyValue},
894
+ * {@link editEntry}, and {@link retranslateEntry}. {@link translate} throws it before anything is
895
+ * read or spent, and {@link watch} once at startup, before any watching begins.
797
896
  * - `UNKNOWN_KEY`: the requested key is not present in the source resource. Thrown by
798
897
  * {@link keyValue}, {@link editEntry}, and {@link retranslateEntry}.
799
898
  * - `PROVIDER_CONSTRUCTION_FAILED`: the provider factory threw. Wraps the provider's own error,
@@ -809,15 +908,24 @@ declare function loadConfig(options?: LoadConfigOptions): Promise<VerbatraConfig
809
908
  * {@link diff}, {@link keyIntegrity}, {@link lockState}, {@link loadLockFile},
810
909
  * {@link exportWorkbook}, {@link importWorkbook}, {@link editEntry}, and
811
910
  * {@link retranslateEntry}.
812
- * - `LOCK_CONTENDED`: a locale's write lock could not be acquired before its timeout elapsed,
813
- * because another process holds it or a killed process left the lock file behind. The message
911
+ * - `LOCK_CONTENDED`: a write lock could not be acquired before its timeout elapsed, because
912
+ * another process holds it or a killed process left the lock file behind. The message
814
913
  * names the lock file's path. Thrown by {@link editEntry} and {@link retranslateEntry}, which
815
- * act on one locale. {@link translate} and {@link importWorkbook} do not throw it: they record it
914
+ * act on one locale, and by {@link updateGlossaryTerm}, which takes the project's glossary lock.
915
+ * {@link translate} and {@link importWorkbook} do not throw it: they record it
816
916
  * on the contended locale's {@link LocaleSummary} and carry on with the other locales.
917
+ * - `GLOSSARY_NOT_FILE_BACKED`: the loaded config's glossary is written inline or absent, so there
918
+ * is no glossary file to read or rewrite. Thrown by {@link readGlossaryFile} and
919
+ * {@link updateGlossaryTerm}, which work on a file-backed glossary alone and never rewrite the
920
+ * config module itself.
921
+ * - `GLOSSARY_UNWRITABLE`: the glossary file could not be written, because it or its directory is
922
+ * read-only, has been removed, or the disk is out of space. Thrown by
923
+ * {@link updateGlossaryTerm}.
817
924
  * - `LOCALE_LAYOUT_INVALID`: the configured `files.pattern` and `files.localeStyle` cannot be
818
925
  * combined, or the style has no valid path spelling for a configured locale. Thrown by
819
926
  * {@link createLocalePathResolver}, and so by every entry point that maps a locale to a path,
820
- * before any file is read and before any provider call.
927
+ * before any file is read and before any provider call. {@link doctor} is the exception: it
928
+ * reports the resolver's failure as a failed `source-file` check instead.
821
929
  * - `LOCALE_PATH_COLLISION`: two configured locales resolve to the same absolute path, which would
822
930
  * make the path-to-locale direction ambiguous and let two locale workers race on one file. Thrown
823
931
  * at the same point as `LOCALE_LAYOUT_INVALID`.
@@ -827,10 +935,16 @@ declare function loadConfig(options?: LoadConfigOptions): Promise<VerbatraConfig
827
935
  * - `CONCURRENCY_BUDGET_CONFLICT`: a live run requested a `concurrency` above 1 while a token
828
936
  * budget is configured. The two are mutually exclusive because concurrency makes the budget's
829
937
  * stop guarantee nondeterministic. A dry run is exempt, since it never consults the budget.
938
+ * - `TARGET_UNWRITABLE`: a target locale file could not be written, because its directory is not
939
+ * writable, does not exist, is read-only, or is out of space. The message names the target file
940
+ * relative to `cwd` and the underlying file-system code, never the internal temporary file the
941
+ * atomic write uses. Thrown by {@link editEntry} and {@link retranslateEntry}, which act on one
942
+ * locale. {@link translate} and {@link importWorkbook} do not throw it: they record it on that
943
+ * locale's {@link LocaleSummary} and carry on with the other locales.
830
944
  * - `LOCALE_FAILED`: never thrown. It is the fallback code recorded on a failed
831
945
  * {@link LocaleSummary} when a per-locale failure carries no code of its own.
832
946
  */
833
- type SdkErrorCode = "CONFIG_NOT_FOUND" | "CONFIG_INVALID" | "UNKNOWN_FORMAT" | "UNKNOWN_LOCALE" | "UNKNOWN_KEY" | "PROVIDER_CONSTRUCTION_FAILED" | "SOURCE_UNREADABLE" | "SOURCE_INVALID" | "LOCK_FILE_INVALID" | "LOCK_CONTENDED" | "LOCALE_LAYOUT_INVALID" | "LOCALE_PATH_COLLISION" | "CONCURRENCY_INVALID" | "CONCURRENCY_BUDGET_CONFLICT" | "LOCALE_FAILED";
947
+ type SdkErrorCode = "CONFIG_NOT_FOUND" | "CONFIG_INVALID" | "UNKNOWN_FORMAT" | "UNKNOWN_LOCALE" | "UNKNOWN_KEY" | "PROVIDER_CONSTRUCTION_FAILED" | "SOURCE_UNREADABLE" | "SOURCE_INVALID" | "LOCK_FILE_INVALID" | "LOCK_CONTENDED" | "GLOSSARY_NOT_FILE_BACKED" | "GLOSSARY_UNWRITABLE" | "LOCALE_LAYOUT_INVALID" | "LOCALE_PATH_COLLISION" | "CONCURRENCY_INVALID" | "CONCURRENCY_BUDGET_CONFLICT" | "TARGET_UNWRITABLE" | "LOCALE_FAILED";
834
948
  /**
835
949
  * The single structured error the SDK throws. Every whole-run failure surfaces as an `SdkError`
836
950
  * carrying a stable {@link SdkErrorCode}; per-locale failures, provider notices, and integrity
@@ -1016,15 +1130,24 @@ interface FormatAdapter {
1016
1130
  */
1017
1131
  validateMessage(value: string): boolean;
1018
1132
  /**
1019
- * Optional branch-aware placeholder comparison for formats with plural/select sub-message structure.
1020
- * When present, callers use it instead of independently extracting each side's placeholders with
1021
- * {@link extractPlaceholders} and diffing the flat lists, since flattening a plural/select value loses
1022
- * which branch a placeholder came from. Absent for every non-ICU adapter, which is compared via
1023
- * `extractPlaceholders` plus a flat multiset check as before.
1133
+ * Optional whole-value placeholder comparison, used by callers instead of independently extracting
1134
+ * each side's placeholders with {@link extractPlaceholders} and diffing the flat lists. Two adapter
1135
+ * families define one, for different reasons:
1136
+ *
1137
+ * - The ICU formats (next-intl and ARB) compare branch by branch, because flattening a
1138
+ * plural/select value loses which branch a placeholder came from.
1139
+ * - The double-brace formats (i18next, ngx-translate, and YAML) add a one-directional
1140
+ * single-brace check on top of their flat multiset comparison: a `{name}`-shaped token present
1141
+ * in the translated value but absent from the source is reported as `extra`. Single-brace text
1142
+ * is ordinary literal content under these formats' default delimiters, so it is never required
1143
+ * to survive a translation, but inventing one is never a legitimate translation either.
1144
+ *
1145
+ * Absent for the remaining adapters, which are compared via `extractPlaceholders` plus a flat
1146
+ * multiset check.
1024
1147
  *
1025
1148
  * @param sourceValue - The source value.
1026
1149
  * @param targetValue - The translated value to check against it.
1027
- * @returns The merged placeholder-integrity result across every branch. Does not throw.
1150
+ * @returns The merged placeholder-integrity result. Does not throw.
1028
1151
  */
1029
1152
  comparePlaceholders?(sourceValue: string, targetValue: string): PlaceholderIntegrityResult;
1030
1153
  }
@@ -1146,9 +1269,10 @@ interface CheckDeps {
1146
1269
  *
1147
1270
  * Use {@link diff} instead when you need the key names rather than the counts.
1148
1271
  *
1149
- * Note that a malformed target locale file surfaces the adapter's own parse error rather than a
1150
- * wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
1151
- * should be ready for an unrecognized error from a target file.
1272
+ * Note that a malformed target locale file surfaces the adapter's own error and code rather than a
1273
+ * wrapped {@link SdkError}, because only source reads are wrapped. Its message names the offending
1274
+ * locale and the resolved path. A caller that maps SDK codes should be ready for an unrecognized
1275
+ * error from a target file.
1152
1276
  *
1153
1277
  * @param input - The config and the optional locale filter.
1154
1278
  * @param deps - Optional adapter registry and file-system overrides.
@@ -1217,9 +1341,10 @@ interface DiffDeps {
1217
1341
  * since the key was last translated rather than merely that the two strings differ. Orphaned keys
1218
1342
  * are reported but never removed here; pruning happens only in {@link translate}.
1219
1343
  *
1220
- * Note that a malformed target locale file surfaces the adapter's own parse error rather than a
1221
- * wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
1222
- * should be ready for an unrecognized error from a target file.
1344
+ * Note that a malformed target locale file surfaces the adapter's own error and code rather than a
1345
+ * wrapped {@link SdkError}, because only source reads are wrapped. Its message names the offending
1346
+ * locale and the resolved path. A caller that maps SDK codes should be ready for an unrecognized
1347
+ * error from a target file.
1223
1348
  *
1224
1349
  * @param input - The config and the optional locale filter.
1225
1350
  * @param deps - Optional adapter registry and file-system overrides.
@@ -1237,6 +1362,115 @@ interface DiffDeps {
1237
1362
  */
1238
1363
  declare function diff(input: DiffInput, deps?: DiffDeps): Promise<DiffSummary>;
1239
1364
 
1365
+ /**
1366
+ * Which project-setup question a {@link DoctorCheck} answers.
1367
+ *
1368
+ * - `config`: a config file was found and passes validation.
1369
+ * - `format-adapter`: the configured `format` resolves to a file adapter.
1370
+ * - `provider`: the configured `provider.id` resolves to a provider factory.
1371
+ * - `api-key`: the environment variable the configured provider reads its key from is set.
1372
+ * - `source-file`: the source locale file exists at its resolved path, is a regular file, and
1373
+ * parses under the configured format.
1374
+ */
1375
+ type DoctorCheckId = "config" | "format-adapter" | "provider" | "api-key" | "source-file";
1376
+ /**
1377
+ * The verdict on one {@link DoctorCheck}. `skipped` is reported only for the checks that need a
1378
+ * loaded config when the `config` check itself failed, so a skipped check is never a problem of its
1379
+ * own.
1380
+ */
1381
+ type DoctorCheckStatus = "pass" | "fail" | "skipped";
1382
+ /** One project-setup question and its verdict. */
1383
+ interface DoctorCheck {
1384
+ /** Which question this check answers. */
1385
+ readonly id: DoctorCheckId;
1386
+ /** A short human-readable name for the check, stable across runs. */
1387
+ readonly title: string;
1388
+ /** The verdict. Only `fail` makes {@link DoctorResult.ok} false. */
1389
+ readonly status: DoctorCheckStatus;
1390
+ /**
1391
+ * Why the check reached its verdict. On a failure this carries the underlying error message
1392
+ * verbatim, so a config validation problem reads exactly as {@link loadConfig} words it. It names
1393
+ * environment variables and paths, never an API key value.
1394
+ */
1395
+ readonly detail: string;
1396
+ }
1397
+ /** The result of {@link doctor}: every check that ran, and one project-wide verdict. */
1398
+ interface DoctorResult {
1399
+ /** True only when no check failed. This is the value a script should branch on. */
1400
+ readonly ok: boolean;
1401
+ /** Every check, always in the same order, one entry per {@link DoctorCheckId}. */
1402
+ readonly checks: readonly DoctorCheck[];
1403
+ }
1404
+ /** Input for {@link doctor}. */
1405
+ interface DoctorInput {
1406
+ /** Directory to search the config from, and the base for locale paths. Defaults to the process working directory. */
1407
+ readonly cwd?: string;
1408
+ /** An explicit config file to validate, bypassing the search. A missing file is an error rather than a failed check. */
1409
+ readonly configPath?: string;
1410
+ }
1411
+ /** Injectable dependencies for {@link doctor}. Every field has a working default. */
1412
+ interface DoctorDeps {
1413
+ /** Format-adapter registry to resolve the configured format against. Defaults to the built-in registry. */
1414
+ readonly adapterRegistry?: AdapterRegistry;
1415
+ /**
1416
+ * File-system port. Threaded into the config loader, so it backs the glossary-file read the
1417
+ * `config` check performs, and used to read and parse the source locale file. Defaults to the
1418
+ * real file system.
1419
+ */
1420
+ readonly fs?: SdkFs;
1421
+ /** Config loader. Defaults to {@link loadConfigWithMeta}. */
1422
+ readonly loadConfig?: (options: LoadConfigOptions) => Promise<LoadedConfig>;
1423
+ }
1424
+ /**
1425
+ * Validates a project's setup and spends nothing: no provider is constructed, no network request is
1426
+ * made, and no file is written. Run it before {@link translate} on a fresh project, or when a run
1427
+ * failed and you want the whole list of problems rather than the first one.
1428
+ *
1429
+ * Five checks run: the config loads and validates, the configured format resolves to an adapter,
1430
+ * the configured provider ID resolves to a factory, the environment variable that provider reads
1431
+ * its API key from is set, and the source locale file can be read. Every check runs even when an
1432
+ * earlier one failed, so one call reports every independent problem. The API key is checked by
1433
+ * variable name only: its value is never read, never returned, and never validated against a
1434
+ * provider.
1435
+ *
1436
+ * The source-file check reads and parses the file rather than only probing for its existence, so a
1437
+ * directory standing in for it, an empty file, and malformed content are all reported here rather
1438
+ * than surfacing later as a whole-run failure of {@link check} or {@link translate}. The failure
1439
+ * detail is the same message those entry points raise. When the configured format resolves to no
1440
+ * adapter there is nothing to parse with, so the check falls back to existence alone and says so.
1441
+ *
1442
+ * The `openai-compatible` provider is the one exception on the key check. It falls back to a
1443
+ * placeholder key, so a missing variable passes unless the config names its own variable through
1444
+ * `provider.options.apiKeyEnvVar`, which then has to be set.
1445
+ *
1446
+ * A target locale file is not checked at all: a missing one is not a problem, because
1447
+ * {@link translate} creates it. The source locale file is checked, because every other entry point
1448
+ * fails on it.
1449
+ *
1450
+ * When the config cannot be loaded the four config-dependent checks report `skipped` rather than a
1451
+ * verdict they could not reach, and {@link DoctorResult.ok} is false because the config check
1452
+ * itself failed.
1453
+ *
1454
+ * @param input - The working directory and an optional explicit config path.
1455
+ * @param deps - Optional adapter registry, file-system, and config-loader overrides.
1456
+ * @returns Every check with its verdict, and the project-wide `ok` verdict.
1457
+ *
1458
+ * @throws {@link SdkError} `CONFIG_NOT_FOUND`: an explicit `configPath` was given and no file
1459
+ * exists there. A config that is merely absent from the search is a failed check instead.
1460
+ *
1461
+ * @example
1462
+ * ```ts
1463
+ * import { doctor } from "@verbatra/sdk";
1464
+ *
1465
+ * const report = await doctor();
1466
+ * for (const check of report.checks) {
1467
+ * console.log(`${check.status}: ${check.title} - ${check.detail}`);
1468
+ * }
1469
+ * process.exitCode = report.ok ? 0 : 1;
1470
+ * ```
1471
+ */
1472
+ declare function doctor(input?: DoctorInput, deps?: DoctorDeps): Promise<DoctorResult>;
1473
+
1240
1474
  /**
1241
1475
  * Why a candidate translation was refused before it could be written. The same gate guards
1242
1476
  * {@link editEntry} and {@link retranslateEntry}, so a hand-typed value and a provider-produced one
@@ -1246,7 +1480,9 @@ declare function diff(input: DiffInput, deps?: DiffDeps): Promise<DiffSummary>;
1246
1480
  * previous translation stays intact.
1247
1481
  *
1248
1482
  * - `placeholder`: the candidate does not carry the same placeholders as the source, so
1249
- * interpolation would break at runtime.
1483
+ * interpolation would break at runtime. For the double-brace formats (i18next, ngx-translate, and
1484
+ * YAML) this also covers a single-brace `{name}`-shaped token the candidate invented and the
1485
+ * source never had, which is a fabrication whichever interpolation delimiters the project uses.
1250
1486
  * - `icu`: the candidate is not a valid ICU message under the configured format's adapter.
1251
1487
  * - `degenerate`: the candidate collapsed into runaway output rather than a translation. Two shapes
1252
1488
  * are detected: the candidate is at least twelve times the length of a source of meaningful
@@ -1311,9 +1547,14 @@ type EditEntryResult = {
1311
1547
  * and feeds the translation memory, which means a later run treats the key as up to date and
1312
1548
  * reuses the edited text rather than paying the provider to translate it again.
1313
1549
  *
1314
- * Note that a malformed target locale file surfaces the adapter's own parse error rather than a
1315
- * wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
1316
- * should be ready for an unrecognized error from a target file.
1550
+ * Note that the target locale file surfaces the adapter's own error and code rather than a wrapped
1551
+ * {@link SdkError}, on the write as well as on the read, because only the source read is wrapped.
1552
+ * A malformed target file fails the read with a message naming the offending locale and the
1553
+ * resolved path. The write raises the adapter's error too, when the entries cannot be represented
1554
+ * in the configured format or the existing destination file cannot be read back to be updated in
1555
+ * place, and that error is re-thrown unchanged so its own code survives for a caller that maps
1556
+ * adapter codes to its own copy. A caller that maps SDK codes should be ready for an unrecognized
1557
+ * error from a target file on either path.
1317
1558
  *
1318
1559
  * @param input - The config, locale, key, and new value.
1319
1560
  * @param deps - Optional adapter registry and file-system overrides.
@@ -1329,8 +1570,14 @@ type EditEntryResult = {
1329
1570
  * @throws {@link SdkError} `UNKNOWN_KEY`: the key is not present in the source resource.
1330
1571
  * @throws {@link SdkError} `LOCK_CONTENDED`: the locale's write lock could not be acquired before
1331
1572
  * the timeout elapsed.
1573
+ * @throws {@link SdkError} `TARGET_UNWRITABLE`: the target locale file could not be written because
1574
+ * of a file-system failure. The message names the target file and the file-system code, never the
1575
+ * internal temporary file.
1332
1576
  * @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
1333
1577
  * unsupported version.
1578
+ * @throws `AdapterError`: the adapter itself refused the target locale file, on the read because it
1579
+ * is malformed or on the write because the entries cannot be represented in the configured format.
1580
+ * Its own code is preserved rather than remapped onto an {@link SdkErrorCode}.
1334
1581
  */
1335
1582
  declare function editEntry(input: EditEntryInput, deps?: EditEntryDeps): Promise<EditEntryResult>;
1336
1583
 
@@ -1387,9 +1634,10 @@ interface KeyIntegrityDeps {
1387
1634
  * {@link retranslateEntry} enforce at write time, and the data behind a review dashboard's
1388
1635
  * per-key integrity indicator.
1389
1636
  *
1390
- * Note that a malformed target locale file surfaces the adapter's own parse error rather than a
1391
- * wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
1392
- * should be ready for an unrecognized error from a target file.
1637
+ * Note that a malformed target locale file surfaces the adapter's own error and code rather than a
1638
+ * wrapped {@link SdkError}, because only source reads are wrapped. Its message names the offending
1639
+ * locale and the resolved path. A caller that maps SDK codes should be ready for an unrecognized
1640
+ * error from a target file.
1393
1641
  *
1394
1642
  * @param input - The config and the optional locale and key filters.
1395
1643
  * @param deps - Optional adapter registry and file-system overrides.
@@ -1441,9 +1689,10 @@ interface KeyValueResult {
1441
1689
  * cannot be edited or retranslated, while a key absent from the target is normal and simply comes
1442
1690
  * back with no `target`.
1443
1691
  *
1444
- * Note that a malformed target locale file surfaces the adapter's own parse error rather than a
1445
- * wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
1446
- * should be ready for an unrecognized error from a target file.
1692
+ * Note that a malformed target locale file surfaces the adapter's own error and code rather than a
1693
+ * wrapped {@link SdkError}, because only source reads are wrapped. Its message names the offending
1694
+ * locale and the resolved path. A caller that maps SDK codes should be ready for an unrecognized
1695
+ * error from a target file.
1447
1696
  *
1448
1697
  * @param input - The config, locale, and key to read.
1449
1698
  * @param deps - Optional adapter registry and file-system overrides.
@@ -1499,8 +1748,9 @@ interface ReadLocaleFileSnapshotDeps {
1499
1748
  * watcher can start before the first translation run has written anything.
1500
1749
  *
1501
1750
  * Note that this entry point does not validate the locale against the configured targets, and that
1502
- * a malformed target file surfaces the adapter's own parse error rather than a wrapped
1503
- * {@link SdkError}, because no source-file contract is being asserted here.
1751
+ * a malformed target file surfaces the adapter's own error and code rather than a wrapped
1752
+ * {@link SdkError}, because no source-file contract is being asserted here. Its message names the
1753
+ * offending locale and the resolved path.
1504
1754
  *
1505
1755
  * @param input - The config and the locale to snapshot.
1506
1756
  * @param deps - Optional adapter registry and file-system overrides.
@@ -1591,9 +1841,10 @@ interface LockStateDeps {
1591
1841
  * When no lock-file exists the call returns `exists: false` rather than throwing, and does no
1592
1842
  * further reading.
1593
1843
  *
1594
- * Note that a malformed target locale file surfaces the adapter's own parse error rather than a
1595
- * wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
1596
- * should be ready for an unrecognized error from a target file.
1844
+ * Note that a malformed target locale file surfaces the adapter's own error and code rather than a
1845
+ * wrapped {@link SdkError}, because only source reads are wrapped. Its message names the offending
1846
+ * locale and the resolved path. A caller that maps SDK codes should be ready for an unrecognized
1847
+ * error from a target file.
1597
1848
  *
1598
1849
  * @param input - The config and the optional locale filter.
1599
1850
  * @param deps - Optional adapter registry and file-system overrides.
@@ -1681,9 +1932,14 @@ type RetranslateEntryResult = {
1681
1932
  * baseline and feeds the translation memory, so a later {@link translate} run sees the key as up
1682
1933
  * to date.
1683
1934
  *
1684
- * Note that a malformed target locale file surfaces the adapter's own parse error rather than a
1685
- * wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
1686
- * should be ready for an unrecognized error from a target file.
1935
+ * Note that the target locale file surfaces the adapter's own error and code rather than a wrapped
1936
+ * {@link SdkError}, on the write as well as on the read, because only the source read is wrapped.
1937
+ * A malformed target file fails the read with a message naming the offending locale and the
1938
+ * resolved path. The write raises the adapter's error too, when the entries cannot be represented
1939
+ * in the configured format or the existing destination file cannot be read back to be updated in
1940
+ * place, and that error is re-thrown unchanged so its own code survives for a caller that maps
1941
+ * adapter codes to its own copy. A caller that maps SDK codes should be ready for an unrecognized
1942
+ * error from a target file on either path.
1687
1943
  *
1688
1944
  * @param input - The config, locale, and key to retranslate.
1689
1945
  * @param deps - Optional adapter registry, provider factory, and file-system overrides.
@@ -1701,8 +1957,14 @@ type RetranslateEntryResult = {
1701
1957
  * most often because its API key environment variable is unset.
1702
1958
  * @throws {@link SdkError} `LOCK_CONTENDED`: the locale's write lock could not be acquired before
1703
1959
  * the timeout elapsed.
1960
+ * @throws {@link SdkError} `TARGET_UNWRITABLE`: the target locale file could not be written because
1961
+ * of a file-system failure. The message names the target file and the file-system code, never the
1962
+ * internal temporary file.
1704
1963
  * @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
1705
1964
  * unsupported version.
1965
+ * @throws `AdapterError`: the adapter itself refused the target locale file, on the read because it
1966
+ * is malformed or on the write because the entries cannot be represented in the configured format.
1967
+ * Its own code is preserved rather than remapped onto an {@link SdkErrorCode}.
1706
1968
  * @throws `ProviderError` `INVALID_RESPONSE`: the provider returned no value for the key. Provider
1707
1969
  * transport and rate-limit failures propagate as `ProviderError` too, since a single-key call has
1708
1970
  * no per-locale summary to record them on.
@@ -1884,11 +2146,21 @@ interface LocaleSummary {
1884
2146
  interface RunSummary {
1885
2147
  /** True when the run computed everything but wrote nothing and called no provider. */
1886
2148
  readonly dryRun: boolean;
1887
- /** The full per-locale account, in configured target order. */
2149
+ /**
2150
+ * The full per-locale account. A {@link translate} or {@link watch} run reports them in
2151
+ * configured target order, whatever order a `locales` subset was given in and whatever order
2152
+ * concurrent locales finished in. An {@link importWorkbook} run does not: it reports them in the
2153
+ * order the handoff yielded them, then appends the configured locales the handoff had nothing
2154
+ * for. Match an entry on its `locale` rather than on its position.
2155
+ */
1888
2156
  readonly locales: readonly LocaleSummary[];
1889
2157
  /** Names of the locales whose status is `succeeded`. */
1890
2158
  readonly succeeded: readonly string[];
1891
- /** Names of the locales whose status is `partial`. */
2159
+ /**
2160
+ * Names of the locales whose status is `partial`: written to disk, but with keys still missing.
2161
+ * Check this alongside `failed` before treating a run as clean. The CLI exits `1` on a partial
2162
+ * locale for that reason.
2163
+ */
1892
2164
  readonly partial: readonly string[];
1893
2165
  /** Names of the locales whose status is `failed`. Check this before treating a run as clean. */
1894
2166
  readonly failed: readonly string[];
@@ -1965,8 +2237,9 @@ type RunStatusResult = {
1965
2237
  * anything.
1966
2238
  *
1967
2239
  * The read is deliberately total: a missing, oversized, unparseable, schema-invalid, or
1968
- * wrong-version file all report `available: false` rather than throwing, because stale local status
1969
- * should never break the tool reading it. This call throws nothing.
2240
+ * wrong-version file all report `available: false` rather than throwing, and so does an injected
2241
+ * `deps.fs` whose read rejects, because stale or unreachable local status should never break the
2242
+ * tool reading it. This call throws nothing.
1970
2243
  *
1971
2244
  * @param input - The optional working directory.
1972
2245
  * @param deps - Optional file-system override.
@@ -2075,6 +2348,18 @@ interface TranslateInput {
2075
2348
  readonly config: VerbatraConfig;
2076
2349
  /** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
2077
2350
  readonly cwd?: string;
2351
+ /**
2352
+ * Restrict the run to a subset of the configured target locales, which is how you translate one
2353
+ * locale at a time against a rate-limited provider. Defaults to every configured target. The
2354
+ * result keeps the configured order whatever order the subset is given in, and any locale named
2355
+ * here that is not a configured target fails the whole run with `UNKNOWN_LOCALE` before anything
2356
+ * is read or spent. An explicit empty array selects no locale at all rather than every locale.
2357
+ *
2358
+ * Locales left out are untouched: their files, their lock entries, and their translation-memory
2359
+ * entries all stay as they were. The run-status file always describes the most recent run alone,
2360
+ * so a subset run narrows it to that subset.
2361
+ */
2362
+ readonly locales?: readonly string[];
2078
2363
  /**
2079
2364
  * Compute the whole run but write nothing and call no provider. The returned
2080
2365
  * {@link RunSummary} reports what would have happened, which makes this safe to run in CI to
@@ -2122,7 +2407,8 @@ interface TranslateDeps {
2122
2407
  readonly fs?: SdkFs;
2123
2408
  }
2124
2409
  /**
2125
- * Runs the one-shot translation flow over every configured target locale: read the source, diff
2410
+ * Runs the one-shot translation flow over every configured target locale, or over the subset named
2411
+ * by `locales`: read the source, diff
2126
2412
  * each locale against the lock-file baseline, translate what is missing or stale, verify placeholder
2127
2413
  * and ICU integrity, write the locale files, and update the lock-file and translation memory.
2128
2414
  *
@@ -2132,8 +2418,10 @@ interface TranslateDeps {
2132
2418
  * {@link LocaleSummary} and the other locales continue, so one unreachable provider or one
2133
2419
  * unwritable file never discards work that succeeded. A locale whose write lock stays contended is
2134
2420
  * one of these per-locale failures: it is recorded with code `LOCK_CONTENDED` on that locale's
2135
- * summary rather than thrown. Callers should therefore inspect {@link RunSummary.failed} rather
2136
- * than relying on a thrown error to detect trouble.
2421
+ * summary rather than thrown. Callers should therefore inspect {@link RunSummary.failed} and
2422
+ * {@link RunSummary.partial} rather than relying on a thrown error to detect trouble. A partial
2423
+ * locale is a locale whose file was written with some keys missing, so treating it as clean would
2424
+ * ship an incomplete translation; the CLI exits `1` for it exactly as it does for a failed one.
2137
2425
  *
2138
2426
  * A corrupt lock-file is the one exception. On a live run each locale reads the lock-file inside
2139
2427
  * its own write lock, so `LOCK_FILE_INVALID` escapes as a thrown error even after earlier locales
@@ -2150,6 +2438,8 @@ interface TranslateDeps {
2150
2438
  * @param deps - Optional adapter registry, provider factory, and file-system overrides.
2151
2439
  * @returns The per-locale account of the run, including usage and budget.
2152
2440
  *
2441
+ * @throws {@link SdkError} `UNKNOWN_LOCALE`: `locales` names a locale that is not a configured
2442
+ * target. Thrown before anything is read, written, or spent.
2153
2443
  * @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
2154
2444
  * @throws {@link SdkError} `CONCURRENCY_INVALID`: `concurrency` is not an integer of at least 1.
2155
2445
  * @throws {@link SdkError} `CONCURRENCY_BUDGET_CONFLICT`: a live run combined a `concurrency` above
@@ -2180,9 +2470,9 @@ interface TranslateDeps {
2180
2470
  * },
2181
2471
  * });
2182
2472
  *
2183
- * if (summary.failed.length > 0) {
2184
- * for (const locale of summary.locales.filter((entry) => entry.status === "failed")) {
2185
- * console.error(`${locale.locale}: ${locale.error?.message}`);
2473
+ * if (summary.failed.length > 0 || summary.partial.length > 0) {
2474
+ * for (const locale of summary.locales.filter((entry) => entry.status !== "succeeded")) {
2475
+ * console.error(`${locale.locale}: ${locale.status} ${locale.error?.message ?? ""}`);
2186
2476
  * }
2187
2477
  * process.exitCode = 1;
2188
2478
  * }
@@ -2280,9 +2570,14 @@ interface ExportWorkbookResult {
2280
2570
  * through the same diff, lock, and integrity checks. It writes only the handoff file and never
2281
2571
  * touches the locale files or the lock-file.
2282
2572
  *
2283
- * Note that a malformed target locale file surfaces the adapter's own parse error rather than a
2284
- * wrapped {@link SdkError}, because only source reads are wrapped. A caller that maps SDK codes
2285
- * should be ready for an unrecognized error from a target file.
2573
+ * Note that a malformed target locale file surfaces the adapter's own error and code rather than a
2574
+ * wrapped {@link SdkError}, because only source reads are wrapped. Its message names the offending
2575
+ * locale and the resolved path. A caller that maps SDK codes should be ready for an unrecognized
2576
+ * error from a target file.
2577
+ *
2578
+ * Writing the handoff itself is unwrapped in the same way. It is not a locale file, so a failure to
2579
+ * create the output directory or to write the file does not become `TARGET_UNWRITABLE`; the
2580
+ * underlying file-system error propagates as it is.
2286
2581
  *
2287
2582
  * @param input - The config, output path, locale filter, and handoff format.
2288
2583
  * @param deps - Optional adapter registry and file-system overrides.
@@ -2297,6 +2592,10 @@ interface ExportWorkbookResult {
2297
2592
  * @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
2298
2593
  * unsupported version.
2299
2594
  * @throws {@link SdkError} `UNKNOWN_LOCALE`: a requested locale is not a configured target locale.
2595
+ * @throws The underlying file-system error, unwrapped, when the handoff could not be written: `out`
2596
+ * names a directory that does not exist or is not writable, or the device is full. Branch on the
2597
+ * Node `code`, such as `ENOENT`, rather than on the message, which can name the internal temporary
2598
+ * file the atomic write uses.
2300
2599
  */
2301
2600
  declare function exportWorkbook(input: ExportWorkbookInput, deps?: ExportWorkbookDeps): Promise<ExportWorkbookResult>;
2302
2601
 
@@ -2346,7 +2645,10 @@ interface ImportWorkbookDeps {
2346
2645
  * that is not configured is contained the same way: that locale fails with `CONFIG_INVALID` on its
2347
2646
  * own {@link LocaleSummary}, so nothing is written to an unmanaged path and the configured locales
2348
2647
  * still import. Once the handoff has been read, every per-locale failure is isolated this way, so
2349
- * callers should inspect {@link RunSummary.failed} rather than relying on a thrown error.
2648
+ * callers should inspect {@link RunSummary.failed} and {@link RunSummary.partial} rather than
2649
+ * relying on a thrown error. A corrupt lock-file is the one exception, because it is a single
2650
+ * shared file rather than a per-locale one: it aborts the whole run even when it is discovered
2651
+ * after a locale has been applied, so the locales still to come are not written at all.
2350
2652
  *
2351
2653
  * @param input - The config and the handoff path, format, and dry-run flag.
2352
2654
  * @param deps - Optional adapter registry and file-system overrides.
@@ -2361,7 +2663,8 @@ interface ImportWorkbookDeps {
2361
2663
  * cannot be combined, or a configured locale has no valid path spelling under that style.
2362
2664
  * @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
2363
2665
  * @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is corrupt, oversized, or at an
2364
- * unsupported version. Read once before any locale is applied.
2666
+ * unsupported version. Read before any locale is applied, and re-read as each locale's entries are
2667
+ * recorded, so a lock-file that turns corrupt mid-run aborts the run rather than failing one locale.
2365
2668
  */
2366
2669
  declare function importWorkbook(input: ImportWorkbookInput, deps?: ImportWorkbookDeps): Promise<RunSummary>;
2367
2670
 
@@ -2576,6 +2879,12 @@ interface WatchInput {
2576
2879
  readonly config: VerbatraConfig;
2577
2880
  /** Directory the `files.pattern` is resolved against. Defaults to the process working directory. */
2578
2881
  readonly cwd?: string;
2882
+ /**
2883
+ * Restrict every run of the session to a subset of the configured target locales. Validated once
2884
+ * when watching starts, so an unconfigured locale throws `UNKNOWN_LOCALE` before any watching
2885
+ * begins rather than failing on each run. An explicit empty array selects no locale at all.
2886
+ */
2887
+ readonly locales?: readonly string[];
2579
2888
  /** How long to coalesce rapid source changes before running. Defaults to 300 milliseconds. */
2580
2889
  readonly debounceMs?: number;
2581
2890
  /**
@@ -2624,7 +2933,8 @@ interface WatchController {
2624
2933
  * The returned promise resolves as soon as watching is established, not when translation finishes,
2625
2934
  * so every run outcome arrives through `onRun`. A run that throws is delivered as a failed
2626
2935
  * {@link WatchRunResult} rather than escaping the session, because a watcher that died on the first
2627
- * bad save would be useless. Only startup problems, validated before any watching begins, throw.
2936
+ * bad save would be useless. Only startup problems throw: the inputs validated before any watching
2937
+ * begins, and a failure to construct the watcher itself.
2628
2938
  *
2629
2939
  * Rapid saves are coalesced by `debounceMs`, and runs never overlap, so an editor writing a file
2630
2940
  * several times in a moment produces one run rather than a queue of them.
@@ -2635,6 +2945,8 @@ interface WatchController {
2635
2945
  * @param deps - Optional adapter registry, provider factory, file-system, watcher, and runner overrides.
2636
2946
  * @returns A controller that stops the session.
2637
2947
  *
2948
+ * @throws {@link SdkError} `UNKNOWN_LOCALE`: `locales` names a locale that is not a configured
2949
+ * target. Thrown once at startup, before any watching begins.
2638
2950
  * @throws {@link SdkError} `CONCURRENCY_INVALID`: `concurrency` is not an integer of at least 1.
2639
2951
  * @throws {@link SdkError} `CONCURRENCY_BUDGET_CONFLICT`: `concurrency` above 1 was combined with a
2640
2952
  * configured token budget.
@@ -2643,6 +2955,9 @@ interface WatchController {
2643
2955
  * @throws {@link SdkError} `LOCALE_PATH_COLLISION`: two configured locales resolve to the same path.
2644
2956
  * @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist when watching
2645
2957
  * starts.
2958
+ * @throws Whatever the watcher factory raised, unwrapped, when it could not build a watcher over
2959
+ * the source file. It is not wrapped as an {@link SdkError}. No run has started at that point, so
2960
+ * nothing is watched and `onRun` is never called.
2646
2961
  *
2647
2962
  * @example
2648
2963
  * ```ts
@@ -2665,4 +2980,4 @@ interface WatchController {
2665
2980
  */
2666
2981
  declare function watch(input: WatchInput, deps?: WatchDeps): Promise<WatchController>;
2667
2982
 
2668
- export { type BudgetBehavior, CACHE_FILE_NAME, type CheckDeps, type CheckInput, type CheckSummary, type ConfigSource, type CreateProvider, type CreateWatcher, DEFAULT_DELIMITED_PATH, DEFAULT_EXCHANGE_FORMAT, DEFAULT_WORKBOOK_PATH, type DiffDeps, type DiffInput, type DiffSummary, type DuplicateKeyReport, EXCHANGE_FORMATS, type EditEntryDeps, type EditEntryInput, type EditEntryResult, type ExchangeFormat, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type GlossaryProvenance, type ImportWorkbookDeps, type ImportWorkbookInput, type IntegrityGateReason, type KeyIntegrityDeps, type KeyIntegrityEntry, type KeyIntegrityInput, type KeyValueDeps, type KeyValueInput, type KeyValueResult, LOCK_FILE_NAME, type LoadConfigOptions, type LoadLockFileDeps, type LoadLockFileInput, type LoadedConfig, type LocaleCheckSummary, type LocaleDiff, type LocaleFileSnapshot, type LocaleFinishedEvent, type LocaleKeyIntegrity, type LocaleNotice, type LocalePathResolver, type LocalePathResolverConfig, type LocaleSnapshotDelta, type LocaleStartedEvent, type LocaleStyle, type LocaleSummary, type LockFile, type LockHolder, type LockLocaleState, type LockStateDeps, type LockStateInput, type LockStateResult, type LockWaitEvent, type LockWaitListener, type MalformedRowReport, type NeedsReviewEntry, type ProgressEvent, type ProgressListener, type ProviderConfig, type ProviderId, type ReadLocaleFileSnapshotDeps, type ReadLocaleFileSnapshotInput, type RetranslateEntryDeps, type RetranslateEntryInput, type RetranslateEntryResult, type ReviewReasonCode, type RunBudget, type RunFinishedEvent, type RunStatusDeps, type RunStatusFile, type RunStatusInput, type RunStatusLocale, type RunStatusResult, type RunSummary, type RunTranslate, type ScaffoldableProviderId, SdkError, type SdkErrorCode, type SdkFs, type SdkNotice, type SdkNoticeCode, type SubBatchProgressEvent, type SupportedFormat$1 as SupportedFormat, type TranslateDeps, type TranslateInput, type TranslationMemory, type UsageSummary, type VerbatraConfig, type VerbatraConfigInput, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, check, createLocalePathResolver, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate, verbatraConfigSchema, watch };
2983
+ export { type BudgetBehavior, CACHE_FILE_NAME, type CheckDeps, type CheckInput, type CheckSummary, type ConfigSource, type CreateProvider, type CreateWatcher, DEFAULT_DELIMITED_PATH, DEFAULT_EXCHANGE_FORMAT, DEFAULT_WORKBOOK_PATH, type DiffDeps, type DiffInput, type DiffSummary, type DoctorCheck, type DoctorCheckId, type DoctorCheckStatus, type DoctorDeps, type DoctorInput, type DoctorResult, type DuplicateKeyReport, EXCHANGE_FORMATS, type EditEntryDeps, type EditEntryInput, type EditEntryResult, type ExchangeFormat, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type GlossaryFileDeps, type GlossaryFileInput, type GlossaryProvenance, type ImportWorkbookDeps, type ImportWorkbookInput, type IntegrityGateReason, type KeyIntegrityDeps, type KeyIntegrityEntry, type KeyIntegrityInput, type KeyValueDeps, type KeyValueInput, type KeyValueResult, LOCK_FILE_NAME, type LoadConfigOptions, type LoadLockFileDeps, type LoadLockFileInput, type LoadedConfig, type LocaleCheckSummary, type LocaleDiff, type LocaleFileSnapshot, type LocaleFinishedEvent, type LocaleKeyIntegrity, type LocaleNotice, type LocalePathResolver, type LocalePathResolverConfig, type LocaleSnapshotDelta, type LocaleStartedEvent, type LocaleStyle, type LocaleSummary, type LockFile, type LockHolder, type LockLocaleState, type LockStateDeps, type LockStateInput, type LockStateResult, type LockWaitEvent, type LockWaitListener, type MalformedRowReport, type NeedsReviewEntry, type ProgressEvent, type ProgressListener, type ProviderConfig, type ProviderId, type ReadLocaleFileSnapshotDeps, type ReadLocaleFileSnapshotInput, type RetranslateEntryDeps, type RetranslateEntryInput, type RetranslateEntryResult, type ReviewReasonCode, type RunBudget, type RunFinishedEvent, type RunStatusDeps, type RunStatusFile, type RunStatusInput, type RunStatusLocale, type RunStatusResult, type RunSummary, type RunTranslate, type ScaffoldableProviderId, SdkError, type SdkErrorCode, type SdkFs, type SdkNotice, type SdkNoticeCode, type SubBatchProgressEvent, type SupportedFormat$1 as SupportedFormat, type TranslateDeps, type TranslateInput, type TranslationMemory, type UpdateGlossaryTermInput, type UsageSummary, type VerbatraConfig, type VerbatraConfigInput, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, check, createLocalePathResolver, defineConfig, diff, diffLocaleSnapshots, doctor, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readGlossaryFile, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate, updateGlossaryTerm, verbatraConfigSchema, watch };