Documentation

CLI

foony-translate.json, the four commands, and a CI recipe.

The foony-translate CLI ships inside @foony/translate. It parses your sources with a real AST (ts-morph), so it extracts exactly what the runtime will hash, and it errors with file:line on patterns it cannot translate.

npx foony-translate <init|scan|translate|check> [--dry-run]

Configuration: foony-translate.json

init writes a skeleton at your project root. The CLI finds it from any working directory below it; paths are relative to the config file’s directory.

{
  "locales": ["fr", "de", "es"],
  "defaultLocale": "en",
  "src": ["src"],
  "dictionaries": [],
  "out": "src/translations",
  "apiUrl": "https://translate-api.foony.io"
}
Field Meaning
locales The target locales, and the source of truth for them: every translate run translates exactly these and syncs the project’s list shown in the dashboard.
defaultLocale Source locale. Only en is supported for now.
src Directories scanned for <T> and t() usage (.ts/.tsx).
dictionaries Files containing defineDict calls or exported object literals.
out Directory the per-locale JSON files are written to. Commit it.
apiUrl The Foony Translate API. Leave the default unless you are told otherwise.

Authentication

The API key comes exclusively from the environment, never the config file:

export FOONY_TRANSLATE_API_KEY=your-project.kid_xxx:sk_xxx

Create a prod key on the project’s API keys tab. scan and check need no key at all.

Commands

init

Writes foony-translate.json and creates the output directory. Fails if the config already exists.

scan

Extracts every entry and prints counts plus any diagnostics. Exits non-zero on errors like ternaries inside <T>, bare dynamic expressions, or dynamic t() messages.

214 entries (167 <T>, 47 strings), 0 error(s)

translate

The whole loop: scan, upload new entries, wait for the LLM worker, download and write out/<locale>.json.

npx foony-translate translate
  • Only entries the service has not seen count as new. Unchanged content re-runs free.
  • --dry-run prints every entry (id or hash plus a source preview) and uploads nothing.
  • Failed translations are reported at the end; inspect and retry them from the dashboard’s Translations tab.

check

The offline CI gate: verifies every scanned entry has a translation in every configured locale, using only the committed JSON. No network, no key. Exits non-zero and prints file:line for anything missing.

CI recipe

Fail any PR that adds copy without translating it:

# .github/workflows/i18n.yml
name: i18n
on: [pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with: { node-version: 22 }
      - run: npm ci
      - run: npx foony-translate check

When the check fails, a developer runs translate locally (or a scheduled job does) and commits the updated JSON in the same PR. Because check is offline, the workflow needs no secret, so it is safe on forks.