Skip to content

fix: validate batch translations and heal keys missing from target locales - #9

Merged
Peyton-Spencer merged 1 commit into
mainfrom
fix/translate-empty-results
Aug 10, 2026
Merged

fix: validate batch translations and heal keys missing from target locales#9
Peyton-Spencer merged 1 commit into
mainfrom
fix/translate-empty-results

Conversation

@Peyton-Spencer

Copy link
Copy Markdown
Contributor

Problem

solid-translate translate against real OpenRouter providers reports success while translating nothing:

Translating 2887 keys to 8 locales...
  es: 0 keys
  ...
Translation complete.   # exit 0, lock fully committed, all target files {}

Root causes, reproduced live:

  1. generateObject + z.record schema: a Record<string,string> compiles to an additionalProperties-only JSON schema. Gemini (via OpenRouter) silently returns {} for it — which validates — and OpenAI strict mode rejects the schema outright. Every batch "succeeded" empty, so no failure was recorded and the lock was committed for all 2887 keys: the exact poisoned state the lock is meant to prevent.
  2. No self-heal: syncLocaleFiles diffs source against the lock only, so once poisoned (or after an interrupted run), keys missing from target files are never re-sent. check flags them, but translate can't fix them.

Fix

  • translateBatch now uses generateText + tolerant JSON extraction (fences/prose stripped, envelope unwrapped) + strict validation: every requested key must come back as a non-empty string. One corrective retry for just the missing keys, then the batch throws — the existing failure path reports it and keeps failed keys out of the lock.
  • syncLocaleFiles computes per-locale missing keys (in source, absent from the target file) and includes them in that locale's batches regardless of lock state, with context hints from the committed lock entry. Deletion-only and no-changes early returns respect healing.

Verified live against google/gemini-2.5-flash via OpenRouter: correct translations with placeholders preserved.

Tests

17 new tests (tests/translate-validation.test.ts): JSON extraction, envelope unwrap, missing/empty/non-string detection, the Gemini empty-object regression, invented-key filtering, per-locale healing, no-changes when complete, and failed-heal retry semantics. Full suite: 167 pass.

🤖 Generated with Claude Code

…cales

Two failure modes let `translate` report success while writing empty locale
files and committing a fully-populated lock (poisoned state):

1. translateBatch used generateObject with a Record schema, which compiles to
   additionalProperties-only JSON schema. Gemini (via OpenRouter) silently
   returns {} for such schemas and OpenAI strict mode rejects them outright.
   Now uses generateText with robust JSON extraction and strict validation:
   every requested key must come back as a non-empty string, with one
   corrective retry for missing keys before the batch hard-fails (which the
   existing failure path then reports and keeps out of the lock).

2. syncLocaleFiles only diffed source against the lock, so keys the lock
   considered translated but absent from a target locale file were never
   re-sent. Target files now self-heal: per-locale missing keys are included
   in that locale's batches regardless of the lock state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Peyton-Spencer
Peyton-Spencer merged commit 36aa031 into main Aug 10, 2026
1 check failed
github-actions Bot pushed a commit that referenced this pull request Aug 10, 2026
## [1.4.3](v1.4.2...v1.4.3) (2026-08-10)

### Bug Fixes

* validate batch translations and heal keys missing from target locales ([#9](#9)) ([36aa031](36aa031))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant