dialect 1.0.4 copy "dialect: ^1.0.4" to clipboard
dialect: ^1.0.4 copied to clipboard

AI-native localization toolkit for Flutter-led teams.

Changelog #

All notable changes to the Dialect CLI are tracked here.

[Unreleased] #

1.0.4 #

Release-pipeline-only patch. Fixes the pub.dev publish step that hung on both 1.0.2 and 1.0.3 even after publisher OIDC trust was configured.

Root cause: dart pub publish --force run via Flutter-bundled Dart did not auto-detect the GitHub Actions OIDC environment and silently fell back to the interactive Google OAuth device-flow ("In a web browser, go to https://accounts.google.com/o/oauth2/auth?…"). With no stdin in CI, the job hangs until cancelled.

Fix: explicit OIDC mint + dart pub token add https://pub.dev ahead of dart pub publish. The token is requested with the https://pub.dev audience via ACTIONS_ID_TOKEN_REQUEST_URL, so pub.dev's publisher-side OIDC trust validates the source repo, tag pattern, and environment as expected.

No CLI, convention, or wire-format changes.

1.0.3 #

Three threads land together: an agent-pilot bug pass against the init flow (B1–B4), the dashboard UX overhaul, and the example-project split. Also the second pub.dev publishing attempt now that publisher OIDC trust is configured, and a CI scoping fix that surfaced after the v1.0.2 push.

Fixed — init plan bugs from Opus 4.7's pilot run #

These four blockers turned dialect init from "AI follows the plan and the app builds" into "AI follows the plan and flutter pub get errors" or "translations silently drop". Full pilot feedback under docs/feedback/.

  • B1 — modern AppLocalizations import path. The init plan was writing the legacy synthetic-package import (package:flutter_gen/gen_l10n/app_localizations.dart). Modern Flutter defaults to synthetic-package: false, where the import is package:<pubspec_name>/l10n/app_localizations.dart. The plan now reads the example's pubspec name and renders the correct import; a new PUBSPEC_NAME token threads through plan rendering.
  • B2 — intl pin conflict on modern Flutter. The init plan pinned intl: ^0.19.0, which fought flutter_localizations' own intl constraint on Flutter 3.41+ SDKs and broke flutter pub get. Now intl: any, with a short note in the plan explaining why.
  • B3 — dialect sync dropping every translated key under a namespace filter. The adapter read namespace metadata from translation entries, but by convention only the source ARB carries @key blocks. With a namespace filter set in platforms.flutter, the filter rejected everything. Now the filter joins translation keys against source-ARB metadata. The adapter API gains a required source: ArbFile? param when isSource: false.
  • B4 — dialect sync --force. Added as an explicit escape hatch for rewriting outputs regardless of content match. (The original B4 repro was a downstream symptom of B3 and is resolved by the B3 fix; --force handles the remaining "rewrite anyway" cases.)

Seven follow-ups (B5 + F1–F6) filed as issues #1–#7.

Changed — example project layout #

  • example/examples/{before,after}/ — two sister Flutter apps. before/ is bare with hardcoded English; after/ is a pure clone used as the target for end-to-end dialect init testing. The user's AI agent populates after/dialect/, l10n.yaml, and AppLocalizations callsites when run against the cloned starting state.
  • Canonical Dialect fixture (used by check / status / roundtrip tests) moved to test/fixtures/canonical/dialect/. Tests now point there instead of example/dialect/, decoupling them from the demo apps.
  • Multi-model validation harness moved to examples/_validation/, reading from examples/before/lib/. INSTRUCTIONS.md and the chat-only fallback updated for the new paths.

Changed — dashboard #

  • Editor UX: replaced blur-to-save with an EntryEditor panel that has explicit Save / Cancel / Revert, plus Copy-from-source, Clear, and lock-to-source. Placeholder + character-count hints inline.
  • Visual refresh: dark-mode support, status pills (missing / stale / locked), per-locale coverage bars in the sidebar.
  • Navigation: keyboard shortcuts for next-missing / next-stale jumps, multi-locale sidebar selection.

