chemist
How to coerce currency symbols with a character prefix? E.g. US$ not $ (even if "en" is the locale)
ex_cldr and ex_money are magnificent libraries and it is probable I am doing something very wrong.
Basically I cannot display the currency dollar symbols with a character prefix when used in the same locale as the currency. This is important as I will need to disambiguate the feedback to the user.
My Cldr config is as follows:
defmodule MyApp.Cldr do
use Cldr,
locales: ["en", "th", "fr", "de"],
default_locale: "en",
add_fallback_locales: false,
data_dir: "./priv/cldr",
otp_app: :my_app,
precompile_number_formats: ["¤¤#,##0.##"],
precompile_transliterations: [{:latn, :arab}, {:thai, :latn}],
providers: [Cldr.Number, Money],
generate_docs: true,
force_locale_download: false
end
libraries:
{:ex_cldr, "~> 2.25.0"},
{:ex_cldr_numbers, "~> 2.24.0"},
{:ex_cldr_dates_times, "~> 2.10.2"},
{:ex_money, "~> 5.7.4"},
I am building my knowledge off this example:
Where i realize there are many possible ways to display currencies but I am not sure how do I coerce the display of a specific format.
Commands run:
string = Money.to_string!(Money.new(currency, amount), locale: "en")
It will result in displaying plain “$” with the value for locale: “en” and yet display US$ for locale: “de” for example.
-
How do I coerce the display of the prefix: “US$” regardless of locale (which may vary) and likewise for the £/€/A$ symbols? This is to prevent any ambiguity or confusion in my system where currency symbols must be clearly displayed at all times especially for the dollar symbol which is used by countless currencies.
-
Some symbols are missing for certain locales for example… in Singapore and S$. How do I customize the library to add in the symbol manually?
Many thanks.
Marked As Solved
kip
Thanks for the kind words, and thanks for the very complete understanding of what you are trying to do.
As you know, each locale specifies currency formatting meant to be appropriate to that language and culture. The underlying data doesn’t attempt to provide a global canonical format for anything. So while CLDR data is an amazing resource, it doesn’t satisfy all requirements.
TLDR;
The nearest approximation I think is possible directly is to use the :currency_sumbol option to Money.to_string/2. This option is actually passed to Cldr.Number.to_string/3 and is documented there. I will make sure it is also documented in ex_money. For example:
iex> Money.to_string Money.new(:USD, 100), currency_symbol: :iso, locale: "en"
{:ok, "USD 100.00"}
iex> Money.to_string Money.new(:USD, 100), currency_symbol: :iso, locale: "de"
{:ok, "100,00 USD"}
iex> Money.to_string Money.new(:USD, 100), currency_symbol: :iso, locale: "fr"
{:ok, "100,00 USD"}
This outputs a consistent currency indicator using the ISO 4217 code.
Longer version
In fact you can pass any string you like to the :currency_symbol option but this definitely requires care. Here’s an example using galleons:
iex> Money.to_string Money.new(:USD, 100), currency_symbol: "Galleons", locale: "it"
{:ok, "100,00 Galleons"}
You can identify what format data drives the money - and any other CDLR number - formatting by introspection:
iex> MyApp.Cldr.Number.Format.formats_for "en", :native
{:ok,
%Cldr.Number.Format{
accounting: "¤#,##0.00;(¤#,##0.00)",
currency: "¤#,##0.00",
currency_long: %{one: [0, " ", 1], other: [0, " ", 1]},
currency_short: [
[1000, %{one: ["¤0K", 1], other: ["¤0K", 1]}],
[10000, %{one: ["¤00K", 2], other: ["¤00K", 2]}],
[100000, %{one: ["¤000K", 3], other: ["¤000K", 3]}],
[1000000, %{one: ["¤0M", 1], other: ["¤0M", 1]}],
[10000000, %{one: ["¤00M", 2], other: ["¤00M", 2]}],
[100000000, %{one: ["¤000M", 3], other: ["¤000M", 3]}],
[1000000000, %{one: ["¤0B", 1], other: ["¤0B", 1]}],
[10000000000, %{one: ["¤00B", 2], other: ["¤00B", 2]}],
[100000000000, %{one: ["¤000B", 3], other: ["¤000B", 3]}],
[1000000000000, %{one: ["¤0T", 1], other: ["¤0T", 1]}],
[10000000000000, %{one: ["¤00T", 2], other: ["¤00T", 2]}],
[100000000000000, %{one: ["¤000T", 3], other: ["¤000T", 3]}]
],
currency_spacing: %{
after_currency: %{
currency_match: "[[:^S:]&[:^Z:]]",
insert_between: " ",
surrounding_match: "[[:digit:]]"
},
before_currency: %{
currency_match: "[[:^S:]&[:^Z:]]",
insert_between: " ",
surrounding_match: "[[:digit:]]"
}
},
decimal_long: [
[1000, %{one: ["0 thousand", 1], other: ["0 thousand", 1]}],
[10000, %{one: ["00 thousand", 2], other: ["00 thousand", 2]}],
[100000, %{one: ["000 thousand", 3], other: ["000 thousand", 3]}],
[1000000, %{one: ["0 million", 1], other: ["0 million", 1]}],
[10000000, %{one: ["00 million", 2], other: ["00 million", 2]}],
[100000000, %{one: ["000 million", 3], other: ["000 million", 3]}],
[1000000000, %{one: ["0 billion", 1], other: ["0 billion", 1]}],
[10000000000, %{one: ["00 billion", 2], other: ["00 billion", 2]}],
[100000000000, %{one: ["000 billion", 3], other: ["000 billion", 3]}],
[1000000000000, %{one: ["0 trillion", 1], other: ["0 trillion", 1]}],
[10000000000000, %{one: ["00 trillion", 2], other: ["00 trillion", 2]}],
[100000000000000, %{one: ["000 trillion", 3], other: ["000 trillion", 3]}]
],
decimal_short: [
[1000, %{one: ["0K", 1], other: ["0K", 1]}],
[10000, %{one: ["00K", 2], other: ["00K", 2]}],
[100000, %{one: ["000K", 3], other: ["000K", 3]}],
[1000000, %{one: ["0M", 1], other: ["0M", 1]}],
[10000000, %{one: ["00M", 2], other: ["00M", 2]}],
[100000000, %{one: ["000M", 3], other: ["000M", 3]}],
[1000000000, %{one: ["0B", 1], other: ["0B", 1]}],
[10000000000, %{one: ["00B", 2], other: ["00B", 2]}],
[100000000000, %{one: ["000B", 3], other: ["000B", 3]}],
[1000000000000, %{one: ["0T", 1], other: ["0T", 1]}],
[10000000000000, %{one: ["00T", 2], other: ["00T", 2]}],
[100000000000000, %{one: ["000T", 3], other: ["000T", 3]}]
],
other: %{
approximately: ["~", 0],
at_least: [0, "+"],
at_most: ["≤", 0],
range: [0, "–", 1]
},
percent: "#,##0%",
scientific: "#E0",
standard: "#,##0.###"
}}
And use the returned data to make a decision about what format you might want use by introspecting the currency data:
iex> {:ok, currency} = MyApp.Cldr.Currency.currency_for_code :USD, locale: "fr"
{:ok, #Cldr.Currency<"USD">}
iex> currency.
__struct__ alt_code cash_digits cash_rounding
code count digits from
iso_digits name narrow_symbol rounding
symbol tender to
iex> currency.symbol
"$US"
iex> currency.narrow_symbol
"$"
Singapore dollar symbols
There is a bug in the CLDR data for the Singapore dollar that should be fixed for CLDR 41 that is currently in “pre alpha” which will probably be release in April. ex_cldr will follow suit on the same day of CLDR release.
Hope this helps, happy to hear any other thoughts, ideas or suggestions!
Also Liked
chemist
This is a great reply. I now understand Cldr better and i really appreciate that.
To check my understanding, you are saying that Cldr is unable to be authoritative - and at best can only display currencies (and other data) as they are displayed in the specified locale passed to the function.
So to disambiguate, it is recommended to use the ISO names instead.
Alternatively, I have decided on a ‘band-aid solution’ by displaying the currencies in the locale: ‘th’ as I understand in Asia, they do display the country prefix before the dollar symbol. And to add on to this understanding, this may need to be refactored if my users are European and may prefer to see currencies displayed in their preferred format (i.e. as a suffix)
The other alternative of currency introspection will be less desirable than ISO formats as it would incur some performance penalty on an unnecessary subroutine.
Regarding the SGD data, I look forward to your update and thank you for all the work you have done and generously shared with the Elixir community as well as your hospitality in this forum. It is much appreciated.
kip
Nothing intrinsically incorrect about using the th locale since it does appear to use currency symbols for quite a lot of currencies. I tried KRW, INR, USD, CAD, AUD, JPY and CNY and all seem to deliver what you are after.
The difference will be that a user requesting a different locale will expect numbers and monies to be formatted according to that locale and may be surprised that the developers choice is overriding their own. If they don’t have an ability to select their own locale then it is what it is.
Unless you are writing a forex or other trading app, I’m not sure a wide audience would recognise the Indian Rupee symbol ₹ or the Korean Won symbol ₩ but perhaps I’m being patronising. Many (most?) of the less traded currencies (Kyrgyzstan Som, Indonesian Rupiah, etc etc) are rendered with their ISO 4217 symbol in the th locale as it is.
Anyway, I think at this point you have a good grasp of how CLDR data is used and how you can apply it. More than happy to answer any other questions and feel free to open an issue if you see any problems.
kip
You might find the locale en-001 is closer to your needs. 001 is the UN M.49 code for “the world” so en-001 means “world English”. its not a great locale for typical use but it might suit in this case. Here are some examples:
iex> Money.to_string Money.new(:USD, 100), locale: "en-001"
{:ok, "US$100.00"}
iex> Money.to_string Money.new(:AUD, 100), locale: "en-001"
{:ok, "A$100.00"}
iex> Money.to_string Money.new(:THB, 100), locale: "en-001"
{:ok, "THB 100.00"}
iex> Money.to_string Money.new(:SGD, 100), locale: "en-001"
{:ok, "SGD 100.00"}
However if you are intending to localise your user experience then I would propose simply using Money.to_string money, currency_symbols: :iso since at least then the number formatting, symbol placement, digit grouping and so on will be appropriate for the users expectations.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









