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:
https://hexdocs.pm/ex_cldr_currencies/Cldr.Currency.html#strings_for_currency/3

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.

kip

kip

ex_cldr Core Team

Agreed, very reasonable to assume that a user in a given locale would recognise their local currency symbol.

I think the OPs post is focused on presenting multiple currencies to users and I’m not sure how familiar anyone is with currency symbols of currencies outside their locale.

You also mentioned the use of IP address to detect locale, and I recognise that’s a common “feature”. I really wish web sites would stop making assumptions about me based upon my location (derived from IP address). This is not only an unreliable way to derive location, it ignores my express preference delivered in the Accept-Language header by browsers.

<rant>
Not the central theme of your post I know, just a pet peeve of mine. I’m travelling in the US right now. My home is in Singapore. But I have my browser set to Australian English (en-AU). I don’t know why sites think my IP address is a better determinate of my preferences than the setting I expressly made to communicate that preference.
</rant>

Where Next?

Popular in Questions Top

vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
Werner
Hi, I’m using Ubuntu 18.04 and after updating to OTP-24.0 yesterday i have this warning when I run “mix local.hex”: 14:57:30.512 [warn] ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement