Localize version 1.0.0
[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/1is public, checking a parsed message against the TR35 data-model rules.Localize.Message.Parser.parse/1remains syntax-only, so tooling that must accept invalid input —to_tokens/2and 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 byformat/3but serialized happily bycanonical_message/2and 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/2no longer serializes a message that violates the data-model rules; it previously round-tripped every invalid form, including a.localdeclaration 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.jsonconformance 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/1now accepts integer M49 codes as its typespec and documentation always promised —validate_territory(1)returns{:ok, :"001"}. An integer previously raisedFunctionClauseErrorbecause the validator's guard omittedis_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 as840are not in CLDR's territory validity set, which uses the alpha-2 code ("US") there.