Skip to content

Raise on a CLDR number symbol that Cldr.Number.Symbol does not know - #274

Open
rubas wants to merge 1 commit into
elixir-cldr:mainfrom
rubas:fix/strict-symbol-struct
Open

rubas wants to merge 1 commit into
elixir-cldr:mainfrom
rubas:fix/strict-symbol-struct

Conversation

@rubas

@rubas rubas commented Sep 26, 2026

Copy link
Copy Markdown

Before

Cldr.Config.number_symbols_for/2 builds Cldr.Number.Symbol with struct/2, which drops every key the struct does not define. With ex_cldr_numbers 2.38.3 it drops these CLDR symbols:

# nb, nn, no, sv and their regional locales: 7 records
time_separator_alt_variant: "."
# every locale: 840 records (fr-BE "≃", nb "ca.")
approximately_sign: "~"
# fr-CH and de-AT: 1 record each
currency_decimal: ".", currency_group: "."

Problem

  • Nobody decided to drop these symbols. They disappear because the struct has no field for them.
  • When CLDR adds a symbol, ex_cldr drops it and nobody sees it.

After

@excluded_number_symbols [:time_separator_alt_variant]
@optional_number_symbols [:approximately_sign, :currency_decimal, :currency_group]

def number_symbol!(symbols) do
  unsupported = @optional_number_symbols -- Map.keys(struct(Cldr.Number.Symbol))
  struct!(Cldr.Number.Symbol, Map.drop(symbols, @excluded_number_symbols ++ unsupported))
end

Any other symbol that the struct does not define raises a KeyError.

Details
Symbol Records Decision Why
time_separator_alt_variant 7 Left out by name The "." that nb, nn, no, and sv use as an alternative time separator. No formatter reads it.
approximately_sign 840 Kept when Cldr.Number.Symbol has the field Real data: in 60 records it differs from the "approximately" pattern (fr-BE sign "≃", pattern "≈{0}"). No ex_cldr_numbers version has the field yet.
currency_decimal, currency_group 1 each Kept when Cldr.Number.Symbol has the field fr-CH "." and de-AT ".". ex_cldr_numbers adds the fields in elixir-cldr/cldr_numbers#66.
  • Compatibility: Cldr.Number.Symbol lives in ex_cldr_numbers, and ex_cldr does not depend on it. The optional symbols follow the struct, so this works with ex_cldr_numbers 2.38.3 and with the fields from Use CLDR currency separators when formatting a currency cldr_numbers#66, and the two can be released in any order.
  • Check: test/number_symbol_test.exs builds the symbols of every locale in priv/cldr/locales (about 5 s), so a CLDR update with a new symbol fails the test suite. Other tests: sv leaves out its "." time separator, and an unknown symbol (digit_marker) raises.
  • test/support/number_symbol.ex stands in for the ex_cldr_numbers 2.38.3 struct in the test env.
  • ex_cldr_numbers mix test against this branch (path dependency): upstream main 5232 passed, Use CLDR currency separators when formatting a currency cldr_numbers#66 5234 passed. Its test backend compiles all 766 locales, which runs number_symbols_for!/2 for each.
  • mix test passes (13359). mix format --check-formatted fails on main too (Elixir 1.20.4), in 21 files, on lines this change does not touch. The changed code is formatted.
  • The ex_cldr_numbers backend (lib/cldr/number/backend/symbol.ex) builds the same struct with its own struct/2, so it still drops these symbols. It could call Cldr.Config.number_symbol!/1.

AI disclosure: AI models helped us find this issue, write the change, and review it.

number_symbols_for/2 built Cldr.Number.Symbol with struct/2, which
drops every key the struct does not define. time_separator_alt_variant
("." in nb, nn, no and sv) is now left out by name. approximately_sign,
currency_decimal and currency_group are kept when the struct defines
them, so this works with every ex_cldr_numbers version. Any other
symbol raises a KeyError.

This branch has not been deployed

No deployments
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