@verbatra/sdk 0.2.2 → 0.4.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 +31 -5
- package/dist/index.cjs +1244 -283
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +283 -135
- package/dist/index.d.ts +283 -135
- package/dist/index.js +1238 -281
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
package/dist/index.d.cts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
+
export { SupportedFormat } from '@verbatra/core';
|
|
1
2
|
import { AnthropicModel, OpenAiModel, GeminiModel, ProviderNotice, TranslationProvider } from '@verbatra/ai-providers';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { AdapterRegistry } from '@verbatra/format-adapters';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* The provider section of the config: a discriminated union over the provider id,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* This union and the factory table below are co-located on purpose: adding a provider
|
|
11
|
-
* is a single edit here (one union variant plus one table entry), and the mapped-type
|
|
12
|
-
* table makes the two sets provably identical at compile time.
|
|
7
|
+
* The provider section of the config: a discriminated union over the provider id, reusing each
|
|
8
|
+
* provider's own config schema. There is no key field anywhere in this union; the provider reads its
|
|
9
|
+
* API key from the environment at construction.
|
|
13
10
|
*/
|
|
14
11
|
declare const providerConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
15
12
|
id: z.ZodLiteral<"anthropic">;
|
|
@@ -39,10 +36,9 @@ type ProviderConfig = z.infer<typeof providerConfigSchema>;
|
|
|
39
36
|
type ProviderId = ProviderConfig["id"];
|
|
40
37
|
|
|
41
38
|
/**
|
|
42
|
-
* The verbatra project configuration.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* regardless of where it was loaded from.
|
|
39
|
+
* The verbatra project configuration. It carries no API key (the provider reads its key from the
|
|
40
|
+
* environment), and unknown top-level keys are rejected so a stray secret cannot hide in it. Validated
|
|
41
|
+
* with zod at the boundary regardless of where it was loaded from.
|
|
46
42
|
*/
|
|
47
43
|
declare const verbatraConfigSchema: z.ZodObject<{
|
|
48
44
|
sourceLocale: z.ZodString;
|
|
@@ -52,6 +48,9 @@ declare const verbatraConfigSchema: z.ZodObject<{
|
|
|
52
48
|
"vue-i18n-json": "vue-i18n-json";
|
|
53
49
|
"next-intl-json": "next-intl-json";
|
|
54
50
|
"ngx-translate-json": "ngx-translate-json";
|
|
51
|
+
xliff: "xliff";
|
|
52
|
+
yaml: "yaml";
|
|
53
|
+
arb: "arb";
|
|
55
54
|
}>;
|
|
56
55
|
files: z.ZodObject<{
|
|
57
56
|
pattern: z.ZodString;
|
|
@@ -86,27 +85,14 @@ declare const verbatraConfigSchema: z.ZodObject<{
|
|
|
86
85
|
informal: "informal";
|
|
87
86
|
neutral: "neutral";
|
|
88
87
|
}>>;
|
|
88
|
+
prune: z.ZodOptional<z.ZodBoolean>;
|
|
89
|
+
generatePlurals: z.ZodOptional<z.ZodBoolean>;
|
|
90
|
+
maxBatchSize: z.ZodOptional<z.ZodNumber>;
|
|
89
91
|
}, z.core.$strict>;
|
|
90
92
|
type VerbatraConfig = z.infer<typeof verbatraConfigSchema>;
|
|
91
93
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* non-empty string. The `string & {}` arm keeps the union from collapsing to plain
|
|
95
|
-
* `string` (so editors still surface the literals as completions) while accepting a
|
|
96
|
-
* brand-new model ID the tool has never heard of. This is a static authoring hint
|
|
97
|
-
* only; the runtime schema stays `z.string().min(1)` and validates nothing against
|
|
98
|
-
* these literals.
|
|
99
|
-
*/
|
|
100
|
-
type OpenModel<M extends string> = M | (string & {});
|
|
101
|
-
/**
|
|
102
|
-
* The authoring view of one provider variant: its runtime config with `options.model`
|
|
103
|
-
* narrowed to that provider's open model union. LLM providers (anthropic, openai,
|
|
104
|
-
* gemini) get suggestions; DeepL has no model field and is carried through unchanged.
|
|
105
|
-
*/
|
|
106
|
-
type AuthoringProviderConfig = AuthoringVariant<"anthropic", AnthropicModel> | AuthoringVariant<"openai", OpenAiModel> | AuthoringVariant<"gemini", GeminiModel> | Extract<ProviderConfig, {
|
|
107
|
-
id: "deepl";
|
|
108
|
-
}>;
|
|
109
|
-
type AuthoringVariant<Id extends ProviderConfig["id"], M extends string> = Extract<ProviderConfig, {
|
|
94
|
+
type KnownModels<M extends string> = M extends string ? (string extends M ? never : M) : never;
|
|
95
|
+
type AuthoringVariant<Id extends ProviderId, M extends string> = Extract<ProviderConfig, {
|
|
110
96
|
id: Id;
|
|
111
97
|
}> extends infer Variant ? Variant extends {
|
|
112
98
|
options: {
|
|
@@ -114,30 +100,49 @@ type AuthoringVariant<Id extends ProviderConfig["id"], M extends string> = Extra
|
|
|
114
100
|
};
|
|
115
101
|
} ? Omit<Variant, "options"> & {
|
|
116
102
|
options: Omit<Variant["options"], "model"> & {
|
|
117
|
-
model:
|
|
103
|
+
model: KnownModels<M>;
|
|
118
104
|
};
|
|
119
105
|
} : never : never;
|
|
106
|
+
type AuthoringProviderVariant = {
|
|
107
|
+
anthropic: AuthoringVariant<"anthropic", AnthropicModel>;
|
|
108
|
+
openai: AuthoringVariant<"openai", OpenAiModel>;
|
|
109
|
+
gemini: AuthoringVariant<"gemini", GeminiModel>;
|
|
110
|
+
deepl: Extract<ProviderConfig, {
|
|
111
|
+
id: "deepl";
|
|
112
|
+
}>;
|
|
113
|
+
};
|
|
120
114
|
/**
|
|
121
|
-
* The authoring view of the whole config:
|
|
122
|
-
* that `
|
|
123
|
-
*
|
|
124
|
-
*
|
|
115
|
+
* The authoring view of the whole config for a provider id: a {@link VerbatraConfig} whose `provider`
|
|
116
|
+
* is that id's single authoring variant, so `options.model` offers only that provider's models. When
|
|
117
|
+
* `TId` defaults to `ProviderId`, `provider` is the full authoring union. Every value here is assignable
|
|
118
|
+
* to {@link VerbatraConfig}, since a model literal is a subtype of `string`.
|
|
125
119
|
*/
|
|
126
|
-
type
|
|
127
|
-
provider:
|
|
120
|
+
type AuthoringConfigFor<TId extends ProviderId = ProviderId> = Omit<VerbatraConfig, "provider"> & {
|
|
121
|
+
provider: AuthoringProviderVariant[TId];
|
|
128
122
|
};
|
|
123
|
+
/**
|
|
124
|
+
* The authoring view of the whole config across every provider (the `TId = ProviderId`
|
|
125
|
+
* case of {@link AuthoringConfigFor}): identical to {@link VerbatraConfig} except that
|
|
126
|
+
* `provider` offers per-provider model completions.
|
|
127
|
+
*/
|
|
128
|
+
type AuthoringConfig = AuthoringConfigFor;
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
|
-
* Identity helper for authoring a
|
|
132
|
-
*
|
|
133
|
-
*
|
|
131
|
+
* Identity helper for authoring a typed `verbatra.config.ts`. It returns its argument unchanged; its
|
|
132
|
+
* only purpose is full type inference and editor autocomplete on the config object, including
|
|
133
|
+
* completion of the selected provider's known model literals.
|
|
134
134
|
*
|
|
135
|
-
* The
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
135
|
+
* The model restriction is a static authoring constraint only: `loadConfig` validates `model` as a
|
|
136
|
+
* non-empty string, so a model the installed provider SDK does not yet list is flagged in the editor
|
|
137
|
+
* but still runs.
|
|
138
|
+
*
|
|
139
|
+
* @param config - The verbatra configuration object.
|
|
140
|
+
* @returns The same config, typed as {@link VerbatraConfig}.
|
|
140
141
|
*/
|
|
142
|
+
declare function defineConfig(config: AuthoringConfigFor<"anthropic">): VerbatraConfig;
|
|
143
|
+
declare function defineConfig(config: AuthoringConfigFor<"openai">): VerbatraConfig;
|
|
144
|
+
declare function defineConfig(config: AuthoringConfigFor<"gemini">): VerbatraConfig;
|
|
145
|
+
declare function defineConfig(config: AuthoringConfigFor<"deepl">): VerbatraConfig;
|
|
141
146
|
declare function defineConfig(config: AuthoringConfig): VerbatraConfig;
|
|
142
147
|
|
|
143
148
|
interface LoadConfigOptions {
|
|
@@ -149,12 +154,10 @@ interface LoadConfigOptions {
|
|
|
149
154
|
*/
|
|
150
155
|
readonly configOverride?: unknown;
|
|
151
156
|
/**
|
|
152
|
-
* An explicit config file to load instead of searching. A relative path resolves
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* present-but-unparseable/invalid file is CONFIG_INVALID. Precedence: configOverride
|
|
157
|
-
* wins over configPath, which wins over search.
|
|
157
|
+
* An explicit config file to load instead of searching. A relative path resolves against `cwd`; an
|
|
158
|
+
* absolute path is used as given. Parsed and zod-validated exactly like a searched file: a missing
|
|
159
|
+
* file is `CONFIG_NOT_FOUND`, a present but invalid one is `CONFIG_INVALID`. Takes precedence over
|
|
160
|
+
* search, but `configOverride` takes precedence over it.
|
|
158
161
|
*/
|
|
159
162
|
readonly configPath?: string;
|
|
160
163
|
}
|
|
@@ -192,16 +195,16 @@ declare function loadConfig(options?: LoadConfigOptions): Promise<VerbatraConfig
|
|
|
192
195
|
* any message: provider/adapter/core errors are already secret-free, and the SDK never
|
|
193
196
|
* reads or holds a key. Each names a distinct boundary:
|
|
194
197
|
*
|
|
195
|
-
* - `CONFIG_NOT_FOUND`:no config was found by search, or an explicit `configPath` does not exist
|
|
198
|
+
* - `CONFIG_NOT_FOUND`: no config was found by search, or an explicit `configPath` does not exist
|
|
196
199
|
* (thrown by `loadConfig`).
|
|
197
|
-
* - `CONFIG_INVALID`:a config was found but is unparseable or fails validation (thrown by `loadConfig`).
|
|
198
|
-
* - `UNKNOWN_FORMAT`:no adapter is registered for the configured format (thrown by `translate`).
|
|
199
|
-
* - `PROVIDER_CONSTRUCTION_FAILED`:the provider factory threw; wraps the provider's own error, including
|
|
200
|
+
* - `CONFIG_INVALID`: a config was found but is unparseable or fails validation (thrown by `loadConfig`).
|
|
201
|
+
* - `UNKNOWN_FORMAT`: no adapter is registered for the configured format (thrown by `translate`).
|
|
202
|
+
* - `PROVIDER_CONSTRUCTION_FAILED`: the provider factory threw; wraps the provider's own error, including
|
|
200
203
|
* a missing `*_API_KEY` reported as `MISSING_API_KEY` (thrown by `translate`, non-dry-run only).
|
|
201
|
-
* - `SOURCE_UNREADABLE`:the source locale file is absent (thrown by `translate`, and by `watch` at startup).
|
|
202
|
-
* - `SOURCE_INVALID`:the source locale file could not be read or parsed; wraps the adapter read error
|
|
204
|
+
* - `SOURCE_UNREADABLE`: the source locale file is absent (thrown by `translate`, and by `watch` at startup).
|
|
205
|
+
* - `SOURCE_INVALID`: the source locale file could not be read or parsed; wraps the adapter read error
|
|
203
206
|
* (thrown by `translate`).
|
|
204
|
-
* - `LOCK_FILE_INVALID`:the lock-file is present but corrupt or oversized (thrown by `translate`).
|
|
207
|
+
* - `LOCK_FILE_INVALID`: the lock-file is present but corrupt or oversized (thrown by `translate`).
|
|
205
208
|
* - `LOCALE_FAILED` (NOT thrown): the fallback `code` recorded on a failed `LocaleSummary` when a
|
|
206
209
|
* per-locale failure carries no string code of its own. See the surfaced-not-thrown distinction on
|
|
207
210
|
* `translate`.
|
|
@@ -218,6 +221,157 @@ declare class SdkError extends Error {
|
|
|
218
221
|
constructor(code: SdkErrorCode, message: string);
|
|
219
222
|
}
|
|
220
223
|
|
|
224
|
+
/** Outcome of a bounded read: the content, or why it could not be read in bounds. */
|
|
225
|
+
type BoundedFileRead = {
|
|
226
|
+
readonly kind: "ok";
|
|
227
|
+
readonly content: string;
|
|
228
|
+
} | {
|
|
229
|
+
readonly kind: "missing";
|
|
230
|
+
} | {
|
|
231
|
+
readonly kind: "too-large";
|
|
232
|
+
};
|
|
233
|
+
/** Outcome of a bounded binary read: the bytes, or why they could not be read in bounds. */
|
|
234
|
+
type BoundedBytesRead = {
|
|
235
|
+
readonly kind: "ok";
|
|
236
|
+
readonly bytes: Uint8Array;
|
|
237
|
+
} | {
|
|
238
|
+
readonly kind: "missing";
|
|
239
|
+
} | {
|
|
240
|
+
readonly kind: "too-large";
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* The minimal file-system surface the SDK needs. Reads are bounded and writes are atomic. Injectable so
|
|
244
|
+
* tests stay deterministic; the format adapters do their own file IO and bypass this seam.
|
|
245
|
+
*/
|
|
246
|
+
interface SdkFs {
|
|
247
|
+
/** Whether a readable file exists at the path. */
|
|
248
|
+
fileExists(path: string): Promise<boolean>;
|
|
249
|
+
/**
|
|
250
|
+
* Read a file as UTF-8 through a single handle, bounded to maxBytes. TOCTOU-safe: the handle is
|
|
251
|
+
* fstat'd and the read never advances past the sized length, so swapping in a larger file cannot
|
|
252
|
+
* bypass the cap. A missing or unreadable path is "missing"; a file over the cap is "too-large".
|
|
253
|
+
*/
|
|
254
|
+
readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
|
|
255
|
+
/** Read a file as raw bytes with the same TOCTOU-safe, bounded discipline as {@link readFileBounded}. */
|
|
256
|
+
readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
|
|
257
|
+
/** Write atomically: a temp file in the same directory, then rename over the target. */
|
|
258
|
+
writeFile(path: string, data: string): Promise<void>;
|
|
259
|
+
/** Write raw bytes atomically (temp file, then rename over the target). Used for the workbook. */
|
|
260
|
+
writeBytes(path: string, data: Uint8Array): Promise<void>;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Per-locale drift status: counts only, no key lists. */
|
|
264
|
+
interface LocaleCheckSummary {
|
|
265
|
+
/** The target locale this entry reports on. */
|
|
266
|
+
readonly locale: string;
|
|
267
|
+
/** Keys present in source but absent from the target. */
|
|
268
|
+
readonly missing: number;
|
|
269
|
+
/** Keys whose source changed since the target was last translated. */
|
|
270
|
+
readonly stale: number;
|
|
271
|
+
/** Keys whose recorded baseline still matches the source. */
|
|
272
|
+
readonly upToDate: number;
|
|
273
|
+
/** True when nothing needs (re)translating for this locale. */
|
|
274
|
+
readonly inSync: boolean;
|
|
275
|
+
}
|
|
276
|
+
/** The aggregate read-only status across all checked target locales. */
|
|
277
|
+
interface CheckSummary {
|
|
278
|
+
/** True only when every checked locale is in sync. */
|
|
279
|
+
readonly inSync: boolean;
|
|
280
|
+
/** One entry per checked target locale, in config order. */
|
|
281
|
+
readonly locales: readonly LocaleCheckSummary[];
|
|
282
|
+
}
|
|
283
|
+
/** Input for {@link check}: the validated config and which locales to check. */
|
|
284
|
+
interface CheckInput {
|
|
285
|
+
/** The validated configuration (typically from {@link loadConfig}). */
|
|
286
|
+
readonly config: VerbatraConfig;
|
|
287
|
+
/** Directory the file pattern and lock-file resolve against; defaults to cwd. */
|
|
288
|
+
readonly cwd?: string;
|
|
289
|
+
/** Subset of target locales to check; defaults to all configured target locales. */
|
|
290
|
+
readonly locales?: readonly string[];
|
|
291
|
+
}
|
|
292
|
+
/** Composition seam for {@link check}: inject a registry and a file system for tests. */
|
|
293
|
+
interface CheckDeps {
|
|
294
|
+
readonly adapterRegistry?: AdapterRegistry;
|
|
295
|
+
readonly fs?: SdkFs;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Report which keys are missing or stale per target locale, without calling a provider, writing any
|
|
299
|
+
* file, or touching the lock. Each locale is reported as counts only (missing, stale, up-to-date);
|
|
300
|
+
* a locale is `inSync` when nothing is missing or stale, and the top-level `inSync` is true only when
|
|
301
|
+
* every checked locale is. Orphaned keys and integrity are not reported, since they concern a write.
|
|
302
|
+
*
|
|
303
|
+
* @param input - The validated config and which locales to check.
|
|
304
|
+
* @param deps - Optional composition seams (registry, file system) for tests.
|
|
305
|
+
* @returns The aggregate and per-locale drift status.
|
|
306
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`, `SOURCE_UNREADABLE`, `SOURCE_INVALID`, `LOCK_FILE_INVALID`
|
|
307
|
+
* with the same meanings as in `translate`.
|
|
308
|
+
*/
|
|
309
|
+
declare function check(input: CheckInput, deps?: CheckDeps): Promise<CheckSummary>;
|
|
310
|
+
|
|
311
|
+
/** Per-locale pending change: the key lists, not counts. */
|
|
312
|
+
interface LocaleDiff {
|
|
313
|
+
/** The target locale this entry reports on. */
|
|
314
|
+
readonly locale: string;
|
|
315
|
+
/** Keys present in source but absent from the target; would be added by a run. */
|
|
316
|
+
readonly missing: readonly string[];
|
|
317
|
+
/** Keys whose source changed since last translated; would be re-translated. */
|
|
318
|
+
readonly changed: readonly string[];
|
|
319
|
+
/** Keys present in target but absent from source; report-only, never pending. */
|
|
320
|
+
readonly orphaned: readonly string[];
|
|
321
|
+
/** True when the locale has missing or changed keys; orphaned keys do not count. */
|
|
322
|
+
readonly hasPendingChanges: boolean;
|
|
323
|
+
}
|
|
324
|
+
/** The aggregate read-only diff across all checked target locales. */
|
|
325
|
+
interface DiffSummary {
|
|
326
|
+
/** True when any checked locale has pending changes. */
|
|
327
|
+
readonly hasPendingChanges: boolean;
|
|
328
|
+
/** One entry per checked target locale, in config order. */
|
|
329
|
+
readonly locales: readonly LocaleDiff[];
|
|
330
|
+
}
|
|
331
|
+
/** Input for {@link diff}: the validated config and which locales to diff. */
|
|
332
|
+
interface DiffInput {
|
|
333
|
+
/** The validated configuration (typically from {@link loadConfig}). */
|
|
334
|
+
readonly config: VerbatraConfig;
|
|
335
|
+
/** Directory the file pattern and lock-file resolve against; defaults to cwd. */
|
|
336
|
+
readonly cwd?: string;
|
|
337
|
+
/** Subset of target locales to diff; defaults to all configured target locales. */
|
|
338
|
+
readonly locales?: readonly string[];
|
|
339
|
+
}
|
|
340
|
+
/** Composition seam for {@link diff}: inject a registry and a file system for tests. */
|
|
341
|
+
interface DiffDeps {
|
|
342
|
+
readonly adapterRegistry?: AdapterRegistry;
|
|
343
|
+
readonly fs?: SdkFs;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Report the exact pending change per target locale as three key lists (missing, changed, orphaned),
|
|
347
|
+
* without calling a provider, writing any file, or touching the lock. This is the detailed sibling of
|
|
348
|
+
* {@link check}. A locale's `hasPendingChanges` is driven by missing or changed only; orphaned keys are
|
|
349
|
+
* reported but do not flip it, since a default `translate` run does not prune. The top-level
|
|
350
|
+
* `hasPendingChanges` is true when any checked locale has pending changes.
|
|
351
|
+
*
|
|
352
|
+
* @param input - The validated config and which locales to diff.
|
|
353
|
+
* @param deps - Optional composition seams (registry, file system) for tests.
|
|
354
|
+
* @returns The aggregate and per-locale pending change.
|
|
355
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`, `SOURCE_UNREADABLE`, `SOURCE_INVALID`, `LOCK_FILE_INVALID`
|
|
356
|
+
* with the same meanings as in `translate`.
|
|
357
|
+
*/
|
|
358
|
+
declare function diff(input: DiffInput, deps?: DiffDeps): Promise<DiffSummary>;
|
|
359
|
+
|
|
360
|
+
/** A stable code for an SDK-originated notice (not a provider notice). */
|
|
361
|
+
type SdkNoticeCode = "PLURAL_CATEGORIES_INCOMPLETE" | "SUB_BATCH_FAILED";
|
|
362
|
+
/**
|
|
363
|
+
* A notice raised by the SDK itself (not a provider), structurally identical to a {@link ProviderNotice}
|
|
364
|
+
* so both share the {@link LocaleSummary.notices} channel. Carries only a stable code and a static,
|
|
365
|
+
* secret-free message; never a key value or translatable content.
|
|
366
|
+
*/
|
|
367
|
+
interface SdkNotice {
|
|
368
|
+
/** The stable {@link SdkNoticeCode} for what the SDK is reporting. */
|
|
369
|
+
readonly code: SdkNoticeCode;
|
|
370
|
+
/** A static, safe description; never a key or translatable content. */
|
|
371
|
+
readonly message: string;
|
|
372
|
+
}
|
|
373
|
+
/** A notice on a locale summary: either a provider-emitted notice or an SDK-emitted one. */
|
|
374
|
+
type LocaleNotice = ProviderNotice | SdkNotice;
|
|
221
375
|
/** Structured outcome for one target locale; surfaced as data on the run, never thrown. */
|
|
222
376
|
interface LocaleSummary {
|
|
223
377
|
/** The target locale this summary is for. */
|
|
@@ -231,14 +385,30 @@ interface LocaleSummary {
|
|
|
231
385
|
readonly translated: readonly string[];
|
|
232
386
|
/** Keys already up to date, left unchanged this run. */
|
|
233
387
|
readonly unchanged: readonly string[];
|
|
234
|
-
/** Target keys with no corresponding source key (candidates for removal). */
|
|
388
|
+
/** Target keys with no corresponding source key (candidates for removal). Reported regardless of pruning. */
|
|
235
389
|
readonly orphaned: readonly string[];
|
|
390
|
+
/**
|
|
391
|
+
* Orphaned keys actually removed this run because pruning was on. In a dry-run with pruning on, the keys
|
|
392
|
+
* that WOULD be removed. Empty when pruning is off (the orphans then survive and are reported in
|
|
393
|
+
* `orphaned` only). A subset of `orphaned`; never includes a source-present key.
|
|
394
|
+
*/
|
|
395
|
+
readonly pruned: readonly string[];
|
|
236
396
|
/** Source keys flagged invalid-ICU that were skipped for translation this run. */
|
|
237
397
|
readonly invalidIcuSource: readonly string[];
|
|
238
398
|
/** Translated keys that failed the placeholder-integrity check and were withheld. */
|
|
239
399
|
readonly integrityMismatches: readonly string[];
|
|
240
|
-
/**
|
|
241
|
-
|
|
400
|
+
/**
|
|
401
|
+
* Plural-category keys verbatra synthesized this run (for example a Polish `items_few` the source
|
|
402
|
+
* never supplied), kept distinct from {@link translated}. Empty unless plural generation was enabled
|
|
403
|
+
* and acted, and empty in a dry-run.
|
|
404
|
+
*/
|
|
405
|
+
readonly generated: readonly string[];
|
|
406
|
+
/**
|
|
407
|
+
* Notices for this locale: provider notices (e.g. DeepL graceful-degradation) and SDK notices
|
|
408
|
+
* (e.g. a target language needing more CLDR plural categories than the source supplies). Empty when
|
|
409
|
+
* nothing was degraded or flagged.
|
|
410
|
+
*/
|
|
411
|
+
readonly notices: readonly LocaleNotice[];
|
|
242
412
|
/**
|
|
243
413
|
* Present only when status is "failed": a structured, secret-free error. `code` is a PRESERVED string
|
|
244
414
|
* (the underlying provider/adapter error's `code`, or `"LOCALE_FAILED"` as a fallback), intentionally
|
|
@@ -261,54 +431,6 @@ interface RunSummary {
|
|
|
261
431
|
readonly failed: readonly string[];
|
|
262
432
|
}
|
|
263
433
|
|
|
264
|
-
/** Outcome of a bounded read: the content, or why it could not be read in bounds. */
|
|
265
|
-
type BoundedFileRead = {
|
|
266
|
-
readonly kind: "ok";
|
|
267
|
-
readonly content: string;
|
|
268
|
-
} | {
|
|
269
|
-
readonly kind: "missing";
|
|
270
|
-
} | {
|
|
271
|
-
readonly kind: "too-large";
|
|
272
|
-
};
|
|
273
|
-
/** Outcome of a bounded binary read: the bytes, or why they could not be read in bounds. */
|
|
274
|
-
type BoundedBytesRead = {
|
|
275
|
-
readonly kind: "ok";
|
|
276
|
-
readonly bytes: Uint8Array;
|
|
277
|
-
} | {
|
|
278
|
-
readonly kind: "missing";
|
|
279
|
-
} | {
|
|
280
|
-
readonly kind: "too-large";
|
|
281
|
-
};
|
|
282
|
-
/**
|
|
283
|
-
* The minimal file-system surface the SDK needs: existence checks, the lock-file read/write,
|
|
284
|
-
* and the untrusted workbook bytes read/write for the export/import flow. Reads are bounded
|
|
285
|
-
* (see {@link readFileBounded} / {@link readBytesBounded}) and writes are atomic. Injectable so
|
|
286
|
-
* tests stay deterministic; the format adapters do their own file IO and are not routed through
|
|
287
|
-
* this seam.
|
|
288
|
-
*/
|
|
289
|
-
interface SdkFs {
|
|
290
|
-
/** Whether a readable file exists at the path. */
|
|
291
|
-
fileExists(path: string): Promise<boolean>;
|
|
292
|
-
/**
|
|
293
|
-
* Read a file as UTF-8 through a single handle, bounded to maxBytes. TOCTOU-safe: the
|
|
294
|
-
* handle is fstat'd and the read never advances past the sized length, so swapping the
|
|
295
|
-
* path for a larger file after the size check cannot bypass the cap. A missing or
|
|
296
|
-
* unreadable path is "missing" (first-run); a file over the cap is "too-large".
|
|
297
|
-
*/
|
|
298
|
-
readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
|
|
299
|
-
/**
|
|
300
|
-
* Read a file as raw bytes through a single handle, bounded to maxBytes, with the SAME
|
|
301
|
-
* TOCTOU-safe discipline as {@link readFileBounded}: the handle is fstat'd and the read never
|
|
302
|
-
* advances past the sized length. Used for the untrusted workbook on import; a file over the
|
|
303
|
-
* cap is "too-large", a missing path is "missing".
|
|
304
|
-
*/
|
|
305
|
-
readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
|
|
306
|
-
/** Write atomically: a temp file in the same directory, then rename over the target. */
|
|
307
|
-
writeFile(path: string, data: string): Promise<void>;
|
|
308
|
-
/** Write raw bytes atomically (temp file, then rename over the target). Used for the workbook. */
|
|
309
|
-
writeBytes(path: string, data: Uint8Array): Promise<void>;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
434
|
/** Builds the provider from its config. Injectable so tests stay offline. */
|
|
313
435
|
type CreateProvider = (config: ProviderConfig) => TranslationProvider;
|
|
314
436
|
|
|
@@ -320,6 +442,19 @@ interface TranslateInput {
|
|
|
320
442
|
readonly cwd?: string;
|
|
321
443
|
/** When true, read + diff + report only: the provider is never constructed or called and nothing is written. */
|
|
322
444
|
readonly dryRun?: boolean;
|
|
445
|
+
/**
|
|
446
|
+
* When true, remove orphaned keys (target keys absent from source) from the written file and the lock.
|
|
447
|
+
* Off by default. When set, takes precedence over the config's `prune` option for this run; when unset,
|
|
448
|
+
* the config's `prune` applies. Only `diff.orphaned` keys are ever removed; no other key is touched.
|
|
449
|
+
*/
|
|
450
|
+
readonly prune?: boolean;
|
|
451
|
+
/**
|
|
452
|
+
* When true, synthesize the CLDR plural forms a richer target language requires but the source lacks
|
|
453
|
+
* (i18next-JSON + LLM providers only; every other case falls back to the existing warning). Off by
|
|
454
|
+
* default. When set, takes precedence over the config's `generatePlurals` option for this run; when
|
|
455
|
+
* unset, the config's `generatePlurals` applies.
|
|
456
|
+
*/
|
|
457
|
+
readonly generatePlurals?: boolean;
|
|
323
458
|
}
|
|
324
459
|
/** Composition seam: inject a registry, a provider builder, and a file system for tests. */
|
|
325
460
|
interface TranslateDeps {
|
|
@@ -344,7 +479,7 @@ interface TranslateDeps {
|
|
|
344
479
|
* {@link SdkErrorCode}. DeepL notices, integrity mismatches, and invalid-ICU source keys likewise
|
|
345
480
|
* surface on each `LocaleSummary`, never as throws.
|
|
346
481
|
*
|
|
347
|
-
* @param input - The validated config and run options (cwd, dryRun).
|
|
482
|
+
* @param input - The validated config and run options (cwd, dryRun, prune, generatePlurals).
|
|
348
483
|
* @param deps - Optional composition seams (registry, provider builder, file system) for tests.
|
|
349
484
|
* @returns A {@link RunSummary}: the per-locale {@link LocaleSummary}s and the succeeded/failed locale lists.
|
|
350
485
|
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
@@ -409,11 +544,10 @@ interface ExportWorkbookResult {
|
|
|
409
544
|
}[];
|
|
410
545
|
}
|
|
411
546
|
/**
|
|
412
|
-
* Export the strings needing human translation into a styled `.xlsx` workbook.
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
* writes the bytes through the {@link SdkFs} seam. No provider is called and no lock-file is written.
|
|
547
|
+
* Export the strings needing human translation into a styled `.xlsx` workbook. Each target locale is
|
|
548
|
+
* diffed against the source and lock baseline to pick the rows (missing and changed by default; add
|
|
549
|
+
* unchanged with `includeUnchanged`), and the bytes are written to `out`. No provider is called and no
|
|
550
|
+
* lock-file is written.
|
|
417
551
|
*
|
|
418
552
|
* @param input - The validated config and export options.
|
|
419
553
|
* @param deps - Optional composition seams (registry, file system) for tests.
|
|
@@ -440,19 +574,16 @@ interface ImportWorkbookDeps {
|
|
|
440
574
|
readonly fs?: SdkFs;
|
|
441
575
|
}
|
|
442
576
|
/**
|
|
443
|
-
* Import a filled workbook back into the locale files.
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
* `validateMessage`), writes the accepted values through the format adapter, and updates the lock
|
|
448
|
-
* through the existing lock logic. Returns a {@link RunSummary} structurally identical to
|
|
449
|
-
* `translate`'s, so the CLI formatter and exit-code rule are shared with no special case.
|
|
577
|
+
* Import a filled workbook back into the locale files. Each target-locale data sheet runs the same
|
|
578
|
+
* source-drift, placeholder, and ICU checks as the translate flow, the accepted values are written
|
|
579
|
+
* through the format adapter, and the lock is updated. Returns a {@link RunSummary} structurally
|
|
580
|
+
* identical to `translate`'s.
|
|
450
581
|
*
|
|
451
582
|
* Whole-run failures (unknown format, unreadable/invalid/oversized workbook, corrupt lock) throw a
|
|
452
583
|
* structured {@link SdkError}. A per-sheet failure (a locale not in config, a broken-round-trip key,
|
|
453
584
|
* a write failure) is isolated as that locale's `status: "failed"`, not a throw; per-row rejections
|
|
454
|
-
* are withheld and reported on the locale
|
|
455
|
-
*
|
|
585
|
+
* are withheld and reported on the locale. Dry-run validates and reports without writing any locale or
|
|
586
|
+
* lock file.
|
|
456
587
|
*
|
|
457
588
|
* @param input - The validated config, the workbook path, and run options.
|
|
458
589
|
* @param deps - Optional composition seams (registry, file system) for tests.
|
|
@@ -461,6 +592,28 @@ interface ImportWorkbookDeps {
|
|
|
461
592
|
*/
|
|
462
593
|
declare function importWorkbook(input: ImportWorkbookInput, deps?: ImportWorkbookDeps): Promise<RunSummary>;
|
|
463
594
|
|
|
595
|
+
/**
|
|
596
|
+
* Read-only metadata the CLI `init` scaffold derives its tables from, so the CLI never restates the
|
|
597
|
+
* provider, env-var, model, or format truth owned by core and ai-providers.
|
|
598
|
+
*/
|
|
599
|
+
declare const scaffoldingMetadata: {
|
|
600
|
+
/** Provider id -> the environment variable its API key is read from. Owned by ai-providers. */
|
|
601
|
+
readonly providerEnv: {
|
|
602
|
+
readonly anthropic: "ANTHROPIC_API_KEY";
|
|
603
|
+
readonly openai: "OPENAI_API_KEY";
|
|
604
|
+
readonly gemini: "GEMINI_API_KEY";
|
|
605
|
+
readonly deepl: "DEEPL_API_KEY";
|
|
606
|
+
};
|
|
607
|
+
/** LLM provider id -> a cosmetic default scaffold model. Owned by ai-providers. DeepL has none. */
|
|
608
|
+
readonly scaffoldModels: {
|
|
609
|
+
readonly anthropic: "claude-sonnet-4-6";
|
|
610
|
+
readonly openai: "gpt-5.4-mini";
|
|
611
|
+
readonly gemini: "gemini-2.5-flash";
|
|
612
|
+
};
|
|
613
|
+
/** The closed set of source format ids. Owned by core. */
|
|
614
|
+
readonly supportedFormats: readonly ["i18next-json", "vue-i18n-json", "next-intl-json", "ngx-translate-json", "xliff", "yaml", "arb"];
|
|
615
|
+
};
|
|
616
|
+
|
|
464
617
|
/** A minimal source-change event source. Production wraps chokidar; tests inject a stub. */
|
|
465
618
|
interface Watcher {
|
|
466
619
|
/** Register a listener invoked once per coalesced source-change event. */
|
|
@@ -470,7 +623,7 @@ interface Watcher {
|
|
|
470
623
|
}
|
|
471
624
|
/** Builds a {@link Watcher} for the given paths; the seam production fills with chokidar. */
|
|
472
625
|
type CreateWatcher = (paths: readonly string[]) => Watcher;
|
|
473
|
-
/** The run a watch trigger performs: the
|
|
626
|
+
/** The run a watch trigger performs: the one-shot translate, unchanged. */
|
|
474
627
|
type RunTranslate = (input: TranslateInput) => Promise<RunSummary>;
|
|
475
628
|
/** The outcome of one run, surfaced to the caller; never carries a secret. */
|
|
476
629
|
type WatchRunResult = {
|
|
@@ -517,16 +670,11 @@ interface WatchController {
|
|
|
517
670
|
stop(): Promise<void>;
|
|
518
671
|
}
|
|
519
672
|
/**
|
|
520
|
-
* Start watching the source file and re-run the one-shot translate on each debounced change.
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
* watching continues. Returns a controller whose stop() closes the watcher and awaits the in-flight
|
|
526
|
-
* run (the caller wires any signal, e.g. SIGINT, to it).
|
|
527
|
-
*
|
|
528
|
-
* Only the startup source check throws; every run outcome after start is surfaced through `onRun` as a
|
|
529
|
-
* {@link WatchRunResult} (a failed run never rejects the in-flight promise).
|
|
673
|
+
* Start watching the source file and re-run the one-shot {@link translate} on each debounced change.
|
|
674
|
+
* Runs are serialized: changes during a run collapse into a single follow-up, so two runs never
|
|
675
|
+
* overlap. A missing source at startup throws; every run outcome after start is surfaced through
|
|
676
|
+
* `onRun` as a {@link WatchRunResult} and watching continues. Returns a controller whose `stop()`
|
|
677
|
+
* closes the watcher and awaits the in-flight run.
|
|
530
678
|
*
|
|
531
679
|
* @param input - The config, optional cwd/debounce, and the `onRun` callback that receives each result.
|
|
532
680
|
* @param deps - Optional composition seams (watcher, run, registry, provider builder, file system) for tests.
|
|
@@ -558,4 +706,4 @@ interface WatchController {
|
|
|
558
706
|
*/
|
|
559
707
|
declare function watch(input: WatchInput, deps?: WatchDeps): Promise<WatchController>;
|
|
560
708
|
|
|
561
|
-
export { type CreateProvider, type CreateWatcher, DEFAULT_WORKBOOK_PATH, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type ImportWorkbookDeps, type ImportWorkbookInput, type LoadConfigOptions, type LocaleSummary, type ProviderConfig, type ProviderId, type RunSummary, type RunTranslate, SdkError, type SdkErrorCode, type SdkFs, type TranslateDeps, type TranslateInput, type VerbatraConfig, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, defineConfig, exportWorkbook, importWorkbook, loadConfig, translate, verbatraConfigSchema, watch };
|
|
709
|
+
export { type CheckDeps, type CheckInput, type CheckSummary, type CreateProvider, type CreateWatcher, DEFAULT_WORKBOOK_PATH, type DiffDeps, type DiffInput, type DiffSummary, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type ImportWorkbookDeps, type ImportWorkbookInput, type LoadConfigOptions, type LocaleCheckSummary, type LocaleDiff, type LocaleNotice, type LocaleSummary, type ProviderConfig, type ProviderId, type RunSummary, type RunTranslate, SdkError, type SdkErrorCode, type SdkFs, type SdkNotice, type SdkNoticeCode, type TranslateDeps, type TranslateInput, type VerbatraConfig, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, check, defineConfig, diff, exportWorkbook, importWorkbook, loadConfig, scaffoldingMetadata, translate, verbatraConfigSchema, watch };
|