defmodule Cldr.Number.Formatter.Currency do @moduledoc """ Number formatter for the `:currency` `:long` format. This formatter implements formatting a currency in a long form. This is not the same as decimal formatting with a currency placeholder. To explain the difference, look at the following examples: iex> Cldr.Number.to_string 123, TestBackend.Cldr, format: :currency, currency: "USD" {:ok, "$123.00"} iex> Cldr.Number.to_string 123, TestBackend.Cldr, format: :long, currency: "USD" {:ok, "123 US dollars"} In the first example the format is defined by a decimal mask. In this example the format mask comes from: iex> {:ok, formats} = Cldr.Number.Format.all_formats_for("en", TestBackend.Cldr) ...> formats.latn.currency "ยค#,##0.00" In the second example we are using a format that combines the number with a language translation of the currency name. In this example the format comes from: iex> {:ok, formats} = Cldr.Number.Format.all_formats_for("en", TestBackend.Cldr) ...> formats.latn.currency_long %{one: [0, " ", 1], other: [0, " ", 1]} Where "{0}" is replaced with the number formatted using the `:standard` decimal format and "{1} is replaced with locale-specific name of the currency adjusted for the locales plural rules." **This module is not part of the public API and is subject to change at any time.** """ alias Cldr.Number.{Format, System} alias Cldr.{Substitution, Currency} alias Cldr.Number.Format.Options alias Cldr.Number.Formatter import DigitalToken, only: [is_digital_token: 1] @doc false def to_string(number, _format, _backend, _options) when is_binary(number) do {:error, { ArgumentError, "Not a number: #{inspect(number)}. Currency long formats only support number or Decimal arguments" }} end # The format :currency_medium is a composition of :currency_long # and the default :currency format. def to_string(number, :currency_long_with_symbol, backend, options) do decimal_options = decimal_options(options, backend, number) decimal_format = decimal_options.format number |> Cldr.Number.to_string!(backend, long_options(options)) |> Formatter.Decimal.to_string(decimal_format, backend, decimal_options) end def to_string(number, :currency_long, backend, options) do locale = options.locale number_system = System.system_name_from!(options.number_system, locale, backend) cardinal = Module.concat(backend, Number.Cardinal) if !(formats = Format.formats_for!(locale, number_system, backend).currency_long) do raise ArgumentError, message: "No :currency_long format known for " <> "locale #{inspect(locale)} and number system #{inspect(number_system)}." end options = options |> Map.put(:format, :standard) |> set_fractional_digits(options.currency, options.fractional_digits) |> Options.resolve_standard_format(backend) currency_string = currency_string(number, options.currency, cardinal, locale, backend) number_string = Cldr.Number.to_string!(number, backend, options) format = cardinal.pluralize(number, locale, formats) Substitution.substitute([number_string, currency_string], format) |> :erlang.iolist_to_binary() end defp currency_string(number, %Cldr.Currency{} = currency, cardinal, locale, _backend) do cardinal.pluralize(number, locale, currency.count) end defp currency_string(_number, currency, _cardinal, _locale, _backend) when is_digital_token(currency) do {:ok, currency_string} = DigitalToken.long_name(currency) currency_string end defp set_fractional_digits(options, %Cldr.Currency{}, nil) do Map.put(options, :fractional_digits, 0) end defp set_fractional_digits(options, _currency, _digits) do options end defp long_options(options) do options |> Map.put(:format, :decimal_long) |> Map.put(:currency, nil) end defp decimal_options(options, backend, number) do currency_format = Currency.currency_format_from_locale(options.locale) options = Map.put(options, :format, currency_format) options |> Options.resolve_standard_format(backend) |> Options.maybe_expand_currency_symbol(number) end end