Skip to content

valerymelou/engo

Engo

Local-first, AI-assisted i18n CLI for XLIFF, ARB, and nested JSON.

CI crates.io License: MIT OR Apache-2.0

Engo fills in the missing strings of your translation catalogs with the LLM of your choice, while preserving file structure, placeholders, metadata, and your reviewers' work. It runs on your machine β€” your API key, your files, no third-party servers in the default path.

cd my-app/
export ANTHROPIC_API_KEY=sk-ant-...
engo init          # detects your i18n setup and writes engo.toml
engo translate     # fills in every missing target string

Why Engo?

Most i18n-with-AI tools are either SaaS (your strings leave your machine) or generic LLM wrappers (they mangle placeholders and lose XLIFF metadata). Engo is built around three beliefs:

  1. Your translation files are source of truth. Engo never reformats, reorders, or strips metadata. It only writes the target values you asked it to translate, and it writes them atomically with a .bak next to each file.
  2. Review state is sacred. Engo never overwrites XLIFF final/signed-off units. Your translators' sign-offs survive every run.
  3. Placeholders are invariants. Every translation is checked: if the source has {name}, %s, or an ICU plural, the target must too. Mismatches are rejected, not written.

Install

Homebrew (macOS, Linux)

brew install valerymelou/engo/engo

Cargo

cargo install engo-cli

cargo-install (pre-built binary)

cargo install engo-cli

Pre-built binaries

Download the archive for your platform from the latest release and put engo on your PATH.

Build from source

git clone https://github.com/valerymelou/engo
cd engo
cargo build --release
./target/release/engo --help

Minimum Rust version: 1.88.


Quickstart

# 1. Provide your API key (BYOK)
export ANTHROPIC_API_KEY=sk-ant-...

# 2. Initialize β€” detects Flutter/Angular/i18next/etc. and writes engo.toml
cd my-app/
engo init

# 3. Preview what's missing (no AI call, no writes)
engo translate --list

# 4. See the translations the AI would produce (no writes)
engo translate --dry-run

# 5. Apply. Writes atomically with a .bak next to each file.
engo translate

Features

  • Three formats, zero guesswork. XLIFF 1.2 and 2.0 (Angular, Symfony, Java, Qt…), ARB (Flutter), and nested JSON (i18next, next-intl, vue-i18n). Key order and surrounding XML are preserved byte-for-byte on unchanged units.
  • State-aware. final and signed-off XLIFF units are never touched. --force re-translates translated units only.
  • Placeholder validation. Rejects translations that drop, rename, or reorder {name}, %s/%d, or ICU plural/select keys.
  • Context-aware prompts. ARB @key.description, XLIFF <note>, and your [glossary] table are sent to the model so short, ambiguous strings ("Log in" β€” verb or noun?) get translated correctly.
  • Persistent cache. .engo/cache.db (SQLite). The same source + context + model + glossary hash never calls the AI twice. Safe to delete; safe to commit.
  • Safety by default. Refuses to run on a dirty git tree without --allow-dirty. Every overwrite leaves a .bak. Writes use atomic rename(2).
  • Bring your own key. Uses your ANTHROPIC_API_KEY directly. No telemetry, no proxy, no account.
  • Fast. Concurrent batched requests (default 15 strings/request, 4 parallel) with Anthropic prompt caching on the system prompt.

Supported formats

Format Ecosystems Source of locale Key shape
XLIFF 1.2 / 2.0 Angular, Symfony, Java ResourceBundle, Qt Linguist target-language / trgLang attribute trans-unit id
ARB Flutter, Dart intl @@locale attribute top-level key
JSON i18next, next-intl, vue-i18n, FormatJS filename stem (fr.json, app_fr.json, messages-fr.json) dot-path auth.login.button

Source and target files are paired by filename stem (so app_en.arb ↔ app_fr.arb, messages-en.json ↔ messages-fr.json). XLIFF files carry their own language tags, so no pairing is required.


Configuration: engo.toml

Written by engo init. Edit freely.

[project]
format = "arb"                       # "xliff" | "arb" | "json"
files_glob = "lib/l10n/*.arb"        # relative to engo.toml
description = "Consumer banking app, casual tone."

[languages]
source = "en"
targets = ["fr", "de", "es-419"]

[ai]
provider = "anthropic"               # "anthropic" today; "openai" / "engo-cloud" on the roadmap
model = "claude-haiku-4-5"
batch_size = 15

# Domain glossary. Keys are the canonical source term; values are the required
# translation (or a short note). Sent to the model on every batch.
[glossary]
"Engo" = "Engo"
"Log in" = "Se connecter"

Commands

engo init

Interactive. Detects your project format (Flutter's pubspec.yaml β†’ ARB, package.json with i18next β†’ JSON, *.xlf files β†’ XLIFF), prompts for source + target languages, and writes engo.toml.

Flags:

  • --yes β€” accept detected defaults, non-interactive.
  • --format <xliff|arb|json> β€” override detection.
  • --source <tag>, --target <tag> β€” skip prompts.

engo translate

Flags:

  • --list β€” print every pending unit per file. No AI call, no writes.
  • --dry-run β€” translate and validate, but don't write.
  • --force β€” also re-translate units in translated state. Never touches final.
  • --target <tag> β€” restrict to one target language.
  • --concurrency <N> β€” max parallel AI calls (default 4).
  • --allow-dirty β€” skip the git-clean check. .bak files are still written.
  • --no-cache β€” bypass reads from .engo/cache.db. Writes still happen.

How the cache works

Every translation is keyed by a SHA-256 over (source, source_lang, target_lang, context, model, glossary_version). Change any one of those and the entry misses β€” so swapping a glossary term, upgrading the model, or editing the source text all correctly force a retranslation. Everything else is a free local lookup.

The cache lives at .engo/cache.db. It's a self-contained SQLite file: safe to delete, safe to commit, safe to share with teammates.


Safety

Translation tools write to your source files, so Engo is paranoid about it:

  • Clean-tree check. engo translate refuses to run if git status --porcelain is non-empty, so a bad run is always recoverable with git checkout. Override with --allow-dirty.
  • .bak files. Every overwrite creates a path.bak next to the file with the previous bytes.
  • Atomic writes. Files are written to a sibling temp file and renamed β€” a crash mid-write leaves the original untouched.
  • Placeholder validation. The AI's output is parsed against the source; missing or renamed placeholders reject the translation before it's written.

Using Engo in CI

Engo fits naturally into a pre-merge check that fails when translations are missing:

# .github/workflows/i18n.yml
name: i18n
on: pull_request
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: cargo install engo-cli
      - run: engo translate --list
      # Or: translate and open a PR
      # - run: engo translate
      #   env:
      #     ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

A GitHub Action wrapper is on the roadmap.


Roadmap

Shipped

  • v0.1 β€” Foundation. XLIFF 1.2 / 2.0 parser and state-aware patcher. engo init with project auto-detection.
  • v0.1 β€” Translation. Anthropic provider with prompt caching and tool-use-backed structured output. Tokio-based concurrent batching. Placeholder / ICU validator. engo translate --list / --dry-run / --force.
  • v0.1 β€” Multi-format + safety. ARB and JSON parsers and patchers. SQLite translation cache. Atomic writes with .bak. Git-clean enforcement.

Next

  • v0.2 β€” OpenAI provider. provider = "openai" with GPT-4o-mini as the default, using the same structured-output contract.
  • v0.2 β€” Engo Cloud (optional). A thin Vercel proxy for teams that don't want to distribute per-developer API keys. Still opt-in; BYOK stays the default.
  • v0.2 β€” engo stats. Cost estimate (tokens in / out) and pending-unit counts per language.
  • v0.3 β€” More formats. PO / gettext, Android strings.xml, Apple .strings and .stringsdict, YAML (Rails config/locales).
  • v0.3 β€” GitHub Action. valerymelou/engo-action@v1 for drop-in CI usage.
  • v0.3 β€” Pluralization-aware validation. Language-specific plural category checks (Arabic's six forms, Russian's three, etc.).

Exploring

  • VS Code extension: inline "translate this" code action.
  • Translation-memory export to TMX and XLIFF TM.
  • Glossary linting: detect candidate glossary terms from your existing translations.
  • Bundle-size analysis: warn when translations materially inflate a ship bundle.

Want to shape one of these? Open an issue or a discussion.


Contributing

Contributions welcome β€” see CONTRIBUTING.md for the build / test / commit workflow.

Quick loop:

cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all

Good first issues are tagged good first issue. Questions live in Discussions; security reports go to the address in SECURITY.md.


License

Dual-licensed under either of

at your option. Unless you explicitly state otherwise, any contribution submitted for inclusion in Engo by you shall be dual-licensed as above, without any additional terms or conditions.

About

πŸš€ Local-first AI translation CLI for developers. Automate XLIFF, ARB, and JSON localization directly in your project using Rust and Anthropic/OpenAI."

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages