Metadata-Version: 2.4
Name: kaeris
Version: 0.2.3
Summary: AI localization CLI — translate JSON/YAML/.strings/.po/ARB/Android XML into 46 languages
Author: KAERIS
License: MIT
Project-URL: Homepage, https://kaeris.dev
Project-URL: Documentation, https://kaeris.dev/developer.html
Project-URL: Repository, https://github.com/RaiGanja/kaeris-cli
Keywords: i18n,l10n,localization,translation,ai,cli
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Software Development :: Localization
Classifier: Topic :: Software Development :: Internationalization
Classifier: Environment :: Console
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# KAERIS i18n — CLI

AI localization from your terminal. Translate your app's strings files into **46 languages** —
locally or in CI/CD. Format-aware, placeholder-safe, and **incremental** (only new keys).

- **Zero dependencies** — pure Python stdlib, installs in a second
- **11 formats** — JSON, YAML, `.strings`, `.po`, ARB, Android XML, CSV (Godot/Unity), XLIFF 1.2, Java `.properties`, .NET `.resx`, Mozilla Fluent `.ftl`
- **Incremental** — `--only-new` translates just the keys you added, merges the rest
- **Translation QA** — flags dropped placeholders & UI-overflow risk; `--verify` back-translates so you can check the meaning
- **`kaeris check`** — an i18n firewall: fails CI if a locale is untranslated or placeholder-broken, no API call
- **CI-ready** — GitHub Action included; open a PR with fresh translations on every push

## Install

```bash
pip install kaeris
# or, without installing:
pipx run kaeris --help
```

## Config file (`kaeris.json`) — set it up once, forget it

```bash
kaeris init                    # generic config
kaeris init --preset next-intl # or: i18next, react-i18next, react-intl, vue-i18n, flutter-arb
```

Writes a `kaeris.json` in the current directory with `source`, `langs`, `keep`, `tone`, `icu`,
`only_new`, `out` and `format`. Once it exists, just run:

```bash
kaeris translate
```

with no arguments — the CLI reads `kaeris.json` from the current directory (or `--config PATH`,
passed before the subcommand) for anything you don't pass on the command line. **Precedence: CLI
flag > kaeris.json > built-in default** — so you can still override any one setting for a single
run, e.g. `kaeris translate --langs es` or `kaeris translate --no-icu`. `kaeris init` refuses to
overwrite an existing `kaeris.json` unless you pass `--force`.

## Quick start

```bash
# Translate a whole file into Spanish, French and Japanese
kaeris translate locales/en.json --langs es,fr,ja --out locales

# Only translate keys that are missing from the existing target files
kaeris translate locales/en.json --langs es,fr,ja --out locales --only-new

# Keep brand/product names verbatim in every language (glossary / do-not-translate)
kaeris translate locales/en.json --langs es,de --keep "KAERIS,GitHub,OpenRouter"

# Translation QA — verify meaning (back-translate) and write verify.json to review
kaeris translate locales/en.json --langs de,ja --verify

# List all supported languages
kaeris languages
```

Output files are written next to the source (or into `--out`), named by language:
`es.json`, `fr.json`, `ja.json` (or `values-es/strings.xml` for Android, etc.).

## Authentication & tiers

| Tier | How | Limit |
|------|-----|-------|
| **Free** (anonymous) | no key | 10,000 chars/file |
| **Pro / Scale** | `--key kaerisp_…` or `KAERIS_API_KEY` | 200k / 500k chars/file |
| **Lifetime (BYOK)** | `--key` **and** `--openrouter-key sk-or-…` | unlimited (you pay OpenRouter for tokens) |

```bash
export KAERIS_API_KEY=kaerisp_xxxxxxxx
export KAERIS_OPENROUTER_KEY=sk-or-v1-xxxx   # Lifetime/BYOK only
kaeris translate en.json --langs de,uk
```

Get a key at <https://kaeris.dev/pricing.html>. A free OpenRouter key: <https://openrouter.ai/keys>.

## `kaeris check` — the i18n firewall

A **local, static, no-API** check: compares your source file against each target locale and
fails (non-zero exit) if anything's missing or broken — the kind of check no other i18n tool
gates a merge on.

```bash
kaeris check                              # reads source/langs/out from kaeris.json
kaeris check --source en.json --langs de,fr,ja --out locales
kaeris check --strict                     # also fail on extra/stale keys in a target
kaeris check --json                       # machine-readable output for CI/agents
kaeris check --ci                         # same exit-code contract; a stable, named entry point for pipelines
```

It runs the same deterministic quality detectors that power the web app — offline, with no
account and no network. **RED** problems fail the build; **YELLOW** are advisory (pass
`--strict` to fail on those too).

**RED — breaks the build (non-zero exit):**

- **Missing keys** — a key exists in the source but not in a target (untranslated).
- **Placeholder mismatch** — a target string's placeholders (`{name}`, `%s`, `%d`, `{{x}}`,
  `${x}`, ICU `{count, plural, ...}`) don't match the source's — dropped, renamed, or
  hallucinated placeholders crash or silently drop data at runtime.
- **Number drift** — a number in the source changed or vanished in the translation
  (`Delete 5 files` → `Delete 50 files`).
- **Broken entities / encoding** — double-encoded (`&amp;amp;`) or malformed `\u` escapes.
- **Lost inline tags** — an HTML/markup tag (`<b>`, `</b>`, …) present in the source is gone.
- **ICU / CLDR plural completeness** — the target language is missing a plural category its
  CLDR rules require.
- **Glossary term dropped** (opt-in) — a term listed under `glossary` in kaeris.json isn't
  carried into the translation.

**YELLOW — advisory warnings (`--strict` to fail):**

- **Untranslated leftover** — the target still reads as the source language.
- **Register / casing drift**, **cross-key inconsistency**, and **UI-overflow risk** (a
  translation much longer than its source, relative to that language's typical expansion).
- **Extra/stale keys** — present in a target but not the source.

Exit codes: `0` clean, `1` a RED problem was found (or a YELLOW under `--strict`), `2` bad
usage (source not found, no languages given, etc.) — drop it straight into CI:

```yaml
- run: kaeris check --source locales/en.json --langs es,fr,de,ja --out locales
```

JSON and ARB for now (ARB `@`-metadata is ignored); more formats land next release.

## CI/CD (GitHub Actions) — translations as PRs, plus an i18n firewall

Add two workflows and translations arrive as a normal pull request — reviewed and merged like
any other code change, no separate translation tool to context-switch into — while a second
workflow blocks merges that leave a locale incomplete or placeholder-broken.

The [`kaeris-translate`](.github/actions/kaeris-translate/action.yml) composite action has
two modes:

- **`mode: translate`** (default) — runs `kaeris translate`, then a `kaeris check --json` before
  *and* after so it can report real numbers, and exposes outputs: `changed` (`"true"` only if a
  locale/lockfile actually changed — gate your PR step on this), `summary` (a markdown report:
  keys translated, a per-language matrix, placeholder-loss count, overflow-warning count when
  available), `missing` and `placeholder-issues` (totals).
- **`mode: check`** — runs `kaeris check --ci` (no API call). Non-zero exit fails the step (and
  the job) if any target locale is missing keys or has a placeholder mismatch — the i18n firewall.

**1. Auto-PR on every push** — see
[`translate.example.yml`](.github/workflows/translate.example.yml):

```yaml
- uses: RaiGanja/kaeris-cli/.github/actions/kaeris-translate@main
  id: kaeris
  with:
    mode: translate
    source: locales/en.json
    languages: es,fr,de,ja
    out: locales
    only-new: "true"

- uses: peter-evans/create-pull-request@v6
  if: steps.kaeris.outputs.changed == 'true'
  with:
    title: "i18n: new translations"
    body: ${{ steps.kaeris.outputs.summary }}
    branch: kaeris/i18n-updates
    add-paths: locales/**
```

**2. Firewall on every PR** — see
[`i18n-check.example.yml`](.github/workflows/i18n-check.example.yml):

```yaml
- uses: RaiGanja/kaeris-cli/.github/actions/kaeris-translate@main
  with:
    mode: check
    source: locales/en.json
    languages: es,fr,de,ja
    out: locales
```

Add that job to branch protection as a required status check and a PR simply cannot merge with
an untranslated or placeholder-broken locale.

## How incremental mode works

`--only-new` (JSON) parses your source and each existing translation, then translates a key if
it's **missing** from the target **or its source text changed** since the last run — and merges
the results back, preserving your existing translations and any non-string values (numbers,
booleans). No more re-translating (and re-paying for) the whole file every time you add one
string, and no more silently-stale translations when you edit an existing English string.

Change detection is powered by `kaeris.lock` — a small JSON file written next to your source
file after every incremental run. It records a SHA-256 of each source string **plus the
settings that produced it**: your tone, your glossary, and the model. Change any of them and
the whole locale is re-translated, so it never ends up a mix of two tones or two models — the
model is picked by your plan, so upgrading Free → Pro re-translates rather than blending
DeepSeek output with GPT-4o-mini. It's what lets
`--only-new` notice an edited key even though it's still present in the target; without it, a
plain "is this key missing?" check would skip the key and leave the old (now wrong) translation
in place. Commit `kaeris.lock` alongside your source file so the check works across machines/CI.
Override its location with `--lock PATH` or `"lock"` in `kaeris.json` (default: `kaeris.lock`
next to the source).

## Environment variables

- `KAERIS_API_KEY` — API key
- `KAERIS_OPENROUTER_KEY` — OpenRouter key (BYOK)
- `KAERIS_API_URL` — override the API base URL
- `NO_COLOR` — disable coloured output

## License

MIT
