@verbatra/sdk 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/mariokreitz/verbatra/main/.github/assets/verbatra-mark.png" alt="verbatra logo, a glowing V mark on a dark square" width="96" height="96" />
2
+ <img src="https://raw.githubusercontent.com/verbatra/verbatra/main/.github/assets/verbatra-mark.png" alt="verbatra logo, a glowing V mark on a dark square" width="96" height="96" />
3
3
  </p>
4
4
 
5
5
  <h1 align="center">@verbatra/sdk</h1>
@@ -10,14 +10,14 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://www.npmjs.com/package/@verbatra/sdk"><img src="https://img.shields.io/npm/v/@verbatra/sdk?label=%40verbatra%2Fsdk" alt="@verbatra/sdk npm version" /></a>
13
- <a href="https://github.com/mariokreitz/verbatra/actions/workflows/ci.yml"><img src="https://github.com/mariokreitz/verbatra/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI" /></a>
14
- <a href="https://codecov.io/gh/mariokreitz/verbatra"><img src="https://codecov.io/gh/mariokreitz/verbatra/graph/badge.svg" alt="Coverage" /></a>
15
- <a href="https://github.com/mariokreitz/verbatra/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT" /></a>
13
+ <a href="https://github.com/verbatra/verbatra/actions/workflows/ci.yml"><img src="https://github.com/verbatra/verbatra/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI" /></a>
14
+ <a href="https://codecov.io/gh/verbatra/verbatra"><img src="https://codecov.io/gh/verbatra/verbatra/graph/badge.svg" alt="Coverage" /></a>
15
+ <a href="https://github.com/verbatra/verbatra/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT" /></a>
16
16
  </p>
17
17
 
18
18
  ## Description
19
19
 
