|
| 1 | +# Localize 1.0 Readiness — Fresh Assessment |
| 2 | + |
| 3 | +Date: July 15, 2026. Baseline: v0.49.0 + unreleased (commit 8c4235d7), Elixir 1.20.1-otp-29 / Erlang 29.0.1. Method: full quality-gate run, `mix hex.build` package inspection, and four verification passes (API freeze, API hygiene/security, documentation, test wiring) re-checking every item in the July 4 plan (`RELEASE_1.0_READINESS.md`) against current source. |
| 4 | + |
| 5 | +## Executive summary |
| 6 | + |
| 7 | +The July 4 plan is almost entirely executed. Milestones 1–3 are done and verified: all six TR35 nonconformances (C1–C6) fixed, all consumer-breaking packaging bugs (B1/B2) fixed and verified in the built hex tarball, security items S1–S6 resolved, the API-freeze renames (B3/B4) shipped in 0.43.0 with deprecated delegates, and the hiding pass (B5) applied. All five quality gates pass clean and coverage is 91.23% (gated at Mix's default 90% threshold on the CI lint row). What remains before an RC is one API decision, four visibility-deviation confirmations, one genuine test gap (the download happy path), a handful of doc nits, and the `localize_mcp` release plumbing. |
| 8 | + |
| 9 | +## Quality gates (July 15, Elixir 1.20.1-otp-29) |
| 10 | + |
| 11 | +| Gate | Result | |
| 12 | +|---|---| |
| 13 | +| `mix format --check-formatted` | ✅ clean | |
| 14 | +| `mix compile --warnings-as-errors` | ✅ clean | |
| 15 | +| `mix test --cover` | ✅ 29,069 passed (945 doctests, 17 properties), 0 failures, 2 excluded; coverage 91.23% | |
| 16 | +| `mix dialyzer` | ✅ Total errors: 0 | |
| 17 | +| `MIX_ENV=release mix docs` | ✅ no localize-owned doc warnings (deps emit their own; see nits) | |
| 18 | + |
| 19 | +`mix hex.build`: 264 files; `c_src/Makefile`, `c_src/env.mk`, `c_src/localize_nif.cpp` present (B1 verified at the artifact level), no stray `.o` files, `priv/localize/locale_hashes.etf` integrity manifest bundled (S1 verified), Elixir floor `~> 1.17` retained. |
| 20 | + |
| 21 | +## Verified done since the July 4 baseline |
| 22 | + |
| 23 | +* **C1–C6 conformance fixes** — shipped across 0.42.0–0.44.0 (compact plurals + operands, BCE era years, significant digits, day periods, locale week configuration, Annex C alias disambiguation). |
| 24 | + |
| 25 | +* **B1 NIF packaging** — fixed and verified in the tarball. **B2 json_polyfill** — resolved by policy: `only: [:dev, :test]` stays until the announced OTP 26 drop on December 31, 2026. |
| 26 | + |
| 27 | +* **B3 option unification** — `Unit.to_string` and `Duration.to_string` both take `:format`; `:style` is a documented deprecated alias on Duration ("removed by Localize 1.0"). `Unit.display_name` and `Localize.quote/2` settled on the naming-vs-formatting split (0.43.0). |
| 28 | + |
| 29 | +* **B4 known_/available_ semantics** — implemented in 0.43.0: `known_*` = locale-independent universe, `supported_*` = configuration, `*_for` = localized inventories. Deprecated delegates in place for every renamed function (removal window "no later than December 2026"). `Territory.known_styles/0` and public `Territory.known_territories/0` exist. |
| 30 | + |
| 31 | +* **B5 hiding pass** — all `Localize.Utils.*`, `DataLoader`, `Substitution`, `FormatCache`, `Locale.Loader`, `LanguageTag.Parser`, `Message.Interpreter`, `LocaleDefaults`, `SupplementalData`, `Validity` are `@moduledoc false`; the keep-public list is public; `guides/conformance.md` no longer references hidden modules; the ex_doc group map covers everything except the bare `Localize` module (conventional). |
| 32 | + |
| 33 | +* **Error-shape policy** — Language/Script `display_name` return `{:error, %InvalidValueError{}}` on bad options; `Localize.quote/2` errors on unknown format; all 5 missing bang variants exist; `Localize.validate_currency/1` delegate exists (all 0.43.0). |
| 34 | + |
| 35 | +* **Security** — S1 hash manifest (0.44.0, fail-closed with `LocaleIntegrityError`); S2 corrupt cache decode → miss/stale at both sites; S3 atomic write-temp-then-rename; S4 `autoredirect: false`; S5 `existing_atom` for `:usage`; S6 resolved via Utils modules going internal. NIF-backend locale validation closed (0.43.0). |
| 36 | + |
| 37 | +* **Docs** — all four planned guides exist (plural_rules, list_formatting, locale_validation, display_names); README feature list complete, install snippet hex-correct, all guide links hexdocs; CHANGELOG fully Keep-a-Changelog; `to_range_string/3` and `!/3` documented; doctests wired for 5 of the 6 flagged modules; conformance.md day-period rows accurate; collation known-failures comment refreshed to zero-threshold reality; `usage-rules.md` spot-checked accurate. |
| 38 | + |
| 39 | +* **Test wiring** — RBNF reference data exercised for en/de/es/fr; MF2 working-group suite runs against the formatter (with documented exclusion groups); likely-subtags FAIL rows asserted with zero skips; every exception module's `message/1` exercised, multi-reason exceptions enumerated per reason atom (gettext msgid-drift guard); coverage ignore-list guarded against orphaned entries; OTP 26 CI rows present pending the December drop. |
| 40 | + |
| 41 | +* **Credo strict** — zero findings, enforced in CI (0.45.0). |
| 42 | + |
| 43 | +## Remaining before RC |
| 44 | + |
| 45 | +### A. API decisions (freeze at 1.0) |
| 46 | + |
| 47 | +1. **`Currency.currencies_for_locale/3` and `!/3` still take positional `only \\ :all, except \\ nil` filter args** (`lib/localize/currency.ex:639`, `:989`). The plan called for an options keyword list. This is the last remaining breaking-shaped API decision; decide (convert with deprecated positional heads, or explicitly accept the positional form) before RC. |
| 48 | + |
| 49 | +2. **Four deviations from the frozen B5 visibility list** — RESOLVED July 15. Three confirmed correct: `List.Pattern` public (its struct is documented API in `Localize.List`), `Number.Format.Meta` public (its type appears in the public `Format.Compiler` specs), `Utils.Code` hidden (nothing public references it). One was wrong: `Number.Format.Options` was hidden while `validate_options/2` is a documented performance idiom in the Performance guide, the number cheatsheet and usage-rules.md — its moduledoc is restored and its doctest wired. |
| 50 | + |
| 51 | +3. **Deprecated-delegate removal timing** — Duration `:style` says "removed by Localize 1.0"; the B4 delegates say "no later than December 2026". If 1.0 ships before December, decide whether 1.0 removes all of them in one breaking sweep (cleanest) or carries them to a 1.1. |
| 52 | + |
| 53 | +### B. Test gaps |
| 54 | + |
| 55 | +4. **`Provider.download_locale/1` happy path has no end-to-end test.** — DONE July 15: `test/localize/locale/download_test.exs` drives HTTP 200 → integrity verify → cache write → read-back against a local `:httpd` server, plus 404, tampered-content and missing-manifest-entry failure paths. Required a `:locale_base_url` config seam in `Provider.base_url/0` (also useful for self-hosted mirrors). |
| 56 | + |
| 57 | +5. **MF2 formatter conformance exclusion groups** — DOCUMENTED July 15: `guides/conformance.md` Part 9 now lists every exclusion group precisely (Error Handling and Data Model rows corrected, new "MF2 known conformance gaps" section; bidi row downgraded to Partial since `u:dir`/`u:id` expression *options* are unimplemented — only the `@u:dir` attribute form works). Closing the gaps themselves remains an explicit 1.0 scope decision. |
| 58 | + |
| 59 | +6. **Favor-region column parsed but never asserted** in `likely_subtags_test.exs` (`_remove_region` in every comprehension); the feature is unimplemented. Either implement + assert, or drop the parse and note it as out of scope. |
| 60 | + |
| 61 | +### C. Documentation nits |
| 62 | + |
| 63 | +7. **`Message.JSON.from_json/1` docstring example is wrong** — DONE July 15. The example had in fact already been corrected; only the doctest was still unwired and the test comment stale. `doctest Localize.Message.JSON` is now enabled and passing. |
| 64 | + |
| 65 | +8. **Collation's bare-return deviation** — DONE July 15: the `Localize.Collation` moduledoc now has a "Return value convention" section explaining the bare returns and that unrecognised option values fall back to defaults (verified: they do not raise). |
| 66 | + |
| 67 | +9. **README has no MCP section.** The skill is documented; `localize_mcp` (complete, 52 tests passing, 11 tools) is unpublished and unmentioned. Once it's on hex, add a section beside the skill one. |
| 68 | + |
| 69 | +### D. Mechanical nits |
| 70 | + |
| 71 | +10. ~~`mix.exs` deprecated `xref: [exclude: ...]` shape~~ — WITHDRAWN July 15: the warning in the gates log came from compiling the `earmark_parser` dep in the docs stage; localize's own mix.exs has no `xref` key and a fresh release-env compile is clean. |
| 72 | + |
| 73 | +11. Two always-true type warnings in `test/localize/unit/data_test.exs` — DONE July 15 (`refute Enum.empty?(...)`). |
| 74 | + |
| 75 | +12. `.github/workflows/upload-locales.yml` cache key — DONE July 15: OTP/Elixir pins moved to workflow `env` and included in the cache key and restore-keys. |
| 76 | + |
| 77 | +13. S7 locale-id shape validation — DONE July 15: `Provider.locale_file_name/1` (the single choke point for both cache paths and download URLs) raises `ArgumentError` unless the id matches `^[A-Za-z0-9_-]+$`, with tests for traversal-shaped atoms. |
| 78 | + |
| 79 | +## Companion deliverables |
| 80 | + |
| 81 | +* **Claude Code skill** — shipped in 0.47.0, committed, README-documented, marketplace manifest in place. Done. |
| 82 | + |
| 83 | +* **`localize_mcp`** — implemented (11 tools, 52 tests passing on 1.20.1-otp-29, version 0.1.0) but a single local "Initial commit": no GitHub remote, not published to hex. Remaining: create repo + push, release review per house checklist, publish, cross-link from README (item 9). |
| 84 | + |
| 85 | +## Suggested order |
| 86 | + |
| 87 | +Updated July 15: items 2, 4, 5 (documentation half), 7, 8, and 10–13 are done as annotated above. Remaining: |
| 88 | + |
| 89 | +1. Item 1 (Currency positional args) and item 3 (delegate removal timing) — the two decisions that freeze. |
| 90 | +2. Scope decisions: close or accept the MF2 conformance gaps now documented in the guide (item 5), and implement-or-drop favor-region (item 6). |
| 91 | +3. Item 9 — `localize_mcp` release (repo, review, hex publish); README MCP section. |
| 92 | +4. Full house release review; 1.0.0-rc.1; soak; 1.0.0. |
| 93 | + |
| 94 | +Nothing found in this pass contradicts the stability-period intent: items 1–3 are decisions plus small diffs, and everything else is additive tests/docs. |
0 commit comments