chemist

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.

  1. 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.

  2. 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

kip

ex_cldr Core Team

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

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

kip

ex_cldr Core Team

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

kip

ex_cldr Core Team

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.

Where Next?

Popular in Questions Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New

Other popular topics Top

siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement