vbss-translator 1.0.2 → 1.1.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,171 +1,541 @@
1
- # vbss-translator
2
-
3
- A lightweight and customizable React translation hook to easily handle multilingual websites. Includes support for browser language auto-detection, local persistence of language, and customizable translation files.
4
-
5
- ---
6
-
7
- ## Features
8
-
9
- - Auto-detect browser language to set the initial language.
10
- - Persist selected language in local storage.
11
- - Customizable and dynamic translation keys.
12
- - Minimalistic integration with React via useTranslator hook.
13
- - Flexible and developer-friendly API.
14
-
15
- ---
16
-
17
- ## Installation
18
-
19
- Install the package using npm:
20
-
21
- ```bash
22
- npm install vbss-translator
23
- ```
24
-
25
- or yarn:
26
-
27
- ```bash
28
- yarn add vbss-translator
29
- ```
30
-
31
- or Github Registry:
32
-
33
- ```bash
34
- npm install @vbss-io/vbss-translator@0.0.1
35
- ```
36
-
37
- *Github Registry may cause incompatibility with npm registry.*
38
-
39
- ---
40
-
41
- ## Usage
42
-
43
- ### Setup the `TranslatorProvider`
44
-
45
- Create a JSON file for translations. For example: `translations.json`
46
-
47
- ```json
48
- [
49
- {
50
- "en": "Hello",
51
- "pt": "Olá"
52
- },
53
- {
54
- "en": "Goodbye",
55
- "pt": "Adeus"
56
- }
57
- ]
58
- ```
59
-
60
- Wrap your application with the TranslatorProvider to make the translator context available throughout your app.
61
-
62
- ```typescript
63
- import translations from './translations.json';
64
-
65
- ReactDOM.render(
66
- <TranslatorProvider translations={translations}>
67
- <App />
68
- </TranslatorProvider>,
69
- document.getElementById('root')
70
- );
71
- ```
72
-
73
- ### Use the `useTranslator` Hook
74
-
75
- The useTranslator hook provides access to the t function for translations and methods to manage the current language.
76
-
77
- Example Usage in Components:
78
-
79
- ```typescript
80
- import React from 'react';
81
- import { useTranslator } from 'react-simple-translator';
82
-
83
- const ExampleComponent = () => {
84
- const { t, setLanguage, language } = useTranslator();
85
-
86
- return (
87
- <div>
88
- <h1>{t('Olá')}</h1>
89
- <p>Current Language: {language}</p>
90
- <button onClick={() => setLanguage('en')}>English</button>
91
- <button onClick={() => setLanguage('pt')}>Português</button>
92
- </div>
93
- );
94
- };
95
-
96
- export default ExampleComponent;
97
- ```
98
-
99
- ### Auto-Detect Browser Language
100
-
101
- If you pass the autoDetectLanguage prop to TranslatorProvider, the library will detect the user's browser language automatically. The fallback language will be the defaultLanguage.
102
-
103
- ```typescript
104
- ReactDOM.render(
105
- <TranslatorProvider
106
- translations={translations}
107
- defaultLanguage="en"
108
- autoDetectLanguage
109
- >
110
- <App />
111
- </TranslatorProvider>,
112
- document.getElementById('root')
113
- );
114
- ```
115
-
116
- For example:
117
-
118
- - Browser language: pt-BR
119
- - Default language: en
120
-
121
- The app will automatically use English as default if not provided or browser language not available.
122
-
123
- ### Persisting the Selected Language
124
-
125
- To persist the selected language across page reloads, use the persist as true and the optional persistKey prop. This will store the selected language in localStorage under the specified key.
126
-
127
- ```typescript
128
- ReactDOM.render(
129
- <TranslatorProvider
130
- translations={translations}
131
- persist
132
- persistKey="myAppLanguage"
133
- >
134
- <App />
135
- </TranslatorProvider>,
136
- document.getElementById('root')
137
- );
138
- ```
139
-
140
- ---
141
-
142
- ### **`TranslatorProvider` Props**
143
-
144
- |Prop|Type|Default Value|Description|
145
- |---|---|---|---|
146
- |`translations`|`Record<string>[]`|Required|The translations JSON file or object.|
147
- |`defaultLanguage`|`string`|`'en'`|The fallback/default language.|
148
- |`autoDetectLanguage`|`boolean`|`false`|If true, detects the user's browser language automatically.|
149
- |`persist`|`boolean`|`false`|If provided, persists the language in `localStorage` with persistKey.|
150
- |`persistKey`|`string`|`language`|If provided, customize the `localStorage` key.|
151
-
152
- ### **`useTranslator` Hook**
153
-
154
- The `useTranslator` hook provides the following:
155
-
156
- |Property|Type|Description|
157
- |---|---|---|
158
- |`t`|`(text: string) => string`|Function to get the translated text for a given key.|
159
- |`language`|`string`|The currently active language.|
160
- |`languages`|`string[]`|The currently available languages.|
161
- |`setLanguage`|`(lang: string) => void`|Function to change the current language.|
162
-
163
- ---
164
-
165
- ## Feedback
166
-
167
- If you enjoy using this package or find any issues, please give us a ⭐ on GitHub or open an issue. We appreciate your support! 🚀
168
-
169
- ## Contributing
170
-
171
- We welcome contributions! Feel free to open issues or submit pull requests.
1
+ # vbss-translator
2
+
3
+ A lightweight React translation toolkit focused on ergonomics, sensible defaults, and escape hatches when local dictionaries are not enough. Ship multilingual web apps with a context-driven provider, `useTranslator` hook, and a CLI that keeps translation indexes in sync.
4
+
5
+ ## Support the Project
6
+
7
+ Help us keep vbss-translator free and maintained:
8
+
9
+ - Buy me a coffee: [buymeacoffee.com/vbss.io](https://www.buymeacoffee.com/vbss.io)
10
+ - Star on GitHub: [github.com/vbss-io/vbss-translator](https://github.com/vbss-io/vbss-translator)
11
+ - Share the tool: [ui.vbss.io/tools/vbss-translator](https://ui.vbss.io/tools/vbss-translator)
12
+
13
+ ---
14
+
15
+ ## Feature Highlights
16
+
17
+ - React context + hook with zero-config setup
18
+ - Auto-detect browser language and persist selections
19
+ - External fallback (Google Translate out-of-the-box, custom providers supported) with caching, dedupe, veto hooks, and structured logs
20
+ - Translation status flags for fine-grained loading states
21
+ - Programmatic translation generator CLI with watch mode and rich validation
22
+ - Battle-tested with Jest + React Testing Library
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install vbss-translator
30
+ # or
31
+ yarn add vbss-translator
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Quick Start
37
+
38
+ 1. Create a translation file (`src/translations.json`):
39
+
40
+ ```json
41
+ [
42
+ { "en": "Hello", "pt": "Olá" },
43
+ { "en": "Goodbye", "pt": "Adeus" }
44
+ ]
45
+ ```
46
+
47
+ 1. Mount the provider in your app:
48
+
49
+ ```typescript
50
+ import ReactDOM from "react-dom";
51
+ import translations from "./translations.json" assert { type: "json" };
52
+ import { TranslatorProvider } from "vbss-translator";
53
+
54
+ ReactDOM.render(
55
+ <TranslatorProvider translations={translations}>
56
+ <App />
57
+ </TranslatorProvider>,
58
+ document.getElementById("root")
59
+ );
60
+ ```
61
+
62
+ 1. Consume translations with the hook:
63
+
64
+ ```typescript
65
+ import { useTranslator } from "vbss-translator";
66
+
67
+ export function Greeting() {
68
+ const { t, language, setLanguage } = useTranslator();
69
+
70
+ return (
71
+ <>
72
+ <h1>{t("Hello")}</h1>
73
+ <p>Currently showing: {language}</p>
74
+ <button onClick={() => setLanguage("en")}>English</button>
75
+ <button onClick={() => setLanguage("pt")}>Português</button>
76
+ </>
77
+ );
78
+ }
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Local Translations & Matching Rules
84
+
85
+ - `translations` must be an array of objects where every object uses the same language keys.
86
+ - When `t(text)` is called, the provider performs a **case-insensitive match across every value** in the translation array. The first record containing that value becomes the source dictionary entry.
87
+ - Given the matched entry:
88
+ - The translation for the active language is returned if available.
89
+ - Fallback order: explicit `fallbackValue` → cached external value → first non-empty value in the entry → the original input string.
90
+
91
+ This means you can seed your UI with any language copy (`t("Olá")`) as long as the entry exists with consistent language keys.
92
+
93
+ ---
94
+
95
+ ## Managing Languages
96
+
97
+ | Capability | How it works |
98
+ | --- | --- |
99
+ | Default language | `defaultLanguage` prop (defaults to `en`). |
100
+ | Auto-detect browser language | Set `autoDetectLanguage`. The navigator language (e.g. `pt-BR`) is simplified to its base (`pt`) before lookup. Falls back to `defaultLanguage` if missing. |
101
+ | Persist between reloads | Enable `persist`. The active language is stored under `persistKey` (defaults to `language`) in `localStorage`. |
102
+
103
+ Language changes happen inside a React transition to keep UI responsive.
104
+
105
+ ---
106
+
107
+ ## `TranslateOptions`
108
+
109
+ Pass options to `t(key, options)` for scoped behaviour:
110
+
111
+ | Option | Type | Description |
112
+ | --- | --- | --- |
113
+ | `preferExternal` | `boolean` | Force an external translation even if a local translation exists. |
114
+ | `sourceLanguage` | `string` | Explicit source language when translating externally. If omitted, the provider tries to use `defaultLanguage` when available. |
115
+ | `fallbackValue` | `string` | UI text to show until a translation resolves (useful for skeletons/placeholders). |
116
+ | `signal` | `AbortSignal` | Cancels the external request via the underlying provider. |
117
+
118
+ ---
119
+
120
+ ## External Translation Pipeline
121
+
122
+ External translation is **disabled by default**. Enable it by passing `externalTranslation={{ enabled: true }}` with your provider configuration (e.g., Google Translate).
123
+
124
+ ### Key Concepts
125
+
126
+ - **Always external keys**: Strings registered via `registerExternalKey(key)` or declared in `externalTranslation.alwaysExternalKeys` skip local dictionaries and go straight to the provider.
127
+ - **Status tracking**:
128
+ - `isTranslatingAny`: `true` when any external request is running.
129
+ - `isTranslating["your-key::pt"]`: `true` while the specific key/language pair is pending.
130
+ - **Retry window**: Failed external requests enter an error state and are retried after 30 seconds when requested again.
131
+
132
+ ### Configuration Surface
133
+
134
+ ```typescript
135
+ const externalTranslation = {
136
+ enabled: true,
137
+ timeoutMs: 5_000,
138
+ debug: false,
139
+ provider: {
140
+ id: "google",
141
+ apiKey: process.env.GOOGLE_TRANSLATE_KEY,
142
+ endpoint: "https://translation.googleapis.com/language/translate/v2",
143
+ },
144
+ cache: {
145
+ enabled: true,
146
+ ttlMs: 30 * 60 * 1000,
147
+ maxEntries: 500,
148
+ },
149
+ glossary: {
150
+ // Optional terminology map forwarded to providers that support glossaries
151
+ BRAND_A: "Marca A",
152
+ },
153
+ alwaysExternalKeys: ["product.description"],
154
+ shouldTranslate: ({ key, text }) => !text.includes("SECRET"),
155
+ onExternalTranslation: ({ key, text }) => {
156
+ console.info("sending text to provider", { key, text });
157
+ // Return false (or a resolved Promise) to veto the request.
158
+ },
159
+ onTranslationError: ({ key, language, error }) => {
160
+ console.warn("translation failed", { key, language, error });
161
+ },
162
+ onTranslationComplete: (result) => {
163
+ console.log("external result", result.translatedText);
164
+ },
165
+ };
166
+ ```
167
+
168
+ | Field | Type | Default | Notes |
169
+ | --- | --- | --- | --- |
170
+ | `enabled` | `boolean` | `false` | Master switch for the entire pipeline. |
171
+ | `timeoutMs` | `number` | `5_000` | Max duration before aborting a request. Exposed to provider via `AbortController`. |
172
+ | `debug` | `boolean` | `false` | Emits structured logs for cache hits, deduped requests, retries, etc. |
173
+ | `provider` | `ProviderConfig` | `{ id: "google" }` | Google or custom provider config. See provider sections below. |
174
+ | `cache` | `CacheConfig` | `{ enabled: false, ttlMs: 3_600_000 }` | In-memory cache with TTL and optional LRU size limit (`maxEntries`). |
175
+ | `glossary` | `Record<string, string>` | `undefined` | Optional term overrides sent when the provider supports them. |
176
+ | `alwaysExternalKeys` | `ReadonlySet` | `new Set()` | Automatically merged with strings registered at runtime. |
177
+ | `shouldTranslate` | `(request) => boolean` | `undefined` | Synchronous guard invoked before caching/dedup. Exceptions default to `true`. |
178
+ | `onExternalTranslation` | `(request) => void \| boolean` | `undefined` | Async-friendly hook after `shouldTranslate` but before the network call. Returning `false` cancels the request. |
179
+ | `onTranslationError` | `(event) => void` | `undefined` | Receives normalized provider errors with retry metadata. |
180
+ | `onTranslationComplete` | `(result) => void` | `undefined` | Fires after a successful response and cache write. |
181
+
182
+ ### Provider Behaviour
183
+
184
+ - The Google provider (`src/external/providers/googleTranslateProvider.ts`) constructs REST calls to the v2 API, supports Glossaries, forwards custom headers, and normalizes errors (incl. retryable codes).
185
+ - Providers may implement `normalizeError` to produce structured failures consumed by the manager.
186
+ - `ExternalTranslationManager` dedupes identical requests, enforces `timeoutMs`, respects `AbortSignal`, handles cache reads/writes, and never throws back into your components. All errors are converted into loggable events and surfaced via callbacks.
187
+
188
+ ### Custom Translation Providers
189
+
190
+ Beyond Google Translate, you can supply your own translation implementation by configuring a `custom` provider. Custom providers must satisfy the `TranslationProvider` contract, ensuring compatibility with caching, error handling, and instrumentation without additional adapters.
191
+
192
+ #### Registering a Custom Provider
193
+
194
+ Supply either an `implementation` (a pre-built provider instance) or a `factory` (a function returning a provider) in your configuration:
195
+
196
+ ```typescript
197
+ import {
198
+ TranslatorProvider,
199
+ type TranslationProvider,
200
+ type TranslateRequest,
201
+ type TranslateResult,
202
+ } from "vbss-translator";
203
+
204
+ // Custom provider implementation
205
+ const myCustomProvider: TranslationProvider = {
206
+ type: "custom",
207
+ checkAvailability: async () => ({ available: true }),
208
+ translate: async (request: TranslateRequest): Promise<TranslateResult> => {
209
+ // Your custom translation logic here
210
+ const response = await fetch("https://my-translation-api.com/translate", {
211
+ method: "POST",
212
+ headers: { "Content-Type": "application/json" },
213
+ body: JSON.stringify({
214
+ text: request.text,
215
+ source: request.sourceLanguage,
216
+ target: request.targetLanguage,
217
+ }),
218
+ });
219
+ const data = await response.json();
220
+ return {
221
+ translatedText: data.translation,
222
+ detectedSourceLanguage: request.sourceLanguage,
223
+ };
224
+ },
225
+ };
226
+
227
+ <TranslatorProvider
228
+ translations={translations}
229
+ externalTranslation={{
230
+ enabled: true,
231
+ provider: {
232
+ id: "custom",
233
+ implementation: myCustomProvider,
234
+ },
235
+ cache: { enabled: true, ttlMs: 1800000 },
236
+ }}
237
+ >
238
+ <App />
239
+ </TranslatorProvider>
240
+ ```
241
+
242
+ #### Using a Provider Factory
243
+
244
+ For scenarios requiring initialization logic or dependency injection, supply a factory function:
245
+
246
+ ```typescript
247
+ const providerFactory = () => {
248
+ const apiKey = process.env.CUSTOM_TRANSLATION_KEY;
249
+ const endpoint = process.env.CUSTOM_TRANSLATION_ENDPOINT;
250
+
251
+ return {
252
+ type: "custom",
253
+ checkAvailability: async () => {
254
+ if (!apiKey || !endpoint) {
255
+ return { available: false, reason: "Missing configuration" };
256
+ }
257
+ return { available: true };
258
+ },
259
+ translate: async (request: TranslateRequest): Promise<TranslateResult> => {
260
+ const response = await fetch(endpoint, {
261
+ method: "POST",
262
+ headers: {
263
+ "Authorization": `Bearer ${apiKey}`,
264
+ "Content-Type": "application/json",
265
+ },
266
+ body: JSON.stringify({
267
+ text: request.text,
268
+ from: request.sourceLanguage,
269
+ to: request.targetLanguage,
270
+ glossary: request.glossary,
271
+ }),
272
+ });
273
+ const data = await response.json();
274
+ return {
275
+ translatedText: data.result,
276
+ providerMetadata: { provider: "custom" },
277
+ };
278
+ },
279
+ };
280
+ };
281
+
282
+ <TranslatorProvider
283
+ translations={translations}
284
+ externalTranslation={{
285
+ enabled: true,
286
+ provider: {
287
+ id: "custom",
288
+ factory: providerFactory,
289
+ },
290
+ }}
291
+ >
292
+ <App />
293
+ </TranslatorProvider>
294
+ ```
295
+
296
+ #### Custom Provider Contract
297
+
298
+ Your implementation must satisfy the `TranslationProvider` interface:
299
+
300
+ ```typescript
301
+ interface TranslationProvider {
302
+ type: string;
303
+ checkAvailability: () => Promise<ProviderAvailability>;
304
+ translate: (request: TranslateRequest) => Promise<TranslateResult>;
305
+ normalizeError?: (error: unknown) => ProviderError;
306
+ }
307
+ ```
308
+
309
+ - `type`: String identifier for your provider (typically `"custom"`).
310
+ - `checkAvailability`: Validates provider readiness (e.g., credentials, network).
311
+ - `translate`: Accepts `TranslateRequest` (text, source/target languages, optional glossary) and returns `TranslateResult` (translated text, optional metadata).
312
+ - `normalizeError` (optional): Converts provider-specific errors into structured `ProviderError` with retryable flags.
313
+
314
+ #### Switching Between Providers
315
+
316
+ Toggle between Google and custom providers without changing downstream code:
317
+
318
+ ```typescript
319
+ // Use Google Translate
320
+ const googleConfig = {
321
+ enabled: true,
322
+ provider: {
323
+ id: "google",
324
+ apiKey: process.env.GOOGLE_TRANSLATE_KEY,
325
+ },
326
+ };
327
+
328
+ // Use custom provider
329
+ const customConfig = {
330
+ enabled: true,
331
+ provider: {
332
+ id: "custom",
333
+ implementation: myCustomProvider,
334
+ },
335
+ };
336
+
337
+ // Select provider at runtime
338
+ const activeConfig = useGoogleTranslate ? googleConfig : customConfig;
339
+
340
+ <TranslatorProvider
341
+ translations={translations}
342
+ externalTranslation={activeConfig}
343
+ >
344
+ <App />
345
+ </TranslatorProvider>
346
+ ```
347
+
348
+ #### Testing Custom Providers
349
+
350
+ Validate your custom provider before production:
351
+
352
+ ```typescript
353
+ import { createTranslationProvider } from "vbss-translator/factory";
354
+
355
+ const provider = createTranslationProvider({
356
+ id: "custom",
357
+ implementation: myCustomProvider,
358
+ });
359
+
360
+ // Test availability
361
+ const availability = await provider.checkAvailability();
362
+ console.log("Provider available:", availability.available);
363
+
364
+ // Test translation
365
+ const result = await provider.translate({
366
+ text: "Hello",
367
+ sourceLanguage: "en",
368
+ targetLanguage: "pt",
369
+ });
370
+ console.log("Translation:", result.translatedText);
371
+ ```
372
+
373
+ Custom providers integrate seamlessly with the existing cache, logging, and callback infrastructure. All `shouldTranslate`, `onExternalTranslation`, `onTranslationError`, and `onTranslationComplete` hooks work identically regardless of the active provider.
374
+
375
+ ### Cache Lifecycle
376
+
377
+ - Cache entries are stored in-memory only.
378
+ - `TranslationCache` enforces TTL and `maxEntries` (evicts oldest first).
379
+ - `cache.enabled = false` effectively turns the cache into a no-op.
380
+ - Debug logs show cache hits/misses when `debug` is enabled.
381
+
382
+ ---
383
+
384
+ ## `TranslatorProvider` Props
385
+
386
+ | Prop | Type | Default | Description |
387
+ | --- | --- | --- | --- |
388
+ | `translations` | `Translation[]` | required | Array of translation records. |
389
+ | `defaultLanguage` | `string` | `"en"` | Fallback when a translation is missing or auto-detect fails. |
390
+ | `autoDetectLanguage` | `boolean` | `false` | Use the browser language (base locale) as the initial language. |
391
+ | `persist` | `boolean` | `false` | Persist language to `localStorage`. |
392
+ | `persistKey` | `string` | `"language"` | Storage key used when `persist` is `true`. |
393
+ | `externalTranslation` | `ExternalTranslationConfigInput` | Disabled by default | External translation behaviour, provider credentials, hooks, and logging. |
394
+
395
+ `TranslatorProvider` exposes a resolved `externalConfig` through context so you can inspect runtime settings (e.g., toggled cache state).
396
+
397
+ ---
398
+
399
+ ## `useTranslator` API
400
+
401
+ | Property | Type | Description |
402
+ | --- | --- | --- |
403
+ | `t` | `(text: string, options?: TranslateOptions) => string` | Translate text using local dictionaries + external fallback when needed. |
404
+ | `language` | `string` | Currently active language. |
405
+ | `languages` | `string[]` | Languages derived from the first translation entry. |
406
+ | `setLanguage` | `(lang: string) => void` | Switch languages and persist if enabled. |
407
+ | `isTranslating` | `Record<string, boolean>` | Map keyed by `text::language` showing pending external requests. |
408
+ | `isTranslatingAny` | `boolean` | `true` when any external request is running. |
409
+ | `registerExternalKey` | `(key: string) => void` | Opt a specific string into the external pathway up front. |
410
+ | `externalConfig` | `ExternalTranslationConfig` | Read-only resolved configuration (includes merged `alwaysExternalKeys`). |
411
+
412
+ ### Pattern: Preferring External Translation Per Call
413
+
414
+ ```typescript
415
+ const abortController = new AbortController();
416
+ const { t } = useTranslator();
417
+ const description = t("Our newest product line", {
418
+ preferExternal: true,
419
+ fallbackValue: "Loading description…",
420
+ signal: abortController.signal,
421
+ });
422
+ ```
423
+
424
+ If the external call fails, the original copy is returned and a retry is attempted on subsequent calls after the cooldown window.
425
+
426
+ ---
427
+
428
+ ## CLI & Programmatic Generator
429
+
430
+ Generate a typed translation index (or plain JS) from scattered JSON files. The CLI orchestrates discovery, validation, deduplication, and file writing.
431
+
432
+ ### Command Reference
433
+
434
+ ```bash
435
+ npx vbss-translator generate [--pattern <glob>] [--output <path>] [--format <ts|js|tsx>] \
436
+ [--reference-language <lang>] [--config <path>] [--watch|-w]
437
+ ```
438
+
439
+ | Flag | Description | Default |
440
+ | --- | --- | --- |
441
+ | `--pattern` | Glob for JSON sources. Resolved relative to `process.cwd()`. | `src/**/translations.json` |
442
+ | `--output` | Output file path. Parent directories are created automatically. | `src/translations/index.ts` |
443
+ | `--format` | Output format (`ts`, `js`, or `tsx`). | `ts` |
444
+ | `--reference-language` | Language key used for deduplication. | First language in the first valid file |
445
+ | `--config` | Path to `vbss-translator.config.json`. | Project root |
446
+ | `--watch`, `-w` | Watch mode with regeneration + debug logs. | Disabled |
447
+
448
+ Order of precedence: **CLI flags > config file > defaults**. Config parsing is performed by `src/cli/config.ts`.
449
+
450
+ ### Output Formats
451
+
452
+ - **ts / tsx**: Imports each JSON file with `assert { type: "json" }`, exports a `Translation` interface, merges arrays (wrapping standalone objects), dedupes using the reference language, and default-exports `uniqueTranslations`.
453
+ - **js**: Inlines JSON payloads directly into the generated file and performs the same deduplication logic without TypeScript types.
454
+
455
+ ### Validation Rules
456
+
457
+ The generator checks that:
458
+
459
+ - Every file parses as JSON (arrays or objects).
460
+ - Each translation record only contains string values.
461
+ - All entries share identical language keys.
462
+ - Language mismatches, missing translations, or file system errors are surfaced as structured `GenerationError`s.
463
+
464
+ Generation fails fast when validation errors occur; exit code `2` signals schema issues, while other failures exit with `1`.
465
+
466
+ ### Watch Mode
467
+
468
+ `npx vbss-translator generate --watch`:
469
+
470
+ - Runs an initial generation before watching.
471
+ - Uses native `fs.watch` with glob filtering to detect additions, changes, and deletions.
472
+ - Debounces rapid changes (300ms) and regenerates the output file.
473
+ - Keeps running until interrupted. Clean-up handlers close watchers on `SIGINT`/`SIGTERM`.
474
+ - Emits verbose debug logs to help diagnose path matching.
475
+
476
+ ### Programmatic API
477
+
478
+ ```typescript
479
+ import { generate } from "vbss-translator/generator";
480
+ import type { GeneratorOptions } from "vbss-translator/generator";
481
+
482
+ const result = await generate({
483
+ pattern: "src/**/translations.json",
484
+ outputPath: "src/translations/index.ts",
485
+ outputFormat: "ts",
486
+ referenceLanguage: "en",
487
+ });
488
+
489
+ if (!result.success) {
490
+ console.error("Generation failed", result.errors);
491
+ }
492
+ ```
493
+
494
+ `generate` returns a `GenerationResult` containing success flag, number of files discovered, number of deduplicated translations, accumulated errors, and the output path. The programmatic API shares the same pipeline as the CLI (discovery, validation, dedupe, and writing).
495
+
496
+ ---
497
+
498
+ ## Translation File Requirements
499
+
500
+ 1. **Single object**
501
+
502
+ ```json
503
+ { "en": "Hello", "pt": "Olá", "es": "Hola" }
504
+ ```
505
+
506
+ 1. **Array of objects**
507
+
508
+ ```json
509
+ [
510
+ { "en": "Hello", "pt": "Olá" },
511
+ { "en": "World", "pt": "Mundo" }
512
+ ]
513
+ ```
514
+
515
+ Rules enforced by the generator:
516
+
517
+ - Every entry must use the same set of language keys.
518
+ - Every value must be a string.
519
+ - Files must be valid JSON (syntax errors are reported).
520
+
521
+ > These validations are applied when you run the CLI or programmatic generator. Passing your own `Translation[]` straight into `TranslatorProvider` skips these checks, so validate manually if you craft arrays by hand.
522
+
523
+ ---
524
+
525
+ ## Debugging & Best Practices
526
+
527
+ - Enable `externalTranslation.debug` during development to track cache hits, deduped requests, vetoes, and timing information. Logs are tagged with `[vbss-translator]`.
528
+ - Register sensitive copy via `registerExternalKey` only after ensuring `shouldTranslate` and `onExternalTranslation` mask or skip secrets.
529
+ - Use `isTranslating` to show per-string loading indicators without blocking initial UI.
530
+ - In CI, run `npx vbss-translator generate` to validate translation files early and fail builds on schema drift.
531
+ - Version-control generated translation indexes so production builds and CI remain deterministic.
532
+
533
+ ---
534
+
535
+ ## Feedback & Contributing
536
+
537
+ We love hearing from you! If vbss-translator helps your team, please ⭐ the repo or share feedback.
538
+
539
+ - GitHub: [github.com/vbss-io/vbss-translator](https://github.com/vbss-io/vbss-translator)
540
+
541
+ 🚀 Happy shipping!