chore(deps): update dependency localize to v1 #33
Loading…
Reference in a new issue
No description provided.
Delete branch "renovate/localize-1.x"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR contains the following updates:
~> 0.50→~> 1.0Release Notes
elixir-localize/localize (localize)
v1.3.0Compare Source
Added
Localize.Date.parse/2,Localize.Time.parse/2andLocalize.DateTime.parse/2parse a localized date, time or datetime string back to a value. They delegate tocalendrical, which the application must add as a dependency.Localize.DependencyRequiredErroris returned when an operation needs a companion package the application does not depend on, naming the:packageand the:operation. It is what the newparse/2functions return whencalendricalis absent.Changed
Localize.validate_territory/1returns the canonical territory code, sovalidate_territory("AN")is now{:ok, :CW}where it was{:ok, :AN}— likewiseSUto:RU,DDto:DE, andCSandYUboth to:RS. This matchesvalidate_locale/1, which already resolved them.Fixed
Localize.Utils.Math.mod/2andamod/2carry overloaded contracts, so integer arguments type as integer results under dialyzer. Downstream calendar arithmetic no longer types as float-possible when it flows through these functions.Localize.validate_territory/1accepts territory codes that CLDR replaces rather than lists:"UK", and the alpha-3 and numeric forms"GBR"and"826", were all rejected and now resolve to:GB. This matters for anything mapping a ccTLD to a territory, since.ukis the domain whileGBis the code.Localize.Interval.to_string/3andto_parts/3no longer raiseFunctionClauseErrorforen-CAwithfields: :month_and_day, format: :short. CLDR publishes analt="variant"interval pattern for en-CA, and it is now resolved as a single date's pattern is, honouring:prefer.en-CAskeleton formats such as:yMdand:MEdrender the locale's standard pattern,2026-05-03, rather than the day-first variant3/5/2026they returned whatever:preferasked for.prefer: :variantstill selects the variant.v1.2.0Compare Source
Changed
Breaking.
Localize.Number.Parser.scan/2now treats a space as a grouping separator in locales that group with one, so runs of digits that it previously returned separately can come back as a single number. Underfr,"chambres 12 14 16"was["chambres ", 12, " ", 14, " ", 16]and is now["chambres ", 121416]; passlenient: falseto require exact grouping sizes and get the old result. This is what lets"1 234,5"be found as one number, which is the point of the change.Breaking.
Localize.Number.parse/2andscan/2validate that grouping separators sit in plausible positions, matching ICU, so input that was previously accepted can now fail. Underfr,parse("3 4")was{:ok, 34}and is now an error, because a single digit is not a group in any locale.Localize.Number.parse/2andLocalize.Number.Parser.scan/2take a:lenientoption governing that validation.true, the default, requires each group to be at least two digits, which is ICU's lenient rule;falserequires exactly the locale's grouping size — three for most locales, and the 3-then-2 ofen-IN.Fixed
Timezone display names use the exemplar city CLDR ships rather than one derived from the IANA identifier. The lookup matched
%{city: _}where the zone data keys:exemplar_city, so it never fired: 48 zones inenand 190 injawere named from the identifier instead, losing renames (America/Godthabis"Nuuk"), accents ("Córdoba"), and in non-Latin locales the script entirely.Timezone display names resolve three-part identifiers such as
America/Indiana/Knox, whose leaf CLDR keys by string one level deeper. They were split into two parts, so the lookup sought a city named"Indiana/Knox"and fell back to"Knox"rather than CLDR's"Knox, Indiana".Localize.Number.Parser.scan/2finds a grouped number written with an ordinary keyboard space in a locale that formats with U+202F. Underfr,scan("1 234,5")was[1, " ", 234.5]and is now[1234.5].Localize.Number.parse/2treats every character in[:Zs:]as a grouping space, per TR35's loose matching for lenient parsing. It previously accepted only U+0020 and the locale's own separator, so afrnumber carrying U+00A0 or U+2009 — as copied out of formatted output — failed to parse.Localize.Number.parse/2ignores every character in[:Cf:], per the same passage's instruction to ignore format characters, "in particular ... any RLM, LRM or ALM used to control BIDI formatting". CLDR writes the minus sign ofar,he,faand 16 other locales as a BIDI mark followed by the sign, so a negative number copied out of that text previously failed to parse.Localize.Number.parse/2applies CLDR'sparseLenientscharacter folds, which were generated into the locale data but never read. The minus, plus, comma and full-stop families now fold to their canonical form, so the 18 locales writingminusSignas U+2212 —fa,fi,svandnoamong them — parse their own negative numbers back. Per TR35 the fold applies to the locale's own separators as well as the input, which is what letsde-CHaccept both spellings of its apostrophe group separator.Added
Localize.DateTime.Timezone.exemplar_city/3returns the localized exemplar city for an IANA timezone identifier.derive: falsereturns an error rather than deriving a name from the identifier, distinguishing a name CLDR vouches for from one this library invented.v1.1.1Compare Source
Fixed
Localize.Unit.to_string/2selects the plural category from the rendered digits rather than the input value, as TR35 defines the plural operands over the source number — "the visual appearance of the digits of the result". The float1.0renders as1and is now"1 hectare"rather than"1 hectares", and conversely1withfractional_digits: 2renders as1.00and is now"1.00 hectares"rather than"1.00 hectare".Localize.Unit.localize/2now resolves preferred units whose name is more than one word. It converted the atoms fromLocalize.Unit.Preference.preferred_units/2withAtom.to_string/1, yielding"cubic_inch"where the parser wants"cubic-inch", so 35 of CLDR's 90 preferred units failed with aLocalize.ParseError— taking the default usage of speed, volume, area, energy and pressure with them.Changed
Unicode-3.0alongsideApache-2.0, andLICENSE.mdgains a "Unicode Data" section setting out which CLDR and UCD data is embedded, what it becomes, and reproducing the Unicode copyright and permission notice as that license requires. Modules emitted bymix localize.unit.gen_conversionscarry the same dual-license SPDX header, so a consumer runningreuse lintover the generated file no longer needs a.licensesidecar.v1.1.0Compare Source
Fixed
t:Localize.Currency.t/0type now declares:iso_digitsasnon_neg_integer() | nil. Historic currencies (for example:BGN) have no ISO 4217 minor-unit definition and carrynil, which the module's ownis_nil/1guards already handle; the previousnon_neg_integer()type caused falsepattern_matchwarnings in downstream code that guards againstnil.Added
mix localize.unit.gen_conversionsgenerates a dependency-free unit conversion module from CLDR data, for embedded targets where compiling Localize costs more than the project itself. There is no unit selection to make: Localize's own parser is inlined into the generated module, so it accepts every identifierLocalize.Unitaccepts — prefixed, powered and-per-compound alike — throughto_base/2,resolve/1and a compile-time~usigil.v1.0.1Compare Source
Fixed
Localize.LanguageTag.best_match/3now prefers a territory's own language when candidates are otherwise equidistant, sosgs(Samogitian, spoken in Lithuania) matchesltrather thanen-LT. The distance trie scores every candidate sharing the desired script and territory identically, which previously left the winner to depend on the order of the supported list.Localize.ParseErrorreports an over-cap input asreason: :input_too_largewith the byte counts in new:sizeand:limitfields, instead of interpolating them into a prose:reasonstring that its own type never allowed. The message, number and unit-identifier parsers all use it, and the oversized input is no longer retained on the exception.v1.0.0Compare Source
The first stable release. MF2 message validation is separated from parsing:
Localize.Message.Parser.parse/1checks syntax,Localize.Message.Validator.validate/1checks 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.v0.50.0Compare Source
Added
Localize.LanguageTag.remove_likely_subtags/2and!/2acceptfavor: :script | :regionto select the TR35 removal variant (zh-Hant-TWminimizes to "zh-Hant" or "zh-TW"), verified against the full CLDR likely-subtags test data.The MessageFormat working group's
:test:function,:test:formatand:test:selectregistry functions are implemented, so the WG conformance suite's selection-mechanics and fallback cases now run.The MF2 numeric functions accept the
signDisplayoption (auto,always,exceptZero,negative,never), with the plus sign taken from the locale's number symbols.An MF2 pattern expression that re-annotates a declared variable operates on the declaration's original value, and numeric functions inherit unset options from a numeric declaration —
.local $x = {41 :integer signDisplay=always}rendered with{$x :offset add=1}produces "+42".The locale download base URL can be overridden with
config :localize, locale_base_url: "..."for deployments mirroring the locale files; downloads are verified against the bundled hash manifest regardless of source.Changed
An unannotated MF2 placeholder with a numeric operand formats with the locale-aware
:numberdefault ({$pi}renders "3.142" in en and "3,142" in fr), matching the MF2 implicit formatting rules; previously the value was stringified verbatim.MessageFormat 2 follows the specification's error semantics: unknown functions are an error (
Localize.FormatErrorreason:unknown_function) instead of formatting the operand, and data-model validation rejects duplicate declarations, duplicate option names, and duplicate variants (NFC-normalized keys) with dedicated error reasons.MessageFormat 2 function validation is strict per TR35: the
selectoption must be a literal set directly on the selector expression, digit-size options must be non-negative integers, string operands of numeric functions must match thenumber-literalproduction, and:currency,:unit,:date,:timeand:datetimeare rejected as selectors.Localize.Number.Format.Optionsis documented again:validate_options/2is the supported way to pre-resolve formatting options for hot loops (as the Performance guide describes), so the module is part of the public API.The TR35 conformance guide documents the MessageFormat 2 working-group suite exclusions precisely: unknown-function fallback, duplicate declaration/option validation, select and digit-size option validation,
u:dir/u:idexpression options, and the:isolatebidi strategy deviation.Deprecated
Localize.Currency.currencies_for_locale/2,3,currencies_for_locale!/2,3,currency_strings/2,3andcurrency_strings!/2,3take the include/exclude filters as:onlyand:exceptoptions instead of positional arguments. The positional forms still work but are deprecated and will be removed by Localize 1.0 and no later than December 2026.Fixed
Localize.Locale.Provider.allow_download?/1ensure the module is loaded beforefunction_exported?/3, so a cold module no longer silently takes the gregorian or default fallback branch.Security
Localize.Locale.Provider.locale_file_name/1rejects locale identifiers that do not have the shape of a CLDR locale id, as defense in depth for the cache-path and download-URL interpolations.v0.49.0Compare Source
Fixed
Change the namespaces on the
leexlexers andyeccparsers to avoid module name clash withex_cldrversions. Thanks to @jerryluk for the PR. Closes #40.The coverage ignore list follows the renamed lexer/parser modules, restoring measured coverage above the 90% CI threshold; a guard test now fails when an ignore-list entry names a module that no longer exists.
v0.48.0Compare Source
Added
Localize.Unit.to_string/2acceptshumanize: trueto render any unit in the unit people expect for its magnitude: bit-, byte-, hertz- and watt-based units scale through thehumanize/2prefix ladder (honouringsystem: :si | :iec) and all other units render through usage-based preferences. An explicit:usageoption keeps precedence for preference-scaled units.Localize.Unit.humanize/2andhumanize!/2also scale hertz- and watt-based units through the SI prefix ladder ("2.5 gigahertz", "3.2 gigawatts") — like bits and bytes, these are locale-invariant quantities that CLDR's unit preferences do not cover. IEC prefixes remain restricted to bit- and byte-based units.v0.47.0Compare Source
Added
Localize.Unit.humanize/2andhumanize!/2convert a byte- or bit-based unit to the prefix that best fits its magnitude, so1_500_000 byteformats as "1.5MB" withformat: :narrow. The:systemoption selects:si(powers of 1000, default) or:iec(powers of 1024) prefixes.Localize.Number.to_string/2acceptsformat: :shortandformat: :longas ex_cldr-compatible aliases, resolving to:decimal_short/:decimal_long, or to:currency_short/:currency_longwhen a:currencyis specified.mix localize.update_cldrorchestrates the CLDR data-update pipeline (copy sources → generate supplemental → compile gate → generate locales → test gate), with--checkpreflight,--localessubsetting, and each step isolated in a fresh VM. The full update process is documented in the consolidated CLDR Update Guide (CLDR_UPDATE_INTEGRATION.md).A Claude Code skill covering all major subsystems (numbers, dates/times, units, lists, collation, MessageFormat 2, locale validation and configuration), installable via
/plugin marketplace add elixir-localize/localize. All 300+ skill examples are execution-verified against the library.Fixed
Timezone formatting consults zone-level names before metazone names per TR35, so a UTC
DateTimerenders "UTC" / "Coordinated Universal Time" instead of "GMT" / "Greenwich Mean Time", and Europe/London in summer renders "British Summer Time" instead of "GMT+01:00". Thanks to @gmile for the PR. Closes #37.Date and time formatting applies the locale's default numbering system to all numeric fields, so
locale: :mrrenders "१५ मार्च, २०२१" (likewise bn, fa, my and others) matching ICU. Pattern-levelnumbers=and caller:number_system_overrideskeep precedence. Thanks to @gmile for the PR.A
-u-nu-locale extension overrides the default numbering system in date and time formatting per TR35:"mr-u-nu-latn"renders latn digits and"en-u-nu-deva"renders deva digits, including when the locale is set withLocalize.put_locale/1.Territory-specific collation tailorings apply from locale data: sorting with
locale: "fr-CA"now uses French Canadian backwards accent comparison. Previously the lookup used only the bare language, silently dropping fr-CA's[backwards 2]override.Localize.Calendar.first_day_for_locale/1honours the-u-fw-extension per TR35:"en-u-fw-mon"returns 1 (Monday) instead of the territory default.v0.46.0Compare Source
Changed
Full compile time drops from ~66s to ~3.5s: the RBNF lexer grammar no longer explodes the generated DFA (its generated Erlang shrinks from 891KB to 41KB), and the language/subdivision validity checks compile to a single map lookup instead of thousands of guard clauses. Token streams and validation results are unchanged.
The collation tailoring table loads at runtime on first use instead of being embedded at compile time, shrinking the library's largest beam from 1.7MB to 44KB.
Every module that embeds ETF data at compile time now declares the file with
@external_resource, so a CLDR data regeneration correctly recompiles the timezone, time-preference, validity and script-mapping modules.v0.45.0Compare Source
A quality release: the codebase now passes
mix credo --strictwith zero findings (enforced in CI), the full MessageFormat 2 working-group conformance suite runs against the formatter, and the documentation gains four new guides. A test-coverage push from 70% toward 90% surfaced — and this release fixes — more than twenty conformance and correctness bugs across date/time, number, unit, collation and language-tag handling.Upgrading from 0.42 or earlier? Version 0.43.0 renamed the inventory functions from the
available_*/known_*names to the uniformknown_*/supported_*/*_forscheme and swapped two:format/:styleoption names. Every old name still works as a deprecated delegate until Localize 1.0 — see the 0.43.0 entry below for the full list.Added
The complete MessageFormat 2 working-group conformance suite (16 test files including all nine
functions/suites) now runs against the formatter: 250 cases asserting formatted output and expected errors, with unimplemented features excluded and documented per case.Four new guides: plural rules, list formatting, locale validation and matching, and display names. Every example is execution-verified.
Localize.Territory.territory_names_for/1andterritories_for/1return the localized territory-name inventory for a locale, completing the*_forfamily across languages, scripts, subdivisions and territories.CLDR metazone data joins the supplemental-data pipeline:
Localize.DateTime.Timezone.metazone_for/2returns the metazone for an IANA zone at a given instant, andzone_for_metazone/2returns a metazone's representative zone per territory. Thez/vformat symbols now draw on the full 191-metazone CLDR mapping instead of a 20-zone builtin table.A documentation depth pass adds around 150 execution-verified doctest examples and 40
### Optionssections across the number, unit, date/time, locale, territory, list, collation and message modules, and corrects stale claims (RBNF number-system conversion, locale defaults, thevalidate_locale/1matching warning).A test-coverage push adds around 1,700 tests across exceptions, collation, date/time formatting, MessageFormat 2 tooling, language-tag validity, number/unit internals and the runtime plumbing, raising runtime-library line coverage from 70% to 91%. The 90% threshold is now enforced in CI.
Changed
The MF2
:offsetfunction follows the specification: it takes exactly one of theaddorsubtractoptions (non-negative integer) and adjusts the operand for both formatting and selection. The previous ICU-styleoffset=option, which only subtracted during selection, is replaced.The MF2
numberingSystemfunction option accepts any valid CLDR numbering system, matching Intl/ICU:{$n :number numberingSystem=thai}renders Thai digits in any locale. Unknown system names still return an error; the:number_systemoption ofLocalize.Number.to_string/2is unchanged.Credo (strict) added to the CI lint stage with a committed
.credo.exs. Policy: theDesign.AliasUsagecheck is disabled (Localize module names such asList,DateandStringshadow the stdlib when aliased; aliasing is applied opportunistically where safe), nesting depth stays at the default maximum of 2, and complexity/apply/arity exceptions are documented inline at each site.Deeply nested functions across the library were refactored into multi-clause private helpers with pattern matching (~120 helpers). Behavior is unchanged; the full test suite, dialyzer and doc builds gate the refactor.
Logger configuration for this project's dev/test environments now renders the
domain: :localizemetadata attached to Localize log messages.Fixed
The
U(cyclic year) andr(related Gregorian year) format symbols work:Urenders the localized sexagesimal name (丙午, "bing-wu") from the calendar's cyclic name sets andrthe Gregorian year in which the calendar year begins, both probing the date's calendar module per the Calendrical protocol. Previously both rendered the raw year number.Date/time skeleton matching compared candidate format fields against the wrong skeleton fields, making format selection nondeterministic across VM runs (
format: :yMdHmcould render "Jul" or "July" run to run). Selection is now deterministic, prefers the exact skeleton, and matches text-field widths by class (E≡EEE) per TR35.The "alt" language code (Southern Altai) was unreachable: the data pipeline stored it as the atom
:alt(a collision with the alt-variant style key), soLocalize.Language.display_name("alt")returned an error. Fixed at data load; the pipeline fix lands with the next CLDR data regeneration.-t-extension dates encode with zero padding (ja-t-it-m0-ungegn-20070315round-trips exactly); previously single-digit months and days encoded with an embedded space, producing an invalid tag.Localize.validate_locale("en-u-vt-00a4")no longer raises during canonical-id formatting, andLocalize.LanguageTag.parse("zh-min-nan")no longer raisesArgumentError(the grammar dropped the extended-language tags for multi-subtag forms).CLDR
{1}…{0}…substitution patterns with two literals placed the substituted items in the wrong order.Collation
reorder: [..., :others]places unmentioned scripts at the:othersposition per TR35; previously they kept unremapped weights, producing orderings incomparable with the reordered scripts.The collation
max_variableoption takes effect in the Elixir backend::spacenarrows and:symbol/:currencywiden the shifted range per the UCA. Previously the setting was ignored and shifting was fixed at the punctuation boundary.Compound unit identifiers canonicalize in the TR35 unitQuantity order, so
"kilowatt-hour"keeps its CLDR pattern and renders "2 kilowatt-hours" instead of "2 hour-kilowatt".:currency_long_with_symbolrenders both the symbol-formatted number and the currency name ("$123.00 US dollars"); both long currency formats now derive the currency from the locale when no:currencyoption is given.Localize.Interval.to_string/3returns an error for mixed endpoint kinds (e.g. aDateand aTime) instead of rendering malformed output, and equalTimeendpoints honour the:time_formatoption in the single-time fallback.The
Qandqdate-format symbols were swapped:Qnow renders the format-context quarter andqthe stand-alone quarter per TR35. Visible only in locales whose quarter names differ by context.Doubled apostrophes inside quoted date-format text render a literal apostrophe (
'o''clock'→ "o'clock"); previously the escape was dropped.Zone format symbols (
Z,O,v,V,x,X) render empty for values without a time zone, matchingz; previously they fabricated a UTC zone.Localize.Duration.new/2acceptsNaiveDateTimepairs (previously raised) and rejects reversedTimepairs with an error instead of returning a negative duration.RBNF plural substitutions (
$(cardinal,…)$) select the plural category on the quotient of the rule divisor per ICU: Russian 2,000,000 spells out as "два миллиона", not "два миллионов".Requesting a spellout ruleset a locale does not have returns
Localize.UnknownRbnfRuleError— with itsavailable:field now populated — instead of silently formatting digits.The
:spelloutformat resolves tospellout-numbering(ICU's default ruleset) deterministically; previously the choice among a locale's gendered rulesets varied across OTP releases (German 1.5 could spell "eine", "ein" or "eins" depending on the VM).Numeric collation computes the value of non-ASCII digits from their Unicode block's zero digit: Arabic-Indic "٢" sorts before "١٠" and "٠" compares equal to "0"; previously digit values were computed modulo 10 of the codepoint, wrapping mid-block.
:fractional_digitsapplies to numbers with a zero integer part:Number.to_string(0.4, fractional_digits: 0)returns "0" (previously "0.4"), with float and Decimal inputs rounding identically across all rounding modes.Localize.Utils.Map.extract_strings/2no longer drops strings that precede a nested list or map, andunderscore_keys(nil)returns nil instead of raising.Number.to_range_string/3withapproximate: trueformats the full range ("~3–5"); previously the range end was silently dropped.Number.PluralRule.plural_type/2returns a bare category atom on both backends; the NIF backend previously returned an{:ok, atom}tuple.Converting a mixed unit to another mixed unit works (
foot-and-inch→yard-and-foot-and-inch); previously it always returned a:mixed_unitserror.Unit plural categories for
Decimalvalues honour the visible fraction (Decimal "1" renders "1 meter"), and denominator units keep their SI prefix and power ("per 100 kilometers", not "per 100 meter").Localize.Utils.Math.power/2with a fractional exponent between 0 and 1 returned the reciprocal (power(4, 0.5)gave 0.5); Decimal bases now always return Decimals.Locale downloads run in a dedicated
:localizehttpc profile with the proxy set per request, so a configured proxy no longer leaks into later requests or into the host application's default httpc profile.Display names of parsed (unvalidated) language tags with
-t-extensions or multi-part-u-values no longer crash or mangle subtags, and multiple-u-dx-scripts render translated and list-separated.Localize.Date.to_string/2no longer overwrites a user-supplied:number_system_overridesoption.Small fixes:
UnknownSubdivisionErrorcarries the original input instead of nil;UnknownCurrencyErrorfor a binary code always carries the binary (previously the atom when it happened to exist); MF2 match results deduplicate their bound-variable lists;Localize.Utils.Json.decode!/1raisesArgumentErroron trailing data instead of a bareMatchError.Language tags with duplicate singleton subtags (
en-u-ca-gregory-u-nu-thai) are rejected with a parse error per RFC 5646; previously the second occurrence was silently dropped.Grandfathered language tags resolve to their preferred values from the CLDR alias data:
i-klingoncanonicalizes totlh(displaying "Klingon"),zh-min-nantonan,en-GB-oedtoen-GB-oxendict, and the sign-language tags to their ISO codes. Previously irregular grandfathered tags parsed to an empty tag.The MF2 Elixir backend reports the same errors as the NIF backend: an unbound variable used as a function option value returns a bind error, and an invalid
:localeoption returns a locale error; both were previously silently ignored.Per-compound units without a direct CLDR pattern compose from the numerator and the per-pattern:
foot-per-secondrenders "2 feet per second" instead of the raw identifier.MF2
:percentselection multiplies the operand by 100 before plural-category selection, matching the formatted value per the specification.An MF2
.matchon an unbound variable returns an unresolved-variable error instead of silently selecting the fallback variant.A duplicated boolean assertion in the message custom-function tests masked half of an intended check; general mechanical cleanups (map_join, empty-enum checks, number underscores, redundant with clauses) applied throughout.
v0.44.0Compare Source
This release closes the TR35 conformance gaps in the date/time formatting layer and fixes locale-fidelity bugs surfaced by newly-wired conformance data.
Added
Flexible day periods: the
Bformat symbol selects CLDR day periods ("in the morning", "mittags", "noon", "midnight") from the day-period rules now in the supplemental data pipeline, andbrenders noon/midnight at the exact points. Languages without rules fall back to AM/PM.Localize.LanguageTag.U.parse/1andparse!/1parse a standalone BCP 47-u-extension string (with or without theu-singleton) into a validatedLocalize.LanguageTag.Ustruct, using the same canonicalization asLocalize.validate_locale/1.Localize.LanguageTag.U.encode/1is now a documented public function.Downloaded locale files are verified against a SHA-256 hash manifest bundled with the package (
priv/localize/locale_hashes.etf, generated bymix localize.generate_locale_hashesat release time), before any decode or cache write. A mismatch or missing entry fails the download withLocalize.LocaleIntegrityError; when no manifest is present a one-time warning is logged and verification is skipped.The rbnf conformance suite now runs the vendored ICU reference data for
de,esandfrin addition toen, and the likely-subtags suite asserts the CLDR FAIL rows.Fixed
Localize.Validity.U.encode/2emits the preferred BCP 47 spelling instead of a deprecated alias (:islamic_civilencodes as "islamic-civil", not "islamicc"), anddecode/2accepts struct-field atom keys and the canonical hyphenated calendar spellings ("islamic-civil").Week fields honour the locale's week configuration:
wandYcompute week-of-year from the locale's first day and minimum days (2023-01-01 is week 1 in en, week 52 of 2022 in de), andWcomputes week-of-month accordingly. Previously all three were ISO-only.Multi-replacement territory aliases follow TR35 Annex C:
hy-SUcanonicalizes tohy-AMvia the likely territory,uk-SUtouk-UA,sr-YUtosr-RS; previously the first entry (RU) was always taken.RBNF decimal-format substitutions (
=#,##​0=) format with the rule's locale instead of the process locale, so fr digits-ordinal renders "1 141e" and de "1.141." rather than the English "1,141".Likely-subtags no longer fabricates mappings for languages without one: private-use languages (
qaa) pass through unmaximized per the CLDR test data instead of inheriting theundmapping.Locales whose language has no CLDR match resolve to root (
und) data instead of the alphabetically nearest wrong language —tlhno longer formats with Afar data.Compact formats no longer raise on a negative
:fractional_digitsoption.Changed
mix test --covernow excludes build-time tooling (the data-generation pipeline, mix tasks, compile-time parser generators) from coverage measurement so the number reflects the runtime library.v0.41.3Compare Source
Changed
Localize.LanguageTag'scanonical_locale_idfield — and thereforeLocalize.LanguageTag.to_string/1— now holds the canonical-syntax form of the requested locale (aliases resolved and subtags ordered, but neither maximized nor minimized), so"en"stays"en"and"en-US"stays"en-US"instead of collapsing to the minimal identity form. Locale identity and data lookup are unaffected; they key offcldr_locale_id.Localize.Language.display_name/2now follows the TR35 display-name algorithm, which canonicalizes rather than adds likely subtags: a bare language keeps its own name ("en"→"English","es"→"Spanish"), while an explicitly supplied region or script still resolves to the region-specific CLDR name ("en-GB"→"British English","pt-BR"→"Brazilian Portuguese"). A string and an equivalentLocalize.LanguageTagreturn the same result.Localize.Language.display_name/2andLocalize.Locale.LocaleDisplay.display_name/2both now validate their input throughLocalize.validate_locale/1and drive the display offcanonical_locale_id, so a string and an equivalent (maximized)Localize.LanguageTagrender identically and no likely subtags leak into the output (a validated"en"tag is"English", not"English (United States)").Fixed
Localize.Language.display_name/2andLocalize.Locale.LocaleDisplay.display_name/2now break the two-subtag candidate tie toward the earlier subtag per TR35, tryinglang-scriptbeforelang-region.Localize.validate_locale/1now accepts a-u-rg-region override that uses a subdivision id (e.g."en-u-rg-gbeng"). The subdivision value was being suffixed with the"zzzz"region filler and rejected as an unknown locale.v0.41.2Compare Source
Fixed
Plural rules now resolve likely subtags before selecting a rule set.
Localize.Number.PluralRule.CardinalandOrdinal(pluralize/3,plural_rules_for/1,plural_rule/3) andLocalize.Number.PluralRule.plural_type/2validate the locale throughLocalize.validate_locale/1instead of a bare parse, so a string and an equivalentLocalize.LanguageTagselect the same category and region-specific rules apply — e.g.plural_rule(0, "pt-PT")is now:other(Portugal) rather than:one(the basept/Brazil rule). This also corrects plural selection in message formatting.Localize.Territory.contains?/2now validates both arguments, so string and atom territories are equivalent (contains?("EU", "DK")matchescontains?(:EU, :DK)); it returnsfalsefor an invalid territory instead of silently treating a string as unmatched. It now also accepts territory strings, matching its sibling relationship functions.Localize.Collation.Options.from_locale/1now canonicalises its input throughLocalize.validate_locale/1, so a string, atom, and equivalentLocalize.LanguageTagyield identical collation options and legacy aliases resolve correctly ("iw"behaves as"he","pt_BR"as"pt-BR").v0.41.1Compare Source
Fixed
Localize.Language.display_name/2now normalises its input throughLocalize.validate_locale/1, so a string and an equivalentLocalize.LanguageTagreturn the same name ("en-GB"andLanguageTag.new!("en-GB")both give"British English") and region-specific codes resolve with fallback ("pt-BR"→"Brazilian Portuguese","ar-SA"→"Arabic"instead of raising). A malformed locale now returnsLocalize.InvalidLocaleErrorrather thanLocalize.UnknownLanguageError. Thanks to @DVSLabs for the report. Closes #36.Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR has been generated by Mend Renovate.
⚠️ Artifact update problem
Renovate failed to update an artifact related to this branch. You probably do not want to merge this PR as-is.
♻ Renovate will retry this branch, including artifacts, only when one of the following happens:
The artifact failure details are included below:
File name: mix.lock
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.