Skip to content

0.7.0-dev: generate Flutter, TypeScript and Python code without flutter gen-l10n - #13

Merged
mike-doctorina merged 10 commits into
masterfrom
feature/v1-multi-target-codegen
Oct 23, 2026
Merged

mike-doctorina merged 10 commits into
masterfrom
feature/v1-multi-target-codegen

Conversation

@mike-doctorina

@mike-doctorina mike-doctorina commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

generate no longer writes ARB files or runs flutter gen-l10n. It parses the sheet, validates it and emits the code itself, so generating needs only the Dart SDK and CI no longer downloads Flutter.

Targets (each sheet is still its own scope, every target has its own sheet filter):

  • Dart (Flutter): a drop-in replacement for the gen-l10n output (same files, classes, of(context), delegates, supportedLocales, intl plural/select). It adds a metadata API for tests: localizationScopes, messages (key, description, placeholders), per-locale templates / fallbackKeys and format(key, args). metadata: false turns the API off.
  • TypeScript (Astro / SolidJS): zero-dependency ESM with strict types, lazy per-locale loaders, resolveLocale, an eager bundle for SSR and a meta module.
  • Python (backend): zero-dependency (≥ 3.9) and mypy --strict clean. Typed snake_case accessors, get() with Accept-Language negotiation, format(key, **args).

Shared behaviour:

  • Embedded CLDR 48 plural rules, so every target renders plurals the same way.
  • {{name}} (mustache) placeholders for chosen sheets.
  • Validation with sheet/cell locations, plus --strict, --check and --diagnostics-format=json|github.
  • sheety.yaml config.
  • Manifest-tracked, staged writes that never touch foreign files, so the output can be wrapped into pub / npm / Python packages.

Breaking changes and migration notes are in CHANGELOG.md (0.7.0-dev).

The old example/ held real sheet data. It is replaced by a synthetic example covering all three targets.

Verification

  • dart format, dart analyze --fatal-infos --fatal-warnings: clean.
  • dart test: green. Integration tests run flutter analyze / flutter test, tsc --strict + node, Python + mypy + pyright.
  • Example --check: up to date. dart pub publish --dry-run: no errors.
  • Against the production sheet (real data stays outside the repo; those tests are opt-in via SHEETY_REAL_SNAPSHOT / SHEETY_REAL_REFERENCE):
    • Dart parity: in legacy mode the Dart output is byte-identical to 0.6.0 + gen-l10n for 443 files.
    • Cross-target rendering: TS and Python match Dart in 82k+ / 67k+ renders.
    • Consumer package: generating into it keeps flutter analyze clean, and its tool/version.dart computes a patch bump.

…en-l10n

`generate` now parses the sheet itself and emits the code directly. It
needs only the Dart SDK: no ARB files, no `flutter gen-l10n`, no Flutter
in CI.

- Dart target: a drop-in replacement for the gen-l10n output (same files,
  classes, delegates, lookup and intl plural/select behaviour) plus a
  metadata API for tests (scopes, messages, templates, fallback keys,
  dynamic format).
- TypeScript target for Astro / SolidJS: zero-dependency ESM, strict
  types, lazy per-locale modules, locale negotiation, meta module.
- Python target for backends: zero-dependency, typed, mypy --strict
  clean, Accept-Language negotiation.
- Embedded CLDR 48 plural rules so that all targets render plurals
  identically; {{name}} mustache placeholders for chosen sheets.
- Validation with sheet/cell locations (ICU syntax, placeholders, plural
  categories, tags, duplicates, reserved names, collisions); --strict,
  --check and json/github diagnostics.
- sheety.yaml config with per-target output, sheet filters and options.
- Manifest-tracked, staged, deterministic writes that never touch files
  the generator does not own, so the output can live inside a package.

BREAKING: ARB-only flags are removed, a real parent locale column wins
over its regional variant, duplicate labels are an error and rows with
missing trailing cells fall back instead of being dropped. See
CHANGELOG.md for the migration notes.
- Share locale column parsing (sanitize, LocaleTag.tryParse, dedupe by
  normalized code) between both pipelines; D1 goes through the same path,
  so "pt BR" is pt_BR and "Notes" or a second "pt_br" are skipped by
  localize as generate skips them. LocaleTag moves to lib/src/common.
- Treat whitespace-only cells as empty everywhere (isBlankCell).
- localize skips rows with an empty source text instead of translating
  the label; generate reports empty-base as a warning and leaves the
  message out, as its message says.
- localize validates translations with the generate ICU parser (and the
  mustache parser for {{name}} sources): unsupported ICU, changed
  placeholder kinds and {{x}} -> {x} are rejected; one-word plural and
  select branches are no longer mistaken for placeholders.
