Skip to content

fix: unrunnable CLI binary, lock poisoning on failed translation, deletion persistence, locale matching - #3

Merged
Peyton-Spencer merged 1 commit into
mainfrom
fix/cli-lock-integrity
Jul 19, 2026
Merged

fix: unrunnable CLI binary, lock poisoning on failed translation, deletion persistence, locale matching#3
Peyton-Spencer merged 1 commit into
mainfrom
fix/cli-lock-integrity

Conversation

@Peyton-Spencer

Copy link
Copy Markdown
Contributor

Fixes six verified bugs in v1.1.0, spanning the CLI binary, lock-file integrity, deletion handling, locale detection, and the ai dependency contract.

1. Unrunnable CLI binary (blocker)

Before: src/cli.ts carried its own #!/usr/bin/env node AND tsup.config.ts added a banner shebang, so dist/cli.js started with two shebang lines — a syntax error under both node and bun. The published 1.1.0 solid-translate binary could not run at all.

After: the source shebang is removed (the tsup banner is the single source of truth), and CI now smoke-tests node dist/cli.js --help and bun dist/cli.js --help after every build so this can never regress.

2. Lock poisoning on failed translation (blocker)

Before: lock entries were recorded for keys before translation ran, and batch failures were caught-and-continued — then target files and the lock were written anyway. A failed run (e.g. missing API key) wrote es.json as {} plus a fully-populated lock; the rerun said "No changes detected". Silent, permanent corruption.

After: the diff produces pending lock entries that are committed only for keys whose batches succeeded for all target locales. Successful batches are still written, but on any failure the CLI exits non-zero naming the failed locales/keys, and the Vite plugin throws (fails the build) with the same detail. The lock never claims a key is translated when it isn't; failed keys are retried on the next run.

3. Deleted keys never persisted

Before: when the only change was deleted source keys, the "no changed keys" early return skipped all writes, so removed strings lived in target locale files (and shipped bundles) forever.

After: a deletions-only run prunes the deleted keys from every target file and writes the updated lock — with zero AI calls.

4. CLI dropped context from lock entries

Before: CLI-written lock entries omitted context while Vite-written ones included it, so mixing solid-translate translate with Vite autoExtract re-translated every context-annotated key on each tool switch.

After: the CLI preserves existing lock contexts when rewriting entries (and passes them as hints to the translator). Bonus: the Vite plugin with autoExtract: false no longer wipes contexts either.

Fixes 2–4 are single-sourced: the previously duplicated (and divergent) lock/diff/write logic in cli.ts and vite.ts now lives in a shared src/lock.ts (diffLock + syncLocaleFiles), used by both entry points.

5. Locale detection case bug

Before: detectLocale lowercased the browser locale but compared against availableLocales verbatim — an available "pt-BR" never matched a browser "pt-br"/"pt-BR" and fell through to the default.

After: matching is case-insensitive and returns the canonical casing from availableLocales ("pt-br""pt-BR").

6. Lazy ai import + correct peer range

Before: dist/vite.js statically imported generateObject from ai, so merely loading the Vite plugin required ai installed even for extract-only or fresh-lock builds. The peer range ai >=3.0.0 also admitted ai v5, which removed the LanguageModelV1 type this package uses.

After: every ai import is a lazy await import("ai") executed only when translation actually runs (verified: the plugin loads and completes a fresh-lock buildStart with node_modules/ai removed). Peer range is now ">=3.0.0 <5.0.0".

Tests

  • New tests/lock.test.ts: 20 tests covering diffing, lock-poisoning/retry behavior, per-batch and per-locale failure isolation, deletion-only pruning, CLI↔Vite context parity, and corrupted-lock recovery (stubbed translator, real temp-dir fs).
  • tests/locale-detect.test.ts: 5 new case-sensitivity/canonical-casing tests.
  • Full suite: 63 pass, 0 fail. bun run build, bunx tsc --noEmit, and node+bun smoke of dist/cli.js all green.

No public API changes. src/extract.ts and the virtual-module design are untouched.

🤖 Generated with Claude Code

…etion persistence, locale matching

- Remove duplicate shebang: src/cli.ts had its own #!/usr/bin/env node on top
  of the tsup banner, so dist/cli.js started with two shebang lines — a syntax
  error under node and bun that made the published binary unrunnable. CI now
  smoke-tests dist/cli.js --help under both runtimes.
- Extract shared lock/sync logic into src/lock.ts (used by both the CLI and
  the Vite plugin) so lock semantics can no longer diverge:
  - Lock entries are committed only for keys whose batches succeeded for ALL
    target locales. Failed batches no longer poison the lock (previously a
    failed run wrote empty target files plus a fully-populated lock, and
    reruns reported "No changes detected").
  - On any batch failure the CLI exits non-zero naming the failed locales and
    keys; the Vite plugin throws and fails the build.
  - Deleted source keys are pruned from target locale files and the lock even
    when there is nothing to translate (previously the early return skipped
    all writes and removed strings lived in target files forever).
  - CLI-written lock entries now preserve context (previously dropped, which
    made mixed CLI + Vite autoExtract usage re-translate every
    context-annotated key).
- detectLocale now matches available locales case-insensitively and returns
  the canonical casing (browser "pt-br" matches available "pt-BR").
- The ai package is imported lazily inside translateBatch/translateMarkdown,
  so loading the Vite plugin for extract-only or fresh-lock builds works
  without ai installed. Peer range corrected to ">=3.0.0 <5.0.0" (the code
  uses LanguageModelV1, removed in ai v5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Peyton-Spencer
Peyton-Spencer merged commit 36dce29 into main Jul 19, 2026
1 check passed
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