Fixed — release-pipeline / CI #

  • CIdart format --set-exit-if-changed now scopes to bin lib test tool instead of .. The examples/*/ subpackages have their own pubspec + Flutter version and are verified independently — formatting them from the root crosses Flutter-SDK boundaries and trips version-sensitive formatter rules (the Flutter-3.41 vs Flutter-3.44 formatter delta surfaced on the v1.0.2 push).
  • Format — ran dart format against the dialect package itself to pick up two pending whitespace adjustments in lib/commands/sync.dart and test/adapters/arb_adapter_test.dart.
  • pub.dev — the v1.0.2 publish hung because the publisher had not enabled OIDC trust for ChauCM/dialect. That's now configured; v1.0.3 re-attempts the publish through the same workflow path.

1.0.2 #

Release-pipeline-only patch. The 1.0.0 and 1.0.1 tags never produced a GitHub release: linux-arm64 builds failed because Flutter doesn't ship an arm64 Linux SDK that subosito/flutter-action can resolve, and macos-13 (Intel) runners stayed queued for 30–45 minutes. This release drops both platforms from the matrix.

  • Pre-built binaries now ship for macos-arm64, linux-x64, and windows-x64 only. Intel macOS and Linux ARM64 users build from source — see the new "Build from source" section in the README.
  • Homebrew formula errors clearly (brew installodie) on Intel Mac and Linux ARM64 instead of pretending to install.
  • install.sh refuses unsupported targets with a pointer to the build-from-source instructions instead of 404'ing on a missing artifact mid-script.
  • No CLI, convention, or wire-format changes.

1.0.1 #

Documentation + tooling polish on top of 1.0.0. No CLI behavior or convention changes — the wire format and Dart APIs are identical to 1.0.0.

  • README rewritten to lead with the chat-first DX. New project: one chat message (run dialect init and follow the instructions). Ongoing work: plain English ("translate the new screen") backed by the AGENTS.md the agent installer drops at the project root. Manual CLI commands collapsed behind <details> blocks as optional advanced touchpoints.
  • README teaser block (the elevator-pitch Dev/AI dialogue) updated to the natural-language flow that lands after dialect init.
  • dart format applied to the nine M3.5 files I'd missed in the 1.0.0 commit. CI's dart format --set-exit-if-changed check is now clean.

1.0.0 #

First stable release.

The shipping convention: flat camelCase keys (checkoutBookNow) with logical grouping in @key.namespace metadata. This is what flutter gen-l10n requires — every source key becomes a method on AppLocalizations after sync. No mangling, no impedance mismatch with Flutter's default localization tool.

Onboarding collapses to one CLI command + one chat message:

$ dialect init (then in your AI agent:)

run dialect init and follow the instructions

dialect init scaffolds, detects the project type, writes a two-phase .dialect/init-plan.md (the AI's playbook), and writes/updates AGENTS.md (or appends to CLAUDE.md if that's what the project already has). Re-running dialect init is idempotent — refreshes the plan without clobbering the scaffold.

Phase 2 of the init plan has a single sizing rule: ≤ 50 candidate strings → AI extracts + translates in one chat turn;

50 → AI extracts only, then stops for the developer to sanity-check key names before the long translation step.

Convention + check rules:

  • ArbMetadata.namespace is a first-class field on every source key. The parser reads it, the writer emits it first in the metadata block, the ARB adapter filters by it.
  • Two new structural check rules: key_format (rejects non-Dart-identifier keys with a specific hint for the pre-1.0 dotted shape) and namespace_required (source keys must declare @key.namespace).
  • All in-repo ARBs (live example/, validation seed) re-keyed to the new shape; example/_validation/INSTRUCTIONS.md refreshed for the new chat-message default.

Distribution: Pub + Homebrew + curl + GitHub Action, all wired in the 1.0.0-rc.* series and now tagged as 1.0.0.

The brainstorm-phase planning docs, research, references, and spikes moved out of this repo and into the archived brainstorm repo at /Users/chaucao/Documents/github/brainstorm/dialect. The shipping repo carries only the user-facing docs, the CLI, the example app, and the validation harness.

1.0.0-rc.3 #

UX pivot: the AI now owns the full CLI chain.

Before: import/describe plan files told the AI "do not run dialect sync or dialect translate — those are the developer's calls." The developer had to chain check --fixsynccheck themselves after every AI run. That broke the 2-step promise in the README.

After: the plan templates instruct the AI to run dialect check --fix && dialect sync && dialect check as the final step of every import / describe job and report the result. The 2-step happy path becomes:

  1. dialect init
  2. Ask the AI to follow dialect/dialect.yaml. The AI extracts, translates, normalizes, syncs, and validates. Reports back.

Terminal dialect check / sync / status / serve are still there for any step the dev wants to inspect or drive manually — they're now framed as advanced touchpoints rather than required steps. README's walkthrough collapses to the 2-step flow with manual commands tucked under collapsible <details> blocks.

Code surface: zero CLI changes. Just templates/import_plan.md, templates/describe_plan.md, baked Dart constants regenerated by tool/sync_templates.dart, two new positive assertions in the import/describe tests, and the README rewrite.

1.0.0-rc.2 #

Re-cut of 1.0.0-rc.1 with the Flutter-setup fix applied to .github/workflows/release.yml (same fix CI got — dart pub get recurses into example/ which needs Flutter, not bare Dart). Bundles the README rewrite that leads with the Lokalise-replacement positioning and a real day-1 walkthrough instead of a summary.

1.0.0-rc.1 #

First release candidate — feature-complete, dogfooding through the distribution pipeline (M11). No source changes since 0.1.0-dev beyond the version bump; this RC's job is to exercise .github/workflows/release.yml end-to-end before tagging v1.0.0. Failed at the dart pub get step on every matrix runner (Flutter not installed); fixed in rc.2.

0.1.0-dev #

The pre-1.0 development line. Every milestone from M0 (convention validation) through M11 (distribution pipeline) is rolled into 0.1.0-dev until the v1.0.0 tag promotes the spec contracts to stable. Detailed per-milestone notes follow.

Added #

  • M11. Distribution pipeline — four channels (Docker + Scoop dropped per scope decision).

    • install.sh at the repo root. POSIX sh, OS/arch detection (macos-arm64, macos-x64, linux-x64, linux-arm64), version pinning via DIALECT_VERSION, install location via DIALECT_INSTALL_DIR (default ~/.local/bin), SHA-256 checksum verification against the release's SHA256SUMS asset. Refuses Windows with a pointer to the GitHub release zip. Hosted at https://dialect.tools/install.sh; also published as a release asset so the GitHub URL is a stable fallback.
    • .github/workflows/release.yml — tag-driven (v*) matrix build across macos-latest (arm64), macos-13 (Intel x64), ubuntu-latest (x64), ubuntu-24.04-arm (arm64), and windows-latest. Each runner: pnpm install + build the dashboard, tool/build_dashboard.dart --no-pnpm to bake the SPA, dart compile exe, tarball/zip + SHA-256. Aggregate job composes SHA256SUMS, attaches every artifact + install.sh to the GitHub release. Separate pub job publishes to pub.dev via OIDC (no secret token). Separate homebrew job renders the formula from homebrew/dialect.rb.tmpl and opens a bump-PR against ChauCM/homebrew-tap (skipped for prereleases — v1.0.0-rc.1 etc. don't ship to Homebrew).
    • action.yml at the repo root — composite GitHub Action. uses: ChauCM/dialect@v1 with args: (default check --strict), version: (default latest), and working-directory: inputs. Installs via the same install.sh script so the action and the curl-installer share one code path.
    • homebrew/dialect.rb.tmpl — formula template; per-target {{*_SHA256}} and {{VERSION}} placeholders. Branched by Hardware::CPU.arm? so a single formula covers macOS arm64, macOS x64, linux arm64, linux x64.
    • tool/render_homebrew_formula.dart — reads the template, fetches SHA256SUMS from the GitHub release for a tag, fills in every placeholder, aborts non-zero if any per-target hash is missing (so the bump-PR can't ship a half-rendered formula).
    • LICENSE.mdLICENSE per pub.dev convention.
    • CHANGELOG.md gains a top-level 0.1.0-dev heading per pub.dev convention so the next dart pub publish --dry-run is down from 4 warnings to the single deferred docs/ → doc/ rename note.
    • Dropped from the original 6-channel plan: Docker (no expected adoption signal yet; can re-add post-v1.0 if users ask) and Scoop (Windows package manager; Windows users get binaries via the GitHub release zip until there's signal).
  • M10. Svelte dashboard SPA + dialect serve.

    • Dart Shelf server at lib/server/server.dart binds localhost:4077 by default (configurable via --port/--host). Each request reloads the project from disk so external ARB edits are visible immediately — the project is tiny, I/O is cheap, and it removes the stale-cache foot-gun.
    • REST API per docs/architecture.md § REST API:
      • GET /api/config — parsed dialect.yaml plus the resolved project name.
      • GET /api/strings?locale=<loc> — every source key with the matching translation entry. Includes description, context, placeholders, locked, glossary_exempt, source_hash, and computed stale + missing flags.
      • PUT /api/strings/<key> — body { locale, value, locked?, glossary_exempt? }. Writes back through ArbWriter (canonical formatting). Locking writes @key.source_hash per dialect/spec/source_hash.md; unlocking clears it. Empty metadata blocks are dropped (no @key: {} noise on translation files).
      • GET /api/glossary — parsed glossary.yaml.
      • GET /api/status — reuses the M6 computeStatus math byte-for-byte, so the dashboard footer and the CLI agree.
      • OPTIONS preflight + permissive CORS so the SPA can run under pnpm dev against the Dart server during dashboard development.
      • JSON 404 for unknown /api/* paths (never falls through to the SPA HTML handler); JSON 500 for uncaught exceptions.
    • Svelte 5 SPA (dashboard/, built with Vite, package manager pinned to pnpm):
      • App.svelte — locale switcher + filter sidebar + translation table + coverage footer.
      • lib/TranslationTable.svelte — keyboard-driven inline editing (Enter to save, Esc to cancel, blur saves, optimistic refresh of strings + status after each PUT).
      • lib/GlossaryHighlight.svelte — whole-word glossary highlighting of the source string with the canonical-translation tooltip.
      • lib/LockToggle.svelte — pin/lock control wired through the locked field on PUT.
      • lib/FilterPanel.svelte — missing/locked/stale toggles, namespace radio group, search box.
      • lib/CoverageFooter.svelte — coverage % + missing/stale/locked counts from /api/status.
      • lib/api.ts — typed fetchConfig/fetchStrings/fetchGlossary /fetchStatus/putString wrappers.
    • Static-asset embedding. tool/build_dashboard.dart walks dashboard/dist/ after pnpm build and emits lib/server/embedded_assets.g.dart — a single const Map<String, List<int>> from forward-slash paths to file bytes. dart compile exe bin/dialect.dart -o build/dialect produces an 8.1 MB self-contained binary that serves the SPA with zero node_modules on disk at runtime. Generator supports --no-pnpm (CI pre-builds) and --check (drift gate).
    • Empty-bundle fallback. When the generator hasn't run on this checkout, / serves a minimal "Dashboard not bundled yet" HTML page that names the exact command to bake it in. The REST API stays live so backend work can proceed without the SPA build.
    • Real implementation of dialect serve (was a stub). --port/--host flags, project-load preflight surfaces "no project" as exit 66 before binding the port, SIGINT clean shutdown.
    • ArbMetadata.copyWith — new helper with a sentinel for sourceHash so callers can explicitly clear the field (unlock) without colliding with the "leave it alone" default. Powers the PUT mutation path.
    • 13 new server route tests (now 178 total). Real E2E verified against build/dialect serve example/: PUT writes Reservar AHORA + @key.source_hash to es.arb on disk; unlock reverts both.

Added #

  • M9. Stable on-disk-contract spec docs under dialect/spec/.
    • icu-json.md — backend JSON output that preserves ICU plural/select expressions byte-identically. Flat keys (not nested objects), one file per locale, UTF-8 NFC, 2-space LF, sorted. Aligned with Dialect.AspNetCore (v1.1) and the third-party backend libraries documented in docs/platforms-backend.md.
    • flat-json.md — sibling format that strips ICU plural/select/selectordinal to a single plain string by taking the other branch and recursively stripping nested expressions. Documents the loss-of-information trade-off and the info hint dialect sync emits when a project's source uses plurals and flat-json is selected.
    • state.md.dialect/state.json shape for soft-mode dialect check acknowledgements (decision 9 in the plan). Per-issue acks keyed by <rule>:<locale>:<key>, fingerprinted with the source/translation hash at ack-time so edits resurface the warning. Structural rules are explicitly NOT ack-able (those are correctness failures, not heuristics).
    • All three carry the same status header as source_hash.md: v1.0 stable contract, breaking changes require a major bump.
    • README.md gains a "Stable on-disk contracts" sub-section linking every spec — backend integrators land on the contract before they write a single line of glue code.
    • Blocks the v1.0.0 tag per the plan. The Dialect.AspNetCore NuGet (v1.1) depends on a stable icu-json contract; shipping v1.0 without versioned specs would cost us users at the first breaking change.

Added #

  • M8. dialect check semantic heuristics — four new rules layered on the M4 Rule interface, runner, and report.
    • source_equality (warning) — flags translations identical to the source. Filters short / symbol-only values; honors @key.locked: true as the explicit dismissal.
    • length_ratio (warning) — flags translations whose char count is outside [min, max] * source.length. Default band [0.3, 2.5]. Per-locale overrides via length_ratio: in dialect.yaml. Source values shorter than 8 chars skip the check (too noisy on "OK"/"Hi"-class strings). Reports the actual ratio in the message.
    • untranslated_english (warning) — flags translation values containing a conservative whole-word English function word (the/and/with/this/that/you) that isn't carried over from the source. Bias: under-flag rather than wrongly flag.
    • glossary (warning) — for every source value containing a glossary term: (whole-word, case-insensitive), each target translation must contain a recognizable prefix of the canonical translations.<locale>. Suffix-inflection tolerance baked in (drops last 2 chars when len > 4, so "Reservar" matches "Reserva"/"Reservamos"). Honors @key.glossary_exempt: true on the source entry — the documented escape hatch for non-literal uses.
  • lib/glossary/glossary_loader.dart — typed loader for dialect/glossary.yaml. Empty when file absent (no error); throws FormatException on malformed YAML. Loaded once at DialectProject.load and exposed as project.glossary so every rule reads from the same in-memory snapshot.
  • --strict-length plumbing. report.dart learned to honor the flag independently of --strict: under bare --strict, every warning promotes to error except length_ratio (length ratios are noisy enough that a blanket CI gate produces too many false positives). --strict-length is the explicit opt-in.
  • CHANGELOG note for example/ polish. Running dialect check on the canonical example/ now surfaces 7 real semantic warnings (source-equality on Total / Email carryovers; glossary misses in ar; length-ratio edge cases for ja/ar). Soft mode exits 0, so the integration test stays green — but these are real signals the demo polish should address (lock the legit carryovers, widen the per-locale length bands).

Added #

  • M7. dialect import + dialect describe — AI-pointer flow.
    • dialect import --from <fmt> --path <path> writes .dialect/import-plan.md. dialect describe [--path <path>] writes .dialect/describe-plan.md. Dialect itself never opens .dart/.kt/.swift/.cs files; the plan tells the user's AI where to look. CLAUDE.md §3.1 made literal.
    • Plan-file content is the load-bearing product surface. Each plan is self-contained — it inlines the load-bearing convention rules (key style, namespaces, "what NOT to extract", placeholder preservation, ICU plural shape, glossary application, hard guardrails) so a stock AI agent doesn't need follow-up prompting, and points at dialect/dialect.yaml as the canonical source if anything conflicts.
    • Templates live at templates/import_plan.md and templates/describe_plan.md (reviewable in PRs as Markdown); tool/sync_templates.dart bakes them into lib/templates/{import,describe}_plan_md.dart. Runtime token substitution via lib/templates/plan_render.dart. Tokens: {{FROM}}, {{PATH}}, {{SOURCE_LOCALE}}, {{TARGET_LOCALES}}, {{PROJECT_NAME}} (reads project.name from dialect.yaml with a friendly fallback), {{NAMESPACES}} (sorted union across platforms), {{GENERATED_AT}} (ISO 8601 UTC). Unknown tokens are left in place so seed-file typos are loud, not silent.
    • Plan files overwrite on every run — .dialect/ is gitignored ephemera per M3, not durable state.
    • Exit codes consistent with status/check: 0 success / 64 usage (missing --path on import, unknown --from) / 65 malformed dialect.yaml / 66 no project.

Added #

  • M6. dialect status — per-locale coverage table.
    • Columns: Locale, Coverage, Stale, New, Locked. Output via a Unicode box-drawing table (lib/render/table.dart).
    • Coverage = (translated keys) / (source keys). New = source keys missing from the translation. Locked = entries with @key.locked: true. Stale = locked entries whose stored @key.source_hash no longer matches the current source value's hash.
  • @key.source_hash spec at dialect/spec/source_hash.md. SHA-256 of the NFC-normalized source value, truncated to 16 lowercase hex chars. Written by the dashboard at lock-time (M10); read by status, the dashboard's stale indicator, and dialect translate --skip-locked (M8+). Hashing the value only — not description or placeholders — so a description edit doesn't invalidate locked translations.
  • lib/arb/source_hash.dartcomputeSourceHash implementing the spec. 6 tests including a locked-in fixture so future implementations can't silently change the on-disk fingerprint format.
  • lib/render/table.dart — generic Unicode-table renderer. Right-aligns numeric data columns, left-aligns text and the header. Zero deps. 5 tests.

Added #

  • M5. dialect sync — ARB-passthrough adapter.
    • Walks platforms: in dialect.yaml. For each platform with format: arb, writes <output>/app_<locale>.arb files (Flutter's gen_l10n convention).
    • Source ARB output keeps @key metadata; translation outputs strip metadata per the convention.
    • Namespace filter: keys whose prefix (before the first .) isn't in platforms.<p>.namespaces are dropped from that platform's output. Empty namespaces list = no filtering.
    • Output paths resolved against the project root (not cwd), so dialect sync /path/to/proj and cd /path/to/proj && dialect sync write identical bytes to the same locations.
    • Idempotent: re-running sync with no changes does not touch any files (mtime preserved). _maybeWrite skips writes when the desired bytes already match disk.
    • Sync does not auto-fix the input ARBs. Run dialect check --fix separately to normalize sources; sync produces canonical output regardless (because it goes through ArbWriter).
    • Non-arb formats print a "lands in v1.1" hint and skip without erroring.
  • DialectConfig.platforms parsing — typed PlatformConfig (name, output, format, namespaces). length_ratio and project blocks still flow through extras for M8.
  • lib/adapters/arb_adapter.dartArbAdapter.prepare (filter + strip), ArbAdapter.encode (delegates to ArbWriter), ArbAdapter.filenameFor (the app_<locale>.arb convention; v1.1 spec will make this configurable via platforms.<p>.filename_pattern).

Added #

  • M4. dialect check — structural validation + --fix normalization.
    • Five structural rules under lib/checks/structural/: missing_keys, placeholder_match, plural_categories, empty_values, orphan_metadata. Each rule emits typed Issue objects with severity, locale, key, file path, line number, and a real soft-mode hint (not a stub). The Rule interface is shared with M8 semantic rules.
    • CLDR table (lib/checks/cldr_categories.dart) covers ~25 common locales; unknown locales emit a friendly warning rather than silently skipping.
    • Position tracking added to the parser: every top-level key gets a 1-based line number (ArbFile.entryLines) via a regex post-pass over the source text. Powers file:line hints in the check report. Both "key": and "@key": lines are recorded so orphan metadata gets a position too.
    • Project loader (lib/project/dialect_project.dart) reads dialect.yaml + source ARB + every target translation. Minimal DialectConfig parser; M5 extends with platforms and length_ratio.
    • --fix mode re-emits every ARB through ArbWriter: sorts keys, hoists @@locale, places @key blocks correctly, strips translation metadata, drops orphans. Re-runs the check pass on the rewritten files so the exit code reflects post-fix state.
    • Real-world fixture coverage: the plural-categories rule uses _validation/runs/{gpt-5-3,claude-post-patch}/dialect/translations/ar.arb as the negative / positive fixtures (the M0+ Codex defect → live bug fixture, no synthetic mockery).
    • Exit codes: 0 clean / 1 errors / 64 usage / 65 malformed ARB or YAML / 66 no project. Soft mode exits 0 for warnings-only;--strict promotes everything to errors.

Changed #

  • M3 follow-up. Three small fixes before M4 starts.
    • templates/glossary.yaml now ships with one example term + a "Replace or delete this entry" hint, the same pedagogical role as common.example in source/en.arb. Was effectively empty before.
    • SDK floor bumped from ^3.4.0 to ^3.11.0 (current stable Dart). Pre-1.0 policy: track current stable. Locks at "current + previous two minor SDK versions" at 1.0 launch. Policy documented in planning/mvp-plan.md.
    • tool/sync_templates.dart simplified — the dart format pipe workaround removed in favour of // dart format off directives in the generated source. The directive landed in Dart 3.7 and is now honored on every supported SDK.
  • v1.0.1 backlog (flagged from M3 review, intentionally not blocking M4):
    • dialect init --force is whole-tree overwrite for the canonical files. A separate --refresh (write only missing files) is friendlier for users with customized dialect.yaml.
    • project.name could auto-detect from a sibling pubspec.yaml's name: field, falling back to the directory basename. Currently everyone gets the placeholder "Your project".

Added #

  • M3. dialect init — the first user-facing command.
    • Accepts an optional positional [path] argument (defaults to cwd).
    • --force overwrites an existing dialect/ directory.
    • Writes dialect/dialect.yaml, dialect/glossary.yaml, dialect/source/en.arb; creates dialect/translations/.
    • Appends .dialect/ to .gitignore (creates the file if missing, deduplicates if already present).
    • Exit codes: 0 success, 64 usage, 65 dialect/ exists w/o --force, 66 target directory missing.
  • Templates pipeline. Canonical seed YAML/ARB at repo-root templates/; tool/sync_templates.dart bakes them into Dart constants under lib/templates/*.dart. --check mode wired into CI so a hand-edited generated file fails the build. The generator pipes output through in-project dart format so the on-disk shape always matches what dart format would produce. 9 init tests in test/commands/init_test.dart (creation, byte-identical templates, .gitignore handling, --force, error cases).

Changed #

  • M2 hardening. Pre-emptive fixes to the ARB substrate before M3 builds on it.
    • Parser preserves unknown @@<name> file-level metadata in ArbFile.fileMetadata (e.g. Flutter gen_l10n's @@last_modified). Writer emits it after @@locale in sorted order. Prevents silent data loss on dialect sync.
    • Orphan @key blocks (no matching key/value) preserved in ArbFile.orphanMetadata so M4 can surface them as structural errors without re-reading raw JSON. Writer skips orphans by construction — dialect check --fix strips them implicitly.
    • example/dialect/source/en.arb synced with the canonical 30-key extract from _validation/runs/claude-post-patch/. Matches the translation files now.
    • arb_roundtrip_test.dart resolves the seed via test/_support/repo_root.dart instead of a cwd-relative path.
    • Removed unused intl: ^0.20.2 dependency.
  • Tooling. tool/sync_version.dart keeps lib/version.dart in lock- step with pubspec.yaml's version: field. --check mode runs in CI so a forgotten sync fails the build. Stricter hand-picked lints (unawaited_futures, avoid_dynamic_calls, prefer_relative_imports, directives_ordering, prefer_final_locals, cancel_subscriptions, close_sinks) layered on top of package:lints/recommended.yaml.
  • CI. .github/workflows/ci.yml runs dart pub get, version-sync --check, dart format --set-exit-if-changed, dart analyze --fatal-infos --fatal-warnings, and dart test on every push and PR.
  • Docs. planning/mvp-plan.md updated to reflect that intl_translation is deprecated and Dialect ships a focused ICU scanner; future agents reading the plan won't try to "fix" the code by ripping out the scanner.

Added #

  • M2. ARB read/write + ICU detection (lib/arb/).
    • arb_file.dart: typed model with description, context, placeholders, locked, glossary_exempt, source_hash, plus extras pass-through.
    • arb_parser.dart: reads ARB JSON; NFC-normalizes all string values (unorm_dart) so Vietnamese NFD vs NFC inputs hash identically.
    • arb_writer.dart: emits canonical ARB JSON matching the seed file byte-for-byte (sorted keys, @@locale first, @key after its key, blank-line separators).
    • icu_message.dart: focused scanner exposing extractPlaceholders, extractPluralCategories, hasExpressions. Handles '' and '…' ICU escape, nested plural/select branches, selectordinal, and the Round-2 "=N mirrors AND CLDR categories" pattern. 40 tests including byte-identical round-trip on the seed.
  • M1. Dart CLI scaffold: dart create --template=cli baseline, pubspec.yaml with SDK constraint ^3.4.0, args ^2.7.0 and lints ^6.0.0 dev. Entry point at bin/dialect.dart. DialectCommandRunner wires the seven v1.0 commands (init, import, describe, sync, check, status, serve) as stubs. --version and --help work. Anti-goal PR checklist in .github/PULL_REQUEST_TEMPLATE.md.
  • M0+. Multi-model convention convergence test across five models (Claude Opus, Claude Sonnet 4.6, Composer 2.5, Gemini 3.5 Flash, Codex 5.3). 9 of 10 axes converged. One Round 2 patch (Arabic CLDR plural worked example). See example/_validation/COMPARISON.md.
  • M0. Canonical convention text in example/dialect/dialect.yaml, glossary.yaml, and seed source/en.arb. Sample booking-style Flutter app under example/lib/. Validation report in planning/convention-validation-report.md.
0
likes
150
points
81
downloads

Documentation

API reference

Publisher

verified publisherdialect.tools

Weekly Downloads

AI-native localization toolkit for Flutter-led teams.

Repository (GitHub)
View/report issues
Contributing

License

MIT (license)

Dependencies

args, crypto, path, shelf, shelf_router, unorm_dart, yaml

More

Packages that depend on dialect