- localize quotes sheet titles in read ranges (v2, web1 are A1 refs).
- New plural-exact-as-category warning: =0/=1/=2 standing in for a CLDR
  category that matches other numbers too (Russian =1 without one).
- Clear error for mustache triple braces.
…l counts

intl tests CLDR `n` relations against the count itself, while the core
evaluator and the Python runtime used `f == 0`, which only sees three
fraction digits: es 1.001 was `one`, ar 1e-7 `zero`, mt 2.0001 `two`
(intl and TypeScript: `other`).

PluralOperands.intl now keeps the count (`n`) and isIntegral uses it.
The Python rules take `(n, i, v, f, t)` and compile `n` relations as
`n == i and ...`; intl_operands returns the count first and converts
Decimal / Fraction counts with float(), raising TypeError for anything
that is not a real number.

The Python integration tests now compare with Intl.pluralLogic (all
intl locales x fractional counts x branch sets) instead of the core
evaluator, and a unit test checks the evaluator against intl.
…at()

- Dart scope names only name a directory and <Stem>Localization classes,
  so default/new/switch/Function are valid again (as in 0.6.0); the
  TypeScript and Python scope checks keep rejecting their keywords.
- localizationArgument accepts any num for a double placeholder and an
  integral finite num for an int one, so format() behaves the same on
  the VM and on the web.
- templates is documented as the source text of the sheet (ICU, or
  mustache for mustache sheets).
- With the metadata API, locales.dart and the barrel ignore every lint
  and type the Locale list; gen-l10n mode output is unchanged.
…sing tools

- Check out the whole tree in the setup action (dart_test.yaml, .pubignore,
  LICENSE, example/ and tool/ were missing from the sparse list).
- Widen the path filters to everything a check depends on.
- Fail the publish dry-run step on a non-zero exit instead of `|| true`,
  and check the generated example with `generate --check`.
- Run all tests in a dedicated integration job with pinned node 22,
  Python 3.12, mypy 2.4.0 and pyright 1.1.414, SHEETY_REQUIRE_TOOLS=1,
  and a scan of the JSON report that fails on any skip other than the
  real-snapshot ones.
- Test helpers: SHEETY_REQUIRE_TOOLS=1 turns a missing-tool skip into a
  failure; pure-Dart PATH lookup instead of `which`; UPDATE_GOLDENS is
  refused when CI is set; tsc pinned to typescript@5.9.3; a failed npm
  install of the Solid check fails on CI.
- Keep test/golden/ and the CLDR sample table out of the pub archive.
…e reporting

- compare target output directories canonically (symlinks, case on
  case-insensitive file systems) and stage each target in its own
  .sheety-staging-<target> directory; refuse overlapping plans
- report a target left partly written as such in the summary and JSON
- never quote a credentials file in errors (no private key excerpt);
  localize reuses the same reader
- an unreadable or non-UTF-8 snapshot is a source-error; unexpected
  exceptions become an internal-error issue with exit code 2
- usage, config and source errors are printed as JSON / GitHub
  annotations according to --diagnostics-format
- a file looks generated only when it carries the whole header as a
  comment; taking over an unowned file is a took-over-file warning
- keep an edited generated file that is no longer generated (dropped
  from the manifest); warn when an edited file is overwritten
- reject Windows device names in generated paths (reserved-file-name)
- remove the unused OutputWriter.skipDirs
- GitHub annotations name files relative to the workspace
- source.dart uses the shared sheetRange of lib/src/common.
- The barrel exports the localize API and runGenerate only; the tests
  import the generate internals through test/sheety.dart.
- Writer issue paths are absolute, so GitHub annotations point at the file.
- Synthetic fixtures no longer reuse names of the production sheet.
- Example regenerated; README, CHANGELOG and CLAUDE.md describe the new
  behaviour.
@mike-doctorina mike-doctorina changed the title v1.0.0: generate Flutter, TypeScript and Python code without flutter gen-l10n 0.7.0-dev: generate Flutter, TypeScript and Python code without flutter gen-l10n Oct 23, 2026
The CI runs the unit tests as root in the dart container, where a
read-only directory does not stop a write. The failures are now caused
by a file where the staging directory goes and by a directory swapped
for a link out of the output directory after planning, both refused as
root too. The CI skip check also allows the case-insensitive file system
test, which can only run on macOS.
@mike-doctorina
mike-doctorina merged commit 953f121 into master Oct 23, 2026
2 checks passed
@mike-doctorina
mike-doctorina deleted the feature/v1-multi-target-codegen branch October 23, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant