AI-native localization for developers who already code with AI.
Developers who code with AI already have the perfect translator sitting right next to them. When you just built a checkout screen in Flutter, your AI co-pilot has full context — the widget tree, the button semantics, the user flow. You can just say “add the labels on this screen to translations and translate them into Spanish, Japanese, and Arabic” and it should just work.
Dialect is a convention-first, open-source localization toolkit. One canonical source syncs translations across your Flutter app and backend services. The CLI is forever free and full-featured — no required dashboard, no context switching, just code. (An optional hosted dashboard at dialect.tools is coming in v1.3 for teams who want non-dev translators to review AI-generated translations — see docs/cloud.md.)
Dev (typing in AI chat session):
"I just built the checkout screen. Translate the new strings
into Spanish, Japanese, and Arabic."
AI: *reads AGENTS.md → this project uses Dialect*
*reads dialect/dialect.yaml + dialect/glossary.yaml — convention,
target locales, glossary loaded*
*reads lib/screens/checkout_screen.dart for context*
*adds 12 keys to dialect/source/en.arb with @key.namespace and
contextual @descriptions*
*replaces hardcoded Text(...) with AppLocalizations.of(context)!*
*translates to es, ja, ar respecting the glossary*
*runs `dialect check --fix && dialect sync && dialect check`*
✓ Flutter ARB updated
✓ Backend JSON updated (icu-json)
✓ All 3 locales complete, placeholders match
One chat message. One source. Flutter + backend in sync. 60 seconds.
# macOS / Linux — official installer
curl -fsSL https://dialect.tools/install.sh | sh
# macOS / Linux — Homebrew
brew install ChauCM/tap/dialect
# Any platform — Dart pub
dart pub global activate dialect
# GitHub Actions
- uses: ChauCM/dialect@v1
with:
args: check --strict
Pre-built binaries for macos-arm64, linux-x64, and windows-x64 are attached to every release. Verify the SHA-256 against the release’s SHA256SUMS asset. Intel Macs and Linux ARM64 — build from source.
If you’re on Intel macOS or Linux ARM64, the Homebrew formula and install.sh will refuse to install — there are no prebuilt binaries for those targets. Build the binary yourself; the Dart toolchain ships with Flutter so any Flutter dev already has what they need.
git clone https://github.com/ChauCM/dialect.git
cd dialect
dart pub get
cd dashboard && pnpm install && pnpm build && cd ..
dart run tool/build_dashboard.dart --no-pnpm
dart compile exe bin/dialect.dart -o build/dialect
sudo install -m 0755 build/dialect /usr/local/bin/dialect
dialect --version
The dev never touches the terminal. Everything starts and ends in your AI agent (Claude Code, Cursor, Cline, Copilot…). The CLI is what your AI agent runs on your behalf.
You have a Flutter app with hardcoded English strings. You want Spanish, Japanese, and Arabic in production. Open your AI agent in the project and paste:
Dev (typing in AI chat session):
"run dialect init and follow the instructions.
Target Spanish, Japanese, and Arabic."
AI: Running `dialect init`...
✓ Scaffolded dialect/ → wrote dialect.yaml, glossary.yaml, en.arb
✓ Wrote .dialect/init-plan.md (two-phase playbook)
✓ Created AGENTS.md (so future sessions know about Dialect)
Reading .dialect/init-plan.md — Phase 1: Setup.
✓ Added flutter_localizations + intl to pubspec.yaml
✓ Created l10n.yaml
✓ Wired MaterialApp.localizationsDelegates + supportedLocales
✓ Added a locale picker in the Settings screen
✓ Smoke test passed: commonLoading flows through AppLocalizations
27 user-facing strings found under lib/. Proceeding to Phase 2
(under the 50-string threshold — extract + translate in this same turn).
✓ Extracted 27 keys into dialect/source/en.arb with @key.namespace
+ @key.description for each
✓ Replaced hardcoded strings in lib/ with AppLocalizations.of(context)!
✓ Translated to {es, ja, ar} respecting dialect/glossary.yaml
✓ Ran `dialect check --fix && dialect sync && dialect check` — clean
Done. Run `flutter pub get && flutter run`, switch locale via
Settings → Language, and translated text renders immediately.
That’s the whole adoption flow: one chat message. The AI runs dialect init itself, reads the plan Dialect emitted, and executes Phase 1 + Phase 2 end-to-end. You review the resulting diff and merge.
The init plan is two-phased to scale gracefully: - ≤ 50 candidate strings — the AI extracts + translates in one chat turn (the example above). - > 50 strings — the AI does Phase 2 extraction only, summarizes the proposed key names, and asks you to confirm before translating. Saves you reviewing 500+ translation lines downstream of bad key names.
Once dialect init has run, your project root has an AGENTS.md telling every future agent session about Dialect. From then on, every translation ask is plain English:
Dev: "I just added a Profile screen. Translate the new strings."
AI: Reading AGENTS.md → I see this project uses Dialect.
Reading dialect/dialect.yaml + dialect/glossary.yaml...
✓ Extracted 8 new keys from lib/screens/profile_screen.dart
✓ Translated to {es, ja, ar} respecting glossary
✓ Replaced hardcoded strings with AppLocalizations.of(context)!
✓ Ran `dialect check --fix && dialect sync && dialect check` — clean
No re-onboarding. The convention file is the spec; the AGENTS.md is the pointer.
The whole CLI surface is still there if you want to drive a specific step yourself rather than let the AI chain everything:
dialect check — validate without changing anything$ dialect check
⚠ dialect/translations/ar.arb:14 glossary Translation for `checkoutYourTripHeader` does not appear to use the glossary term "Trip" (expected "رحلة").
hint: Glossary defines "Trip" → "رحلة" in `ar`. If this key uses "Trip"
in a non-literal sense, add `"glossary_exempt": true` to the
@key block in the source ARB.
! dialect check: 1 warning (warnings only — exit 0 in soft mode;
run with --strict in CI)
Five structural rules + four semantic heuristics. Every issue has a `file:line` and a real remediation hint.
dialect sync — regenerate platform files$ dialect sync
✓ Wrote lib/l10n/app_en.arb
✓ Wrote lib/l10n/app_es.arb
✓ Wrote lib/l10n/app_ja.arb
✓ Wrote lib/l10n/app_ar.arb
Flutter `gen_l10n` picks these up directly. Backend `flat-json` & `icu-json` adapters ship today — `dialect sync` writes `dialect serve — local review UI for non-engineers$ dialect serve
Dialect Review running at http://localhost:4077
Reading from: ./dialect/

Every key, every locale, side by side with `@description` context and glossary highlighting. Per-locale coverage in the sidebar, status pills (missing / stale / locked), explicit Save / Revert in the editor, light + dark themes. Lock human-reviewed translations to skip them on the next AI re-translate.
dialect status — coverage snapshot$ dialect status
┌────────┬──────────┬───────┬─────┬────────┐
│ Locale │ Coverage │ Stale │ New │ Locked │
├────────┼──────────┼───────┼─────┼────────┤
│ es │ 100% │ 0 │ 0 │ 0 │
│ ja │ 100% │ 0 │ 0 │ 0 │
│ ar │ 95.8% │ 0 │ 1 │ 0 │
└────────┴──────────┴───────┴─────┴────────┘
name: Dialect
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ChauCM/dialect@v1
with:
args: check --strict
`--strict` promotes warnings to errors, so a missing placeholder, an orphan `@key` block, or a glossary violation fails the build.
Dialect emits JSON for backends in two flavors — pick one per platform in dialect.yaml:
icu-json — preserves {count, plural, …} for stacks with an ICU runtime.flat-json — plain { "key": "value" } for stacks without ICU.platforms:
backend:
output: api/locales/
format: icu-json # or flat-json
namespaces: [common, backend]
Then point your stack’s existing localization library at the output directory — callsites don’t change:
| Stack | Integration | Surface |
|---|---|---|
| ASP.NET (C#) | ~30-line JsonStringLocalizer over IStringLocalizer<T>, reading Dialect’s icu-json. Keeps _localizer["checkoutBookNow"] everywhere, no dependency. |
Snippet (a Dialect.AspNetCore NuGet wrapping it is a planned nice-to-have) |
| Node / Express / Fastify | i18next-fs-backend reads flat-key JSON natively. ICU plurals via intl-messageformat. |
~10-line snippet |
| Django | Drop-in JSON catalog adapter; keep _("checkoutBookNow") callsites. |
~15-line snippet |
| Flask / FastAPI | Tiny middleware loads <locale>.json from Accept-Language; standard template-helper interface. |
~15-line snippet |
| Go | go-i18n consumes flat-key JSON natively and parses ICU plurals out of the box. |
One-line config |
For each stack, docs/platforms-backend.md carries the full snippet — registration, ICU plural wiring, fallback behavior, and (for ASP.NET) the hand-rolled JsonStringLocalizer template if you can’t add a dependency. The principle is Backend Humility: your backend keeps its native localization interface (IStringLocalizer<T>, _(), i18n.Localize), Dialect just swaps the backing store.
your-project/
├── dialect/
│ ├── dialect.yaml # Config + AI-readable convention
│ ├── source/
│ │ └── en.arb # Canonical source strings (ARB format)
│ ├── translations/
│ │ ├── es.arb
│ │ ├── ja.arb
│ │ └── ...
│ └── glossary.yaml # Project-specific terms for consistent AI translation
├── .dialect/ # Gitignored. AI-pointer plan files land here.
└── lib/l10n/ # `dialect sync` output (Flutter convention)
| Command | Description | Status |
|---|---|---|
dialect init |
Scaffold the dialect/ directory |
v1.0 |
dialect import |
AI-pointer flow: import existing ARBs into the convention | v1.0 |
dialect describe |
AI-pointer flow: backfill @description from callsites |
v1.0 |
dialect sync |
Generate platform-specific files from canonical ARBs | v1.0 |
dialect check |
Validate completeness, correctness, and translation quality heuristics | v1.0 |
dialect status |
Coverage overview across locales | v1.0 |
dialect serve |
Local web UI for reviewing translations | v1.0 |
dialect translate |
AI-pointer flow for translation (--auto for direct LLM call) |
v1.0 |
dialect publish |
Build versioned bundle (manifest + per-locale JSON) and upload to S3/R2/git/local | v1.2 |
dialect pull |
Fetch latest bundle into dialect/translations/ (use in CI deploy scripts) |
v1.2 |
dialect login / link / push |
Talk to dialect-server (Cloud or self-host) — see docs/cloud.md |
v1.3 |
dialect export |
Full project tarball for migration between Cloud / self-host / local | v1.3 |
dialect diff |
Show translation changes for PR review | v1.5 |
| Document | Description |
|---|---|
| Why Dialect | The problem with localization today and the insight behind Dialect |
| Roadmap | What’s shipped, what’s planned for v1.1 → v2.0+, and what’s explicitly out of scope |
| Architecture | File convention, CLI reference, config format, CI integration |
| Dialect Cloud | Cloud (v1.3), self-host (v1.4), and OSS local-only — three modes, same protocol |
| Frontend Platforms | Flutter primary. iOS / Android native and React / RN as edge cases. |
| Backend Platforms | ASP.NET, Node, Django, FastAPI, Go — format adapters and bundle-URL integration |
| Flutter OTA | Over-the-air protocol for the dialect_ota Flutter package — deferred to v2.0+ |
dialect/spec/)These specify Dialect’s versioned file formats. Backend localizer libraries (Dialect.AspNetCore, third-party adapters) target this contract; breaking changes require a major-version bump.
| Spec | Description |
|---|---|
icu-json |
Backend JSON output that preserves ICU plural/select expressions byte-identically |
flat-json |
Backend JSON output that strips ICU plural/select to a plain string (takes the other branch) |
@key.source_hash |
Source-value fingerprint that powers dialect status “stale” and the dashboard lock indicator |
.dialect/state.json |
Soft-mode acknowledgement store for the dialect check rules |
Six things, in order of “what you touch”:
@description / @placeholders / glossary metadata, plus a YAML config that doubles as an AI-readable instruction sheet. Any modern AI editor produces correct output by reading it.init scaffolds, import / describe write structured plan files the AI follows, check validates structurally and semantically, sync generates platform files, status reports coverage. Forever free and full-featured.dialect serve). A Svelte SPA embedded in the binary; no node_modules at runtime. Side-by-side source + target, glossary highlighting, inline edit, pin/lock.dialect/spec/). Backend localizer libraries target the spec, not the CLI version — breaking changes require a major bump.dialect.tools for translator review without git access. Self-host comes in v1.4 — same binary, your infra.dialect_ota Flutter package; reuses the v1.2 bundle format.Small and medium teams who code with AI and want a localization workflow that lives in code, not a dashboard.