Skip to content

Localize version 1.0.0

Choose a tag to compare

@kipcole9 kipcole9 released this 30 Jul 23:16
· 327 commits to main since this release

[1.0.0] — July 31st, 2026

The first stable release. MF2 message validation is separated from parsing: Localize.Message.Parser.parse/1 checks syntax, Localize.Message.Validator.validate/1 checks the TR35 data-model rules, and everything that formats or serializes a message runs both. A message like .local $count = {$count :number} — a declaration that reads the variable it declares, which is a Duplicate Declaration error rather than a valid annotation of an external variable — is now rejected wherever it appears rather than only when formatted. The construct for annotating an external variable is .input {$count :number}.

Added

  • An ecosystem guide sets out the library's shape: a set of structured, locale-aware types, and the four operations — formatting, parsing, ordering and serialization — that apply both to them and to most of Elixir's own types. It tabulates which library provides each operation for each type.

  • Localize.Message.Validator.validate/1 is public, checking a parsed message against the TR35 data-model rules. Localize.Message.Parser.parse/1 remains syntax-only, so tooling that must accept invalid input — to_tokens/2 and the highlighters built on it — parses without validating, while everything that formats or serializes composes the two and reports syntax errors before data-model ones.

Fixed

  • Duplicate Declaration is now caught wherever a message is used, not only when it is formatted. .local $count = {$count :number} reads the variable it declares, so it was rejected by format/3 but serialized happily by canonical_message/2 and accepted at compile time as a Gettext msgid. Use .input {$count :number} to annotate an external variable.

  • Three further TR35 data-model rules are now enforced: Missing Selector Annotation (a selector that does not resolve, directly or through a chain of locals, to a declaration carrying a function), Variant Key Mismatch (a variant whose key count differs from the selector count), and Missing Fallback Variant (no variant with only * keys). None was detected previously — the first two were documented as enforced during match evaluation and were not, and all three could be formatted or serialized without error.

  • Localize.Message.canonical_message/2 no longer serializes a message that violates the data-model rules; it previously round-tripped every invalid form, including a .local declaration reading the variable it declares. The compile-time Gettext interpolator validates too, so a mis-authored msgid fails the build rather than surfacing at runtime.

  • The MF2 working-group data-model-errors.json conformance cases now assert the specific error reason rather than merely that some error occurred. The WG cases pass no bindings, so an incidental unbound-variable error satisfied the old assertion — which is how three unimplemented rules stayed green.

  • Localize.validate_territory/1 now accepts integer M49 codes as its typespec and documentation always promised — validate_territory(1) returns {:ok, :"001"}. An integer previously raised FunctionClauseError because the validator's guard omitted is_integer/1, even though its normalization already zero-padded integers. Numeric codes may also be given as strings ("001"); note that country-level M49 numbers such as 840 are not in CLDR's territory validity set, which uses the alpha-2 code ("US") there.