dialect 0.1.0-dev copy "dialect: ^0.1.0-dev" to clipboard
dialect: ^0.1.0-dev copied to clipboard

AI-native localization toolkit for Flutter-led teams.

Changelog #

All notable changes to the Dialect CLI are tracked here. Pre-1.0 entries are work-in-progress milestones from planning/mvp-plan.md.

[Unreleased] #

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
0
points
95
downloads

Publisher

verified publisherdialect.tools

Weekly Downloads

AI-native localization toolkit for Flutter-led teams.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

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

More

Packages that depend on dialect