dialect 1.0.4
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 tosynthetic-package: false, where the import ispackage:<pubspec_name>/l10n/app_localizations.dart. The plan now reads the example's pubspec name and renders the correct import; a newPUBSPEC_NAMEtoken threads through plan rendering. - B2 —
intlpin conflict on modern Flutter. The init plan pinnedintl: ^0.19.0, which foughtflutter_localizations' ownintlconstraint on Flutter 3.41+ SDKs and brokeflutter pub get. Nowintl: any, with a short note in the plan explaining why. - B3 —
dialect syncdropping every translated key under a namespace filter. The adapter read namespace metadata from translation entries, but by convention only the source ARB carries@keyblocks. With a namespace filter set inplatforms.flutter, the filter rejected everything. Now the filter joins translation keys against source-ARB metadata. The adapter API gains a requiredsource: ArbFile?param whenisSource: 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;--forcehandles 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-enddialect inittesting. The user's AI agent populatesafter/dialect/,l10n.yaml, and AppLocalizations callsites when run against the cloned starting state.- Canonical Dialect fixture (used by
check/status/ roundtrip tests) moved totest/fixtures/canonical/dialect/. Tests now point there instead ofexample/dialect/, decoupling them from the demo apps. - Multi-model validation harness moved to
examples/_validation/, reading fromexamples/before/lib/.INSTRUCTIONS.mdand the chat-only fallback updated for the new paths.
Changed — dashboard #
- Editor UX: replaced blur-to-save with an
EntryEditorpanel 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 #
- CI —
dart format --set-exit-if-changednow scopes tobin lib test toolinstead of.. Theexamples/*/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 formatagainst the dialect package itself to pick up two pending whitespace adjustments inlib/commands/sync.dartandtest/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, andwindows-x64only. Intel macOS and Linux ARM64 users build from source — see the new "Build from source" section in the README. - Homebrew formula errors clearly (
brew install→odie) on Intel Mac and Linux ARM64 instead of pretending to install. install.shrefuses 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 formatapplied to the nine M3.5 files I'd missed in the 1.0.0 commit. CI'sdart format --set-exit-if-changedcheck 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.namespaceis 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) andnamespace_required(source keys must declare@key.namespace). - All in-repo ARBs (live
example/, validation seed) re-keyed to the new shape;example/_validation/INSTRUCTIONS.mdrefreshed 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 --fix → sync → check
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:
dialect init- 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.shat the repo root. POSIXsh, OS/arch detection (macos-arm64,macos-x64,linux-x64,linux-arm64), version pinning viaDIALECT_VERSION, install location viaDIALECT_INSTALL_DIR(default~/.local/bin), SHA-256 checksum verification against the release'sSHA256SUMSasset. Refuses Windows with a pointer to the GitHub release zip. Hosted athttps://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 acrossmacos-latest(arm64),macos-13(Intel x64),ubuntu-latest(x64),ubuntu-24.04-arm(arm64), andwindows-latest. Each runner: pnpm install + build the dashboard,tool/build_dashboard.dart --no-pnpmto bake the SPA,dart compile exe, tarball/zip + SHA-256. Aggregate job composesSHA256SUMS, attaches every artifact +install.shto the GitHub release. Separatepubjob publishes to pub.dev via OIDC (no secret token). Separatehomebrewjob renders the formula fromhomebrew/dialect.rb.tmpland opens a bump-PR againstChauCM/homebrew-tap(skipped for prereleases —v1.0.0-rc.1etc. don't ship to Homebrew).action.ymlat the repo root — composite GitHub Action.uses: ChauCM/dialect@v1withargs:(defaultcheck --strict),version:(defaultlatest), andworking-directory:inputs. Installs via the sameinstall.shscript so the action and the curl-installer share one code path.homebrew/dialect.rb.tmpl— formula template; per-target{{*_SHA256}}and{{VERSION}}placeholders. Branched byHardware::CPU.arm?so a single formula covers macOS arm64, macOS x64, linux arm64, linux x64.tool/render_homebrew_formula.dart— reads the template, fetchesSHA256SUMSfrom 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.md→LICENSEper pub.dev convention.CHANGELOG.mdgains a top-level0.1.0-devheading per pub.dev convention so the nextdart pub publish --dry-runis down from 4 warnings to the single deferreddocs/ → 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.dartbindslocalhost:4077by 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— parseddialect.yamlplus the resolved project name.GET /api/strings?locale=<loc>— every source key with the matching translation entry. Includesdescription,context,placeholders,locked,glossary_exempt,source_hash, and computedstale+missingflags.PUT /api/strings/<key>— body{ locale, value, locked?, glossary_exempt? }. Writes back throughArbWriter(canonical formatting). Locking writes@key.source_hashperdialect/spec/source_hash.md; unlocking clears it. Empty metadata blocks are dropped (no@key: {}noise on translation files).GET /api/glossary— parsedglossary.yaml.GET /api/status— reuses the M6computeStatusmath byte-for-byte, so the dashboard footer and the CLI agree.OPTIONSpreflight + permissive CORS so the SPA can run underpnpm devagainst 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 thelockedfield 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— typedfetchConfig/fetchStrings/fetchGlossary/fetchStatus/putStringwrappers.
- Static-asset embedding.
tool/build_dashboard.dartwalksdashboard/dist/afterpnpm buildand emitslib/server/embedded_assets.g.dart— a singleconst Map<String, List<int>>from forward-slash paths to file bytes.dart compile exe bin/dialect.dart -o build/dialectproduces an 8.1 MB self-contained binary that serves the SPA with zeronode_moduleson 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/--hostflags, project-load preflight surfaces "no project" as exit 66 before binding the port, SIGINT clean shutdown. ArbMetadata.copyWith— new helper with a sentinel forsourceHashso 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 writesReservar AHORA+@key.source_hashtoes.arbon disk; unlock reverts both.
- Dart Shelf server at
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 withDialect.AspNetCore(v1.1) and the third-party backend libraries documented indocs/platforms-backend.md.flat-json.md— sibling format that strips ICU plural/select/selectordinal to a single plain string by taking theotherbranch and recursively stripping nested expressions. Documents the loss-of-information trade-off and theinfohintdialect syncemits when a project's source uses plurals andflat-jsonis selected.state.md—.dialect/state.jsonshape for soft-modedialect checkacknowledgements (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.AspNetCoreNuGet (v1.1) depends on a stableicu-jsoncontract; shipping v1.0 without versioned specs would cost us users at the first breaking change.
Added #
- M8.
dialect checksemantic heuristics — four new rules layered on the M4Ruleinterface, runner, and report.source_equality(warning) — flags translations identical to the source. Filters short / symbol-only values; honors@key.locked: trueas 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 vialength_ratio:indialect.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 glossaryterm:(whole-word, case-insensitive), each target translation must contain a recognizable prefix of the canonicaltranslations.<locale>. Suffix-inflection tolerance baked in (drops last 2 chars when len > 4, so "Reservar" matches "Reserva"/"Reservamos"). Honors@key.glossary_exempt: trueon the source entry — the documented escape hatch for non-literal uses.
lib/glossary/glossary_loader.dart— typed loader fordialect/glossary.yaml. Empty when file absent (no error); throwsFormatExceptionon malformed YAML. Loaded once atDialectProject.loadand exposed asproject.glossaryso every rule reads from the same in-memory snapshot.--strict-lengthplumbing.report.dartlearned to honor the flag independently of--strict: under bare--strict, every warning promotes to error exceptlength_ratio(length ratios are noisy enough that a blanket CI gate produces too many false positives).--strict-lengthis the explicit opt-in.- CHANGELOG note for example/ polish. Running
dialect checkon the canonicalexample/now surfaces 7 real semantic warnings (source-equality onTotal/Emailcarryovers; 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/.csfiles; 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.yamlas the canonical source if anything conflicts. - Templates live at
templates/import_plan.mdandtemplates/describe_plan.md(reviewable in PRs as Markdown);tool/sync_templates.dartbakes them intolib/templates/{import,describe}_plan_md.dart. Runtime token substitution vialib/templates/plan_render.dart. Tokens:{{FROM}},{{PATH}},{{SOURCE_LOCALE}},{{TARGET_LOCALES}},{{PROJECT_NAME}}(readsproject.namefromdialect.yamlwith 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--pathon import, unknown--from) / 65 malformeddialect.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_hashno longer matches the current source value's hash.
- Columns:
@key.source_hashspec atdialect/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, anddialect 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.dart—computeSourceHashimplementing 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:indialect.yaml. For each platform withformat: arb, writes<output>/app_<locale>.arbfiles (Flutter'sgen_l10nconvention). - Source ARB output keeps
@keymetadata; translation outputs strip metadata per the convention. - Namespace filter: keys whose prefix (before the first
.) isn't inplatforms.<p>.namespacesare 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/projandcd /path/to/proj && dialect syncwrite identical bytes to the same locations. - Idempotent: re-running
syncwith no changes does not touch any files (mtime preserved)._maybeWriteskips writes when the desired bytes already match disk. - Sync does not auto-fix the input ARBs. Run
dialect check --fixseparately to normalize sources; sync produces canonical output regardless (because it goes throughArbWriter). - Non-
arbformats print a "lands in v1.1" hint and skip without erroring.
- Walks
DialectConfig.platformsparsing — typedPlatformConfig(name,output,format,namespaces).length_ratioandprojectblocks still flow throughextrasfor M8.lib/adapters/arb_adapter.dart—ArbAdapter.prepare(filter + strip),ArbAdapter.encode(delegates toArbWriter),ArbAdapter.filenameFor(theapp_<locale>.arbconvention; v1.1 spec will make this configurable viaplatforms.<p>.filename_pattern).
Added #
- M4.
dialect check— structural validation +--fixnormalization.- Five structural rules under
lib/checks/structural/:missing_keys,placeholder_match,plural_categories,empty_values,orphan_metadata. Each rule emits typedIssueobjects with severity, locale, key, file path, line number, and a real soft-mode hint (not a stub). TheRuleinterface 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. Powersfile:linehints 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) readsdialect.yaml+ source ARB + every target translation. MinimalDialectConfigparser; M5 extends withplatformsandlength_ratio. --fixmode re-emits every ARB throughArbWriter: sorts keys, hoists@@locale, places@keyblocks 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.arbas 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;
--strictpromotes everything to errors.
- Five structural rules under
Changed #
- M3 follow-up. Three small fixes before M4 starts.
templates/glossary.yamlnow ships with one example term + a "Replace or delete this entry" hint, the same pedagogical role ascommon.exampleinsource/en.arb. Was effectively empty before.- SDK floor bumped from
^3.4.0to^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 inplanning/mvp-plan.md. tool/sync_templates.dartsimplified — thedart formatpipe workaround removed in favour of// dart format offdirectives 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 --forceis whole-tree overwrite for the canonical files. A separate--refresh(write only missing files) is friendlier for users with customizeddialect.yaml.project.namecould auto-detect from a siblingpubspec.yaml'sname: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). --forceoverwrites an existingdialect/directory.- Writes
dialect/dialect.yaml,dialect/glossary.yaml,dialect/source/en.arb; createsdialect/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.
- Accepts an optional positional
- Templates pipeline. Canonical seed YAML/ARB at repo-root
templates/;tool/sync_templates.dartbakes them into Dart constants underlib/templates/*.dart.--checkmode wired into CI so a hand-edited generated file fails the build. The generator pipes output through in-projectdart formatso the on-disk shape always matches whatdart formatwould produce. 9 init tests intest/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 inArbFile.fileMetadata(e.g. Fluttergen_l10n's@@last_modified). Writer emits it after@@localein sorted order. Prevents silent data loss ondialect sync. - Orphan
@keyblocks (no matching key/value) preserved inArbFile.orphanMetadataso M4 can surface them as structural errors without re-reading raw JSON. Writer skips orphans by construction —dialect check --fixstrips them implicitly. example/dialect/source/en.arbsynced with the canonical 30-key extract from_validation/runs/claude-post-patch/. Matches the translation files now.arb_roundtrip_test.dartresolves the seed viatest/_support/repo_root.dartinstead of acwd-relative path.- Removed unused
intl: ^0.20.2dependency.
- Parser preserves unknown
- Tooling.
tool/sync_version.dartkeepslib/version.dartin lock- step withpubspec.yaml'sversion:field.--checkmode 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 ofpackage:lints/recommended.yaml. - CI.
.github/workflows/ci.ymlrunsdart pub get, version-sync--check,dart format --set-exit-if-changed,dart analyze --fatal-infos --fatal-warnings, anddart teston every push and PR. - Docs.
planning/mvp-plan.mdupdated to reflect thatintl_translationis 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 withdescription,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,@@localefirst,@keyafter its key, blank-line separators).icu_message.dart: focused scanner exposingextractPlaceholders,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=clibaseline,pubspec.yamlwith SDK constraint^3.4.0,args ^2.7.0andlints ^6.0.0dev. Entry point atbin/dialect.dart.DialectCommandRunnerwires the seven v1.0 commands (init,import,describe,sync,check,status,serve) as stubs.--versionand--helpwork. 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 seedsource/en.arb. Sample booking-style Flutter app underexample/lib/. Validation report inplanning/convention-validation-report.md.