20
- `@verbatra/sdk` is the engine behind verbatra: load and validate a config, run the one-shot translate flow over every target locale, watch the source and re-translate on each change, check or diff your locales without writing, or export and import an Excel workbook for manual translation. The [`@verbatra/cli`](https://github.com/mariokreitz/verbatra/tree/main/packages/cli) command is a thin wrapper over this package.
20
+ `@verbatra/sdk` is the engine behind verbatra: load and validate a config, run the one-shot translate flow over every target locale, watch the source and re-translate on each change, check or diff your locales without writing, validate the whole project setup before you spend anything, or export and import a translator handoff for manual translation. The [`@verbatra/cli`](https://github.com/verbatra/verbatra/tree/main/packages/cli) command is a thin wrapper over this package.
21
21
 
22
22
  ## Requirements
23
23
 
@@ -89,13 +89,13 @@ Discovers and validates the configuration. With no arguments it searches upward
89
89
 
90
90
  ### `translate(input): Promise<RunSummary>`
91
91
 
92
- Runs the one-shot read, diff, translate, write flow over every target locale. `input` is a `TranslateInput`: `{ config, cwd?, dryRun?, prune?, generatePlurals?, cache?, concurrency?, lockAcquireTimeoutMs?, onProgress?, onLockWait? }`. With `dryRun: true` it reads, diffs, and reports without calling the provider or writing anything. `prune` and `generatePlurals` each override the matching config option for this run.
92
+ Runs the one-shot read, diff, translate, write flow over every target locale. `input` is a `TranslateInput`: `{ config, cwd?, locales?, dryRun?, prune?, generatePlurals?, cache?, concurrency?, lockAcquireTimeoutMs?, onProgress?, onLockWait? }`. With `dryRun: true` it reads, diffs, and reports without calling the provider or writing anything. `locales` narrows the run to a subset of the configured target locales, exactly as it does on `check` and `diff`, and a locale that is not configured raises `UNKNOWN_LOCALE` before anything is read or spent. `prune` and `generatePlurals` each override the matching config option for this run.
93
93
 
94
94
  `cache` (on by default, and ignored on a dry run) toggles the local content-addressed translation-memory cache in `verbatra.cache.json`: a key whose source content is unchanged, including under a renamed key or shared with another key, is served from the cache instead of being re-sent to the provider. A cached value is re-checked against that key's own current source first, and one that fails the gate falls through to the provider rather than being written. `concurrency` (defaults to 1, strictly serial) is how many target locales may run at once; it must be an integer of at least 1, and on a live run a value above 1 is refused when the config sets `maxTokens`, because concurrent locales would overshoot the budget nondeterministically. `lockAcquireTimeoutMs` overrides how long a locale's write lock keeps retrying before it fails. `onProgress` receives a structured event once per locale before and after it runs, once per provider sub-batch, and once when the locale loop ends; `onLockWait` fires while a locale's write lock is blocked on another process holding it. The SDK writes to no stream, so these callbacks are the only progress signal.
95
95
 
96
96
  Resolves to a `RunSummary`: `dryRun`, `locales`, `succeeded`, `partial`, `failed`, plus `usage` when any provider call reported tokens and `budget` when `maxTokens` is configured. A locale's `status` is `"succeeded"` when nothing was withheld (a no-op with no candidate keys included), `"partial"` when it accepted at least one key and withheld at least one, and `"failed"` when it withheld keys and accepted none, or threw. Each `LocaleSummary` carries `locale`, `status`, `translated`, `unchanged`, `orphaned`, `pruned`, `invalidIcuSource`, `cacheHits`, `integrityMismatches`, `providerFailures`, `budgetWithheld`, `generated`, `notices`, `needsReview`, `unfilled`, `malformedRows`, `duplicateKeys`, an optional `usage`, and an optional `error` on a locale that threw. `cacheHits` are keys served from the translation-memory cache rather than the provider. `integrityMismatches` is a translation that came back and was withheld by verbatra's integrity gate, whose rejection reasons are the exported `IntegrityGateReason` union. `providerFailures` is a key withheld because nothing was translated for it (the provider call failed, or the response was still missing that key), with any secret-free failure code and message reported in `notices`. `budgetWithheld` is a candidate never sent because a `maxTokens` budget in `"stop"` mode had already tripped. `needsReview` flags accepted keys the review heuristics want a human to look at, and never withholds anything. `unfilled`, `malformedRows`, and `duplicateKeys` are populated only by `importWorkbook`, which returns this same shape. Every withheld key keeps its prior lock hash and is retried next run.
97
97
 
98
- Whole-run failures throw an `SdkError`: an unknown format, provider construction (including a missing API key), an unreadable or invalid source file, a corrupt lock file, an invalid `concurrency`, or the concurrency-and-budget conflict above. A per-locale failure never throws; it is isolated on that locale's summary. On a non-dry run the flow also writes `.verbatra-local/run-status.json` (best-effort, read back through `runStatus`).
98
+ Whole-run failures throw an `SdkError`: an unknown format, a `locales` entry that is not a configured target, provider construction (including a missing API key), an unreadable or invalid source file, a corrupt lock file, an invalid `concurrency`, or the concurrency-and-budget conflict above. A per-locale failure never throws; it is isolated on that locale's summary. On a non-dry run the flow also writes `.verbatra-local/run-status.json` (best-effort, read back through `runStatus`).
99
99
 
100
100
  ```ts
101
101
  const preview = await translate({ config, dryRun: true });
@@ -103,9 +103,9 @@ const preview = await translate({ config, dryRun: true });
103
103
 
104
104
  ### `watch(input): Promise<WatchController>`
105
105
 
106
- Watches the source file and re-runs the translate flow on each debounced change. `input` is `{ config, cwd?, debounceMs?, onRun, cache?, concurrency?, lockAcquireTimeoutMs?, onLockWait?, onProgress? }`; `debounceMs` defaults to 300, and the last five are passed straight through to every run. One run starts immediately at startup, before any change arrives. Runs are serialized, so changes during a run collapse into a single follow-up.
106
+ Watches the source file and re-runs the translate flow on each debounced change. `input` is `{ config, cwd?, locales?, debounceMs?, onRun, cache?, concurrency?, lockAcquireTimeoutMs?, onLockWait?, onProgress? }`; `debounceMs` defaults to 300, `locales` narrows every run of the session to a subset of the configured target locales and is validated once at startup rather than per cycle, and the last five are passed straight through to every run. One run starts immediately at startup, before any change arrives. Runs are serialized, so changes during a run collapse into a single follow-up.
107
107
 
108
- `onRun` receives a `WatchRunResult` per run: `{ status: "succeeded", summary }` or `{ status: "failed", error }` with a secret-free `{ code, message }`, so a failing run is reported and watching continues. `watch` itself throws only at startup: `CONCURRENCY_INVALID` or `CONCURRENCY_BUDGET_CONFLICT` for a `concurrency` no cycle could honor (resolved once, before the watcher exists, rather than failing every cycle), and `SOURCE_UNREADABLE` when the source locale file is absent. Resolves to a `WatchController` whose `stop()` closes the watcher and awaits the in-flight run.
108
+ `onRun` receives a `WatchRunResult` per run: `{ status: "succeeded", summary }` or `{ status: "failed", error }` with a secret-free `{ code, message }`, so a failing run is reported and watching continues. `watch` itself throws only at startup: `UNKNOWN_LOCALE` for a `locales` entry that is not a configured target, `CONCURRENCY_INVALID` or `CONCURRENCY_BUDGET_CONFLICT` for a `concurrency` no cycle could honor (resolved once, before the watcher exists, rather than failing every cycle), and `SOURCE_UNREADABLE` when the source locale file is absent. Resolves to a `WatchController` whose `stop()` closes the watcher and awaits the in-flight run.
109
109
 
110
110
  ```ts
111
111
  import { loadConfig, watch } from "@verbatra/sdk";
@@ -146,13 +146,33 @@ const config = await loadConfig();
146
146
  const summary = await diff({ config });
147
147
  ```
148
148
 
149
+ ### `doctor(input?): Promise<DoctorResult>`
150
+
151
+ Validates the project setup and spends nothing: no provider is constructed, no network request is made, no file is written, and no API key value is ever read. `input` is `{ cwd?, configPath? }`, and it is optional as a whole because `doctor` loads the config itself, so a project with no config at all still gets a report rather than a thrown error. Five checks run, one per `DoctorCheckId`: `"config"` (a config was found and validates), `"format-adapter"` (the configured `format` resolves to an adapter), `"provider"` (the configured `provider.id` resolves to a factory), `"api-key"` (the environment variable that provider reads its key from is set), and `"source-file"` (the source locale file exists at its resolved path). Every check runs even when an earlier one failed, so one call reports every independent problem; when the config itself cannot be loaded, the four checks that depend on it report `"skipped"` instead of a verdict they could not reach. Resolves to a `DoctorResult` whose `ok` is true only when no check failed, and whose `checks` carry an `id`, a stable `title`, a `status` of `"pass"`, `"fail"`, or `"skipped"`, and a `detail` naming the variable or path behind the verdict but never a key value. Throws `CONFIG_NOT_FOUND` only for an explicit `configPath` that does not exist; a config that is merely absent from the search is a failed check instead.
152
+
153
+ ```ts
154
+ import { doctor } from "@verbatra/sdk";
155
+
156
+ const report = await doctor();
157
+
158
+ for (const entry of report.checks) {
159
+ console.log(`${entry.status}: ${entry.title} - ${entry.detail}`);
160
+ }
161
+
162
+ process.exitCode = report.ok ? 0 : 1;
163
+ ```
164
+
149
165
  ### `exportWorkbook(input): Promise<ExportWorkbookResult>`
150
166
 
151
- Exports the strings that need translating into a styled Excel workbook for a human translator. `input` is `{ config, cwd?, out?, locales?, includeUnchanged? }`. By default it writes the missing and changed strings for every target locale to `verbatra-translations.xlsx`; `out` overrides the path, `locales` narrows which target locales are exported, and `includeUnchanged: true` also exports already up-to-date strings. No provider is called and no lock file is written. Resolves to an `ExportWorkbookResult` with the absolute `path` written and a per-locale row count.
167
+ Exports the strings that need translating into a handoff for a human translator. `input` is `{ config, cwd?, out?, locales?, includeUnchanged?, format? }`. By default it writes the missing and changed strings for every target locale; `locales` narrows which target locales are exported, and `includeUnchanged: true` also exports already up-to-date strings. No provider is called and no lock file is written.
168
+
169
+ `format` is an `ExchangeFormat`, one of the exported `EXCHANGE_FORMATS` and defaulting to `DEFAULT_EXCHANGE_FORMAT` (`"xlsx"`). With `"xlsx"` it writes one styled workbook with a sheet per locale to `DEFAULT_WORKBOOK_PATH` (`verbatra-translations.xlsx`). With `"csv"` or `"tsv"` it writes one plain `<locale>.csv` or `<locale>.tsv` per exported locale into a directory, `DEFAULT_DELIMITED_PATH` (`verbatra-translations`) by default, creating it if it is missing, and it also leaves a hidden manifest there naming the locales it wrote, which is what lets a later import reject a file left over from a wider export. `out` overrides the default: a file path for `"xlsx"`, a directory for the delimited formats. Resolves to an `ExportWorkbookResult` with the absolute `path` written (the shared directory when one file per locale was written) and a per-locale row count.
152
170
 
153
171
  ### `importWorkbook(input): Promise<RunSummary>`
154
172
 
155
- Imports a filled workbook back into the locale files, gating every row through the same integrity gate as `translate` on top of a fresh source-drift check. `input` is `{ config, workbook, cwd?, dryRun? }`. With `dryRun: true` it validates and reports without writing locale files or updating the lock. Resolves to a `RunSummary`, the same shape `translate` returns: a row the translator left blank whose key still needs a translation is reported in that locale's `unfilled` (nothing is written and the prior lock baseline is kept), an unreadable row in `malformedRows`, a repeated key in `duplicateKeys` (the first occurrence wins), and a configured target locale whose sheet is missing from the workbook is reported as that locale's failed summary rather than silently dropped. `unfilled`, `malformedRows`, and `duplicateKeys` do not feed a locale's `status`, so a partly filled sheet still imports the rows it has. A key is cleared by filling its Translation cell with the `[[CLEAR]]` sentinel; an ordinary blank never clears a value.
173
+ Imports a filled handoff back into the locale files, gating every row through the same integrity gate as `translate` on top of a fresh source-drift check. `input` is `{ config, workbook, cwd?, dryRun?, format? }`. With `dryRun: true` it validates and reports without writing locale files or updating the lock.
174
+
175
+ `format` takes the same `ExchangeFormat` values as `exportWorkbook` and must match how the handoff was written. For `"xlsx"`, `workbook` is the workbook file and the locale comes from the sheet name. For `"csv"` and `"tsv"` the locale comes from the file name instead, and `workbook` is tried as a single `<locale>.csv` file first, so one locale can be imported on its own; if no file exists there it is read as the directory the per-locale files were written into. A file in that directory that the most recent export's manifest does not list is refused as a leftover rather than applied, and reported as that locale's failure with `HANDOFF_FILE_STALE`; a directory with no readable manifest is read as it always was, with every file present imported. Resolves to a `RunSummary`, the same shape `translate` returns: a row the translator left blank whose key still needs a translation is reported in that locale's `unfilled` (nothing is written and the prior lock baseline is kept), an unreadable row in `malformedRows`, a repeated key in `duplicateKeys` (the first occurrence wins), and a configured target locale whose sheet or delimited file is missing from the handoff is reported as that locale's failed summary rather than silently dropped. `unfilled`, `malformedRows`, and `duplicateKeys` do not feed a locale's `status`, so a partly filled sheet still imports the rows it has. A key is cleared by filling its Translation cell with the `[[CLEAR]]` sentinel; an ordinary blank never clears a value.
156
176
 
157
177
  ```ts
158
178
  import { exportWorkbook, importWorkbook, loadConfig } from "@verbatra/sdk";
@@ -170,7 +190,7 @@ See [Manual translation](https://verbatra.kreitz-webdev.de/docs/manual-translati
170
190
 
171
191
  ### More entry points
172
192
 
173
- Beyond the flows above, the SDK exports the building blocks Verbatra Studio and other tooling sit on, each a one-call read or a locked single-key write:
193
+ Beyond the flows above, the SDK exports the building blocks Verbatra Studio and other tooling sit on. Most are a one-call read or a locked single-key write:
174
194
 
175
195
  - `keyIntegrity` reports, per changed key, whether its placeholders still match the source (with the missing and extra tokens on a mismatch) and whether the current target value is still valid ICU.
176
196
  - `lockState` reports the lock file's existence, version, and per-locale drift; `loadLockFile` reads the lock file itself.
@@ -179,6 +199,15 @@ Beyond the flows above, the SDK exports the building blocks Verbatra Studio and
179
199
  - `editEntry` saves a manually edited translation for one key, and `retranslateEntry` re-runs the provider for one key; both run the candidate through the same integrity gate as a full run (a rejection names an `IntegrityGateReason` and writes nothing) and hold the same per-locale write lock.
180
200
  - `readLocaleFileSnapshot` and `diffLocaleSnapshots` snapshot one locale file as per-key content hashes and compare two snapshots, the primitives behind live-refresh watching.
181
201
  - `loadConfigWithMeta` is `loadConfig` plus config-source and glossary provenance.
202
+ - `readGlossaryFile` reads a file-backed glossary fresh from disk, under the same validation `loadConfig` applies, for a long-running tool that has to show the glossary as it is now rather than as it was when the config was loaded. `updateGlossaryTerm` adds, replaces, or removes exactly one term (`translation: null` removes it) and returns the glossary as it now stands, keeping the file's existing key order and indentation. Both take the `GlossaryProvenance` from `loadConfigWithMeta` rather than a path, so the file they touch is always the one the config names. A write runs under a project-wide glossary lock and replaces the file atomically. Only a file-backed glossary can be read or changed this way: a glossary written inline in the config module is refused with `GLOSSARY_NOT_FILE_BACKED` rather than rewritten, and a failed write is `GLOSSARY_UNWRITABLE`.
203
+
204
+ The rest are the values a tool needs to agree with verbatra rather than restate it:
205
+
206
+ - `createLocalePathResolver` builds the two-way locale-to-path mapping from the `sourceLocale`, `targetLocales`, and `files` slice of a config: `pathFor(locale)` gives a locale's absolute file path and `localeFor(path)` gives the configured locale owning a path, which is how a file watcher decides whether a change concerns verbatra at all.
207
+ - `EXCHANGE_FORMATS`, `DEFAULT_EXCHANGE_FORMAT`, `DEFAULT_WORKBOOK_PATH`, and `DEFAULT_DELIMITED_PATH` are the handoff formats and their default output paths, so a `--format` argument can be validated and offered without restating the list. `EXCHANGE_FORMATS` is derived from the `ExchangeFormat` type itself, so it can never drift behind a format the SDK accepts.
208
+ - `CACHE_FILE_NAME` and `LOCK_FILE_NAME` are the names of the two files a run maintains in the project (`verbatra.cache.json` and `verbatra.lock.json`), for tooling that has to find, gitignore, or clear them.
209
+ - `verbatraConfigSchema` is the zod schema `loadConfig` validates against, for validating a config object you assembled yourself. The same schema is published as a JSON Schema document at `@verbatra/sdk/config-schema.json`, generated at build time, so an editor can complete and validate a `.verbatrarc.json` or YAML config; the config accepts an optional top-level `$schema` key to point at it, which is ignored at runtime. `scaffoldingMetadata` carries the facts a project generator needs to write a first config (each provider's key environment variable, a starting model, the token-limit option name it takes, and the supported formats), which is what `verbatra init` renders from. Neither holds an API key value.
210
+ - `SdkFs` is the file-system port every file the SDK touches goes through, so supplying your own as the `deps.fs` option redirects all of it and an entire run can be held in memory: the run-status file, the lock file, the config glossary, the workbook and delimited I/O, and the locale files themselves, which the format adapters read and write through a port built from this one. Reads are size-bounded by contract and writes are expected to be atomic. The single exception is a caller-supplied `deps.adapterRegistry`: those adapters were constructed by you, so `deps.fs` cannot reach them and passing both means you own that wiring.
182
211
 
183
212
  ## Errors and results
184
213
 
@@ -189,9 +218,9 @@ Per-locale failures do not throw: they are recorded on the `RunSummary` so one f
189
218
  ## Documentation
190
219
 
191
220
  - [Documentation site](https://verbatra.kreitz-webdev.de)
192
- - [Project README](https://github.com/mariokreitz/verbatra)
193
- - [`@verbatra/cli`](https://github.com/mariokreitz/verbatra/tree/main/packages/cli) for the command-line tool
221
+ - [Project README](https://github.com/verbatra/verbatra)
222
+ - [`@verbatra/cli`](https://github.com/verbatra/verbatra/tree/main/packages/cli) for the command-line tool
194
223
 
195
224
  ## License
196
225
 
197
- [MIT](https://github.com/mariokreitz/verbatra/blob/main/LICENSE) (c) Mario Kreitz
226
+ [MIT](https://github.com/verbatra/verbatra/blob/main/LICENSE) (c) Mario Kreitz
@@ -0,0 +1,306 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "$schema": {
6
+ "type": "string"
7
+ },
8
+ "sourceLocale": {
9
+ "type": "string",
10
+ "minLength": 1
11
+ },
12
+ "targetLocales": {
13
+ "minItems": 1,
14
+ "type": "array",
15
+ "items": {
16
+ "type": "string",
17
+ "minLength": 1
18
+ }
19
+ },
20
+ "format": {
21
+ "type": "string",
22
+ "enum": [
23
+ "i18next-json",
24
+ "vue-i18n-json",
25
+ "next-intl-json",
26
+ "ngx-translate-json",
27
+ "xliff",
28
+ "yaml",
29
+ "arb",
30
+ "properties"
31
+ ]
32
+ },
33
+ "files": {
34
+ "type": "object",
35
+ "properties": {
36
+ "pattern": {
37
+ "type": "string",
38
+ "minLength": 1,
39
+ "pattern": "\\{locale\\}"
40
+ },
41
+ "localeStyle": {
42
+ "type": "string",
43
+ "enum": [
44
+ "literal",
45
+ "posix",
46
+ "android"
47
+ ]
48
+ }
49
+ },
50
+ "required": [
51
+ "pattern"
52
+ ],
53
+ "additionalProperties": false
54
+ },
55
+ "provider": {
56
+ "oneOf": [
57
+ {
58
+ "type": "object",
59
+ "properties": {
60
+ "id": {
61
+ "type": "string",
62
+ "const": "anthropic"
63
+ },
64
+ "options": {
65
+ "type": "object",
66
+ "properties": {
67
+ "model": {
68
+ "type": "string",
69
+ "minLength": 1
70
+ },
71
+ "maxTokens": {
72
+ "type": "integer",
73
+ "exclusiveMinimum": 0,
74
+ "maximum": 9007199254740991
75
+ },
76
+ "requestTimeoutMs": {
77
+ "type": "integer",
78
+ "exclusiveMinimum": 0,
79
+ "maximum": 9007199254740991
80
+ }
81
+ },
82
+ "required": [
83
+ "model",
84
+ "maxTokens"
85
+ ],
86
+ "additionalProperties": false
87
+ }
88
+ },
89
+ "required": [
90
+ "id",
91
+ "options"
92
+ ],
93
+ "additionalProperties": false
94
+ },
95
+ {
96
+ "type": "object",
97
+ "properties": {
98
+ "id": {
99
+ "type": "string",
100
+ "const": "openai"
101
+ },
102
+ "options": {
103
+ "type": "object",
104
+ "properties": {
105
+ "model": {
106
+ "type": "string",
107
+ "minLength": 1
108
+ },
109
+ "maxOutputTokens": {
110
+ "type": "integer",
111
+ "exclusiveMinimum": 0,
112
+ "maximum": 9007199254740991
113
+ },
114
+ "requestTimeoutMs": {
115
+ "type": "integer",
116
+ "exclusiveMinimum": 0,
117
+ "maximum": 9007199254740991
118
+ }
119
+ },
120
+ "required": [
121
+ "model",
122
+ "maxOutputTokens"
123
+ ],
124
+ "additionalProperties": false
125
+ }
126
+ },
127
+ "required": [
128
+ "id",
129
+ "options"
130
+ ],
131
+ "additionalProperties": false
132
+ },
133
+ {
134
+ "type": "object",
135
+ "properties": {
136
+ "id": {
137
+ "type": "string",
138
+ "const": "gemini"
139
+ },
140
+ "options": {
141
+ "type": "object",
142
+ "properties": {
143
+ "model": {
144
+ "type": "string",
145
+ "minLength": 1
146
+ },
147
+ "maxOutputTokens": {
148
+ "type": "integer",
149
+ "exclusiveMinimum": 0,
150
+ "maximum": 9007199254740991
151
+ },
152
+ "requestTimeoutMs": {
153
+ "type": "integer",
154
+ "exclusiveMinimum": 0,
155
+ "maximum": 9007199254740991
156
+ }
157
+ },
158
+ "required": [
159
+ "model",
160
+ "maxOutputTokens"
161
+ ],
162
+ "additionalProperties": false
163
+ }
164
+ },
165
+ "required": [
166
+ "id",
167
+ "options"
168
+ ],
169
+ "additionalProperties": false
170
+ },
171
+ {
172
+ "type": "object",
173
+ "properties": {
174
+ "id": {
175
+ "type": "string",
176
+ "const": "deepl"
177
+ },
178
+ "options": {
179
+ "type": "object",
180
+ "properties": {
181
+ "glossaryId": {
182
+ "type": "string",
183
+ "minLength": 1
184
+ },
185
+ "requestTimeoutMs": {
186
+ "type": "integer",
187
+ "exclusiveMinimum": 0,
188
+ "maximum": 9007199254740991
189
+ }
190
+ },
191
+ "additionalProperties": false
192
+ }
193
+ },
194
+ "required": [
195
+ "id",
196
+ "options"
197
+ ],
198
+ "additionalProperties": false
199
+ },
200
+ {
201
+ "type": "object",
202
+ "properties": {
203
+ "id": {
204
+ "type": "string",
205
+ "const": "openai-compatible"
206
+ },
207
+ "options": {
208
+ "type": "object",
209
+ "properties": {
210
+ "baseUrl": {
211
+ "type": "string",
212
+ "pattern": "^https?:\\/\\/"
213
+ },
214
+ "model": {
215
+ "type": "string",
216
+ "minLength": 1
217
+ },
218
+ "maxOutputTokens": {
219
+ "type": "integer",
220
+ "exclusiveMinimum": 0,
221
+ "maximum": 9007199254740991
222
+ },
223
+ "apiKeyEnvVar": {
224
+ "type": "string",
225
+ "minLength": 1
226
+ },
227
+ "requestTimeoutMs": {
228
+ "type": "integer",
229
+ "exclusiveMinimum": 0,
230
+ "maximum": 9007199254740991
231
+ }
232
+ },
233
+ "required": [
234
+ "baseUrl",
235
+ "model",
236
+ "maxOutputTokens"
237
+ ],
238
+ "additionalProperties": false
239
+ }
240
+ },
241
+ "required": [
242
+ "id",
243
+ "options"
244
+ ],
245
+ "additionalProperties": false
246
+ }
247
+ ]
248
+ },
249
+ "glossary": {
250
+ "anyOf": [
251
+ {
252
+ "type": "object",
253
+ "propertyNames": {
254
+ "type": "string"
255
+ },
256
+ "additionalProperties": {
257
+ "type": "string"
258
+ }
259
+ },
260
+ {
261
+ "type": "string",
262
+ "minLength": 1
263
+ }
264
+ ]
265
+ },
266
+ "tone": {
267
+ "type": "string",
268
+ "enum": [
269
+ "formal",
270
+ "informal",
271
+ "neutral"
272
+ ]
273
+ },
274
+ "prune": {
275
+ "type": "boolean"
276
+ },
277
+ "generatePlurals": {
278
+ "type": "boolean"
279
+ },
280
+ "maxBatchSize": {
281
+ "type": "integer",
282
+ "exclusiveMinimum": 0,
283
+ "maximum": 9007199254740991
284
+ },
285
+ "maxTokens": {
286
+ "type": "integer",
287
+ "exclusiveMinimum": 0,
288
+ "maximum": 9007199254740991
289
+ },
290
+ "budgetBehavior": {
291
+ "type": "string",
292
+ "enum": [
293
+ "warn",
294
+ "stop"
295
+ ]
296
+ }
297
+ },
298
+ "required": [
299
+ "sourceLocale",
300
+ "targetLocales",
301
+ "format",
302
+ "files",
303
+ "provider"
304
+ ],
305
+ "additionalProperties": false
306
+ }