{"id":23496416,"url":"https://github.com/vbss-io/vbss-translator","last_synced_at":"2026-02-07T09:31:24.722Z","repository":{"id":268698212,"uuid":"904666519","full_name":"vbss-io/vbss-translator","owner":"vbss-io","description":"A lightweight and customizable React translation hook for multilingual websites. Features include browser language auto-detection, local language persistence, dynamic translation keys, and an easy-to-use API.","archived":false,"fork":false,"pushed_at":"2025-11-11T23:38:51.000Z","size":236,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-12T01:18:20.524Z","etag":null,"topics":["hooks","internationalization","language-switcher","localization","react","react-hooks","translation","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vbss-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-12-17T10:24:38.000Z","updated_at":"2025-01-04T11:45:37.000Z","dependencies_parsed_at":"2024-12-18T11:43:05.529Z","dependency_job_id":"1dfd2146-4715-4c91-9ea3-6e2d0aedb85f","html_url":"https://github.com/vbss-io/vbss-translator","commit_stats":null,"previous_names":["vbss-io/vbss-translator"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vbss-io/vbss-translator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vbss-io%2Fvbss-translator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vbss-io%2Fvbss-translator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vbss-io%2Fvbss-translator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vbss-io%2Fvbss-translator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vbss-io","download_url":"https://codeload.github.com/vbss-io/vbss-translator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vbss-io%2Fvbss-translator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29191391,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T07:37:03.739Z","status":"ssl_error","status_checked_at":"2026-02-07T07:37:03.029Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hooks","internationalization","language-switcher","localization","react","react-hooks","translation","typescript"],"created_at":"2024-12-25T04:12:59.234Z","updated_at":"2026-02-07T09:31:24.715Z","avatar_url":"https://github.com/vbss-io.png","language":"TypeScript","funding_links":["https://www.buymeacoffee.com/vbss.io"],"categories":[],"sub_categories":[],"readme":"# vbss-translator\n\nA 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.\n\n## Support the Project\n\nHelp us keep vbss-translator free and maintained:\n\n- Buy me a coffee: [buymeacoffee.com/vbss.io](https://www.buymeacoffee.com/vbss.io)\n- Star on GitHub: [github.com/vbss-io/vbss-translator](https://github.com/vbss-io/vbss-translator)\n- Share the tool: [ui.vbss.io/tools/vbss-translator](https://ui.vbss.io/tools/vbss-translator)\n\n---\n\n## Feature Highlights\n\n- React context + hook with zero-config setup\n- Auto-detect browser language and persist selections\n- External fallback (Google Translate out-of-the-box, custom providers supported) with caching, dedupe, veto hooks, and structured logs\n- Translation status flags for fine-grained loading states\n- Programmatic translation generator CLI with watch mode and rich validation\n- Battle-tested with Jest + React Testing Library\n\n---\n\n## Installation\n\n```bash\nnpm install vbss-translator\n# or\nyarn add vbss-translator\n```\n\n---\n\n## Quick Start\n\n1. Create a translation file (`src/translations.json`):\n\n```json\n[\n  { \"en\": \"Hello\", \"pt\": \"Olá\" },\n  { \"en\": \"Goodbye\", \"pt\": \"Adeus\" }\n]\n```\n\n1. Mount the provider in your app:\n\n```typescript\nimport ReactDOM from \"react-dom\";\nimport translations from \"./translations.json\" assert { type: \"json\" };\nimport { TranslatorProvider } from \"vbss-translator\";\n\nReactDOM.render(\n  \u003cTranslatorProvider translations={translations}\u003e\n    \u003cApp /\u003e\n  \u003c/TranslatorProvider\u003e,\n  document.getElementById(\"root\")\n);\n```\n\n1. Consume translations with the hook:\n\n```typescript\nimport { useTranslator } from \"vbss-translator\";\n\nexport function Greeting() {\n  const { t, language, setLanguage } = useTranslator();\n\n  return (\n    \u003c\u003e\n      \u003ch1\u003e{t(\"Hello\")}\u003c/h1\u003e\n      \u003cp\u003eCurrently showing: {language}\u003c/p\u003e\n      \u003cbutton onClick={() =\u003e setLanguage(\"en\")}\u003eEnglish\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e setLanguage(\"pt\")}\u003ePortuguês\u003c/button\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n---\n\n## Local Translations \u0026 Matching Rules\n\n- `translations` must be an array of objects where every object uses the same language keys.\n- 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.\n- Given the matched entry:\n  - The translation for the active language is returned if available.\n  - Fallback order: explicit `fallbackValue` → cached external value → first non-empty value in the entry → the original input string.\n\nThis means you can seed your UI with any language copy (`t(\"Olá\")`) as long as the entry exists with consistent language keys.\n\n---\n\n## Managing Languages\n\n| Capability | How it works |\n| --- | --- |\n| Default language | `defaultLanguage` prop (defaults to `en`). |\n| 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. |\n| Persist between reloads | Enable `persist`. The active language is stored under `persistKey` (defaults to `language`) in `localStorage`. |\n\nLanguage changes happen inside a React transition to keep UI responsive.\n\n---\n\n## `TranslateOptions`\n\nPass options to `t(key, options)` for scoped behaviour:\n\n| Option | Type | Description |\n| --- | --- | --- |\n| `preferExternal` | `boolean` | Force an external translation even if a local translation exists. |\n| `sourceLanguage` | `string` | Explicit source language when translating externally. If omitted, the provider tries to use `defaultLanguage` when available. |\n| `fallbackValue` | `string` | UI text to show until a translation resolves (useful for skeletons/placeholders). |\n| `signal` | `AbortSignal` | Cancels the external request via the underlying provider. |\n\n---\n\n## External Translation Pipeline\n\nExternal translation is **disabled by default**. Enable it by passing `externalTranslation={{ enabled: true }}` with your provider configuration (e.g., Google Translate).\n\n### Key Concepts\n\n- **Always external keys**: Strings registered via `registerExternalKey(key)` or declared in `externalTranslation.alwaysExternalKeys` skip local dictionaries and go straight to the provider.\n- **Status tracking**:\n  - `isTranslatingAny`: `true` when any external request is running.\n  - `isTranslating[\"your-key::pt\"]`: `true` while the specific key/language pair is pending.\n- **Retry window**: Failed external requests enter an error state and are retried after 30 seconds when requested again.\n\n### Configuration Surface\n\n```typescript\nconst externalTranslation = {\n  enabled: true,\n  timeoutMs: 5_000,\n  debug: false,\n  provider: {\n    id: \"google\",\n    apiKey: process.env.GOOGLE_TRANSLATE_KEY,\n    endpoint: \"https://translation.googleapis.com/language/translate/v2\",\n  },\n  cache: {\n    enabled: true,\n    ttlMs: 30 * 60 * 1000,\n    maxEntries: 500,\n  },\n  glossary: {\n    // Optional terminology map forwarded to providers that support glossaries\n    BRAND_A: \"Marca A\",\n  },\n  alwaysExternalKeys: [\"product.description\"],\n  shouldTranslate: ({ key, text }) =\u003e !text.includes(\"SECRET\"),\n  onExternalTranslation: ({ key, text }) =\u003e {\n    console.info(\"sending text to provider\", { key, text });\n    // Return false (or a resolved Promise) to veto the request.\n  },\n  onTranslationError: ({ key, language, error }) =\u003e {\n    console.warn(\"translation failed\", { key, language, error });\n  },\n  onTranslationComplete: (result) =\u003e {\n    console.log(\"external result\", result.translatedText);\n  },\n};\n```\n\n| Field | Type | Default | Notes |\n| --- | --- | --- | --- |\n| `enabled` | `boolean` | `false` | Master switch for the entire pipeline. |\n| `timeoutMs` | `number` | `5_000` | Max duration before aborting a request. Exposed to provider via `AbortController`. |\n| `debug` | `boolean` | `false` | Emits structured logs for cache hits, deduped requests, retries, etc. |\n| `provider` | `ProviderConfig` | `{ id: \"google\" }` | Google or custom provider config. See provider sections below. |\n| `cache` | `CacheConfig` | `{ enabled: false, ttlMs: 3_600_000 }` | In-memory cache with TTL and optional LRU size limit (`maxEntries`). |\n| `glossary` | `Record\u003cstring, string\u003e` | `undefined` | Optional term overrides sent when the provider supports them. |\n| `alwaysExternalKeys` | `ReadonlySet` | `new Set()` | Automatically merged with strings registered at runtime. |\n| `shouldTranslate` | `(request) =\u003e boolean` | `undefined` | Synchronous guard invoked before caching/dedup. Exceptions default to `true`. |\n| `onExternalTranslation` | `(request) =\u003e void \\| boolean` | `undefined` | Async-friendly hook after `shouldTranslate` but before the network call. Returning `false` cancels the request. |\n| `onTranslationError` | `(event) =\u003e void` | `undefined` | Receives normalized provider errors with retry metadata. |\n| `onTranslationComplete` | `(result) =\u003e void` | `undefined` | Fires after a successful response and cache write. |\n\n### Provider Behaviour\n\n- 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).\n- Providers may implement `normalizeError` to produce structured failures consumed by the manager.\n- `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.\n\n### Custom Translation Providers\n\nBeyond 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.\n\n#### Registering a Custom Provider\n\nSupply either an `implementation` (a pre-built provider instance) or a `factory` (a function returning a provider) in your configuration:\n\n```typescript\nimport {\n  TranslatorProvider,\n  type TranslationProvider,\n  type TranslateRequest,\n  type TranslateResult,\n} from \"vbss-translator\";\n\n// Custom provider implementation\nconst myCustomProvider: TranslationProvider = {\n  type: \"custom\",\n  checkAvailability: async () =\u003e ({ available: true }),\n  translate: async (request: TranslateRequest): Promise\u003cTranslateResult\u003e =\u003e {\n    // Your custom translation logic here\n    const response = await fetch(\"https://my-translation-api.com/translate\", {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/json\" },\n      body: JSON.stringify({\n        text: request.text,\n        source: request.sourceLanguage,\n        target: request.targetLanguage,\n      }),\n    });\n    const data = await response.json();\n    return {\n      translatedText: data.translation,\n      detectedSourceLanguage: request.sourceLanguage,\n    };\n  },\n};\n\n\u003cTranslatorProvider\n  translations={translations}\n  externalTranslation={{\n    enabled: true,\n    provider: {\n      id: \"custom\",\n      implementation: myCustomProvider,\n    },\n    cache: { enabled: true, ttlMs: 1800000 },\n  }}\n\u003e\n  \u003cApp /\u003e\n\u003c/TranslatorProvider\u003e\n```\n\n#### Using a Provider Factory\n\nFor scenarios requiring initialization logic or dependency injection, supply a factory function:\n\n```typescript\nconst providerFactory = () =\u003e {\n  const apiKey = process.env.CUSTOM_TRANSLATION_KEY;\n  const endpoint = process.env.CUSTOM_TRANSLATION_ENDPOINT;\n\n  return {\n    type: \"custom\",\n    checkAvailability: async () =\u003e {\n      if (!apiKey || !endpoint) {\n        return { available: false, reason: \"Missing configuration\" };\n      }\n      return { available: true };\n    },\n    translate: async (request: TranslateRequest): Promise\u003cTranslateResult\u003e =\u003e {\n      const response = await fetch(endpoint, {\n        method: \"POST\",\n        headers: {\n          \"Authorization\": `Bearer ${apiKey}`,\n          \"Content-Type\": \"application/json\",\n        },\n        body: JSON.stringify({\n          text: request.text,\n          from: request.sourceLanguage,\n          to: request.targetLanguage,\n          glossary: request.glossary,\n        }),\n      });\n      const data = await response.json();\n      return {\n        translatedText: data.result,\n        providerMetadata: { provider: \"custom\" },\n      };\n    },\n  };\n};\n\n\u003cTranslatorProvider\n  translations={translations}\n  externalTranslation={{\n    enabled: true,\n    provider: {\n      id: \"custom\",\n      factory: providerFactory,\n    },\n  }}\n\u003e\n  \u003cApp /\u003e\n\u003c/TranslatorProvider\u003e\n```\n\n#### Custom Provider Contract\n\nYour implementation must satisfy the `TranslationProvider` interface:\n\n```typescript\ninterface TranslationProvider {\n  type: string;\n  checkAvailability: () =\u003e Promise\u003cProviderAvailability\u003e;\n  translate: (request: TranslateRequest) =\u003e Promise\u003cTranslateResult\u003e;\n  normalizeError?: (error: unknown) =\u003e ProviderError;\n}\n```\n\n- `type`: String identifier for your provider (typically `\"custom\"`).\n- `checkAvailability`: Validates provider readiness (e.g., credentials, network).\n- `translate`: Accepts `TranslateRequest` (text, source/target languages, optional glossary) and returns `TranslateResult` (translated text, optional metadata).\n- `normalizeError` (optional): Converts provider-specific errors into structured `ProviderError` with retryable flags.\n\n#### Switching Between Providers\n\nToggle between Google and custom providers without changing downstream code:\n\n```typescript\n// Use Google Translate\nconst googleConfig = {\n  enabled: true,\n  provider: {\n    id: \"google\",\n    apiKey: process.env.GOOGLE_TRANSLATE_KEY,\n  },\n};\n\n// Use custom provider\nconst customConfig = {\n  enabled: true,\n  provider: {\n    id: \"custom\",\n    implementation: myCustomProvider,\n  },\n};\n\n// Select provider at runtime\nconst activeConfig = useGoogleTranslate ? googleConfig : customConfig;\n\n\u003cTranslatorProvider\n  translations={translations}\n  externalTranslation={activeConfig}\n\u003e\n  \u003cApp /\u003e\n\u003c/TranslatorProvider\u003e\n```\n\n#### Testing Custom Providers\n\nValidate your custom provider before production:\n\n```typescript\nimport { createTranslationProvider } from \"vbss-translator/factory\";\n\nconst provider = createTranslationProvider({\n  id: \"custom\",\n  implementation: myCustomProvider,\n});\n\n// Test availability\nconst availability = await provider.checkAvailability();\nconsole.log(\"Provider available:\", availability.available);\n\n// Test translation\nconst result = await provider.translate({\n  text: \"Hello\",\n  sourceLanguage: \"en\",\n  targetLanguage: \"pt\",\n});\nconsole.log(\"Translation:\", result.translatedText);\n```\n\nCustom 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.\n\n### Cache Lifecycle\n\n- Cache entries are stored in-memory only.\n- `TranslationCache` enforces TTL and `maxEntries` (evicts oldest first).\n- `cache.enabled = false` effectively turns the cache into a no-op.\n- Debug logs show cache hits/misses when `debug` is enabled.\n\n---\n\n## `TranslatorProvider` Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `translations` | `Translation[]` | required | Array of translation records. |\n| `defaultLanguage` | `string` | `\"en\"` | Fallback when a translation is missing or auto-detect fails. |\n| `autoDetectLanguage` | `boolean` | `false` | Use the browser language (base locale) as the initial language. |\n| `persist` | `boolean` | `false` | Persist language to `localStorage`. |\n| `persistKey` | `string` | `\"language\"` | Storage key used when `persist` is `true`. |\n| `externalTranslation` | `ExternalTranslationConfigInput` | Disabled by default | External translation behaviour, provider credentials, hooks, and logging. |\n\n`TranslatorProvider` exposes a resolved `externalConfig` through context so you can inspect runtime settings (e.g., toggled cache state).\n\n---\n\n## `useTranslator` API\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `t` | `(text: string, options?: TranslateOptions) =\u003e string` | Translate text using local dictionaries + external fallback when needed. |\n| `language` | `string` | Currently active language. |\n| `languages` | `string[]` | Languages derived from the first translation entry. |\n| `setLanguage` | `(lang: string) =\u003e void` | Switch languages and persist if enabled. |\n| `isTranslating` | `Record\u003cstring, boolean\u003e` | Map keyed by `text::language` showing pending external requests. |\n| `isTranslatingAny` | `boolean` | `true` when any external request is running. |\n| `registerExternalKey` | `(key: string) =\u003e void` | Opt a specific string into the external pathway up front. |\n| `externalConfig` | `ExternalTranslationConfig` | Read-only resolved configuration (includes merged `alwaysExternalKeys`). |\n\n### Pattern: Preferring External Translation Per Call\n\n```typescript\nconst abortController = new AbortController();\nconst { t } = useTranslator();\nconst description = t(\"Our newest product line\", {\n  preferExternal: true,\n  fallbackValue: \"Loading description…\",\n  signal: abortController.signal,\n});\n```\n\nIf the external call fails, the original copy is returned and a retry is attempted on subsequent calls after the cooldown window.\n\n---\n\n## CLI \u0026 Programmatic Generator\n\nGenerate a typed translation index (or plain JS) from scattered JSON files. The CLI orchestrates discovery, validation, deduplication, and file writing.\n\n### Command Reference\n\n```bash\nnpx vbss-translator generate [--pattern \u003cglob\u003e] [--output \u003cpath\u003e] [--format \u003cts|js|tsx\u003e] \\\n  [--reference-language \u003clang\u003e] [--config \u003cpath\u003e] [--watch|-w]\n```\n\n| Flag | Description | Default |\n| --- | --- | --- |\n| `--pattern` | Glob for JSON sources. Resolved relative to `process.cwd()`. | `src/**/translations.json` |\n| `--output` | Output file path. Parent directories are created automatically. | `src/translations/index.ts` |\n| `--format` | Output format (`ts`, `js`, or `tsx`). | `ts` |\n| `--reference-language` | Language key used for deduplication. | First language in the first valid file |\n| `--config` | Path to `vbss-translator.config.json`. | Project root |\n| `--watch`, `-w` | Watch mode with regeneration + debug logs. | Disabled |\n\nOrder of precedence: **CLI flags \u003e config file \u003e defaults**. Config parsing is performed by `src/cli/config.ts`.\n\n### Output Formats\n\n- **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`.\n- **js**: Inlines JSON payloads directly into the generated file and performs the same deduplication logic without TypeScript types.\n\n### Validation Rules\n\nThe generator checks that:\n\n- Every file parses as JSON (arrays or objects).\n- Each translation record only contains string values.\n- All entries share identical language keys.\n- Language mismatches, missing translations, or file system errors are surfaced as structured `GenerationError`s.\n\nGeneration fails fast when validation errors occur; exit code `2` signals schema issues, while other failures exit with `1`.\n\n### Watch Mode\n\n`npx vbss-translator generate --watch`:\n\n- Runs an initial generation before watching.\n- Uses native `fs.watch` with glob filtering to detect additions, changes, and deletions.\n- Debounces rapid changes (300ms) and regenerates the output file.\n- Keeps running until interrupted. Clean-up handlers close watchers on `SIGINT`/`SIGTERM`.\n- Emits verbose debug logs to help diagnose path matching.\n\n### Programmatic API\n\n```typescript\nimport { generate } from \"vbss-translator/generator\";\nimport type { GeneratorOptions } from \"vbss-translator/generator\";\n\nconst result = await generate({\n  pattern: \"src/**/translations.json\",\n  outputPath: \"src/translations/index.ts\",\n  outputFormat: \"ts\",\n  referenceLanguage: \"en\",\n});\n\nif (!result.success) {\n  console.error(\"Generation failed\", result.errors);\n}\n```\n\n`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).\n\n---\n\n## Translation File Requirements\n\n1. **Single object**\n\n   ```json\n   { \"en\": \"Hello\", \"pt\": \"Olá\", \"es\": \"Hola\" }\n   ```\n\n1. **Array of objects**\n\n   ```json\n   [\n     { \"en\": \"Hello\", \"pt\": \"Olá\" },\n     { \"en\": \"World\", \"pt\": \"Mundo\" }\n   ]\n   ```\n\nRules enforced by the generator:\n\n- Every entry must use the same set of language keys.\n- Every value must be a string.\n- Files must be valid JSON (syntax errors are reported).\n\n\u003e 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.\n\n---\n\n## Debugging \u0026 Best Practices\n\n- Enable `externalTranslation.debug` during development to track cache hits, deduped requests, vetoes, and timing information. Logs are tagged with `[vbss-translator]`.\n- Register sensitive copy via `registerExternalKey` only after ensuring `shouldTranslate` and `onExternalTranslation` mask or skip secrets.\n- Use `isTranslating` to show per-string loading indicators without blocking initial UI.\n- In CI, run `npx vbss-translator generate` to validate translation files early and fail builds on schema drift.\n- Version-control generated translation indexes so production builds and CI remain deterministic.\n\n---\n\n## Feedback \u0026 Contributing\n\nWe love hearing from you! If vbss-translator helps your team, please ⭐ the repo or share feedback.\n\n- GitHub: [github.com/vbss-io/vbss-translator](https://github.com/vbss-io/vbss-translator)\n\n🚀 Happy shipping!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvbss-io%2Fvbss-translator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvbss-io%2Fvbss-translator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvbss-io%2Fvbss-translator/lists"}