defmodule Cldr.Unit.Backend do
def define_unit_module(config) do
module = inspect(__MODULE__)
backend = config.backend
additional_units = Module.concat(backend, Unit.Additional)
config = Macro.escape(config)
quote location: :keep,
bind_quoted: [
module: module,
backend: backend,
config: config,
additional_units: additional_units
] do
# Create an empty additional units module if it wasn't previously
# defined
unless Code.ensure_loaded?(additional_units) do
defmodule additional_units do
@moduledoc false
def known_locales do
[]
end
def units_for(_locale, _style) do
%{}
end
def additional_units do
[]
end
end
end
defmodule Unit do
@moduledoc false
if Cldr.Config.include_module_docs?(config.generate_docs) do
@moduledoc """
Supports the CLDR Units definitions which provide for the localization of many
unit types.
"""
end
@styles [:long, :short, :narrow]
alias Cldr.Math
defdelegate new(unit, value), to: Cldr.Unit
defdelegate new!(unit, value), to: Cldr.Unit
defdelegate compatible?(unit_1, unit_2), to: Cldr.Unit
defdelegate value(unit), to: Cldr.Unit
defdelegate zero(unit), to: Cldr.Unit
defdelegate zero?(unit), to: Cldr.Unit
defdelegate decompose(unit, list), to: Cldr.Unit
defdelegate localize(unit, usage, options), to: Cldr.Unit
defdelegate measurement_system_from_locale(locale), to: Cldr.Unit
defdelegate measurement_system_from_locale(locale, category), to: Cldr.Unit
defdelegate measurement_system_from_locale(locale, backend, category), to: Cldr.Unit
defdelegate measurement_systems_for_unit(unit), to: Cldr.Unit
defdelegate measurement_system_for_territory(territory), to: Cldr.Unit
defdelegate measurement_system_for_territory(territory, key), to: Cldr.Unit
defdelegate measurement_system?(unit, systems), to: Cldr.Unit
@deprecated "Use #{inspect(__MODULE__)}.measurement_system_for_territory/1"
defdelegate measurement_system_for(territory),
to: Cldr.Unit,
as: :measurement_system_for_territory
@deprecated "Use #{inspect(__MODULE__)}.measurement_system_for_territory/2"
defdelegate measurement_system_for(territory, key),
to: Cldr.Unit,
as: :measurement_system_for_territory
defdelegate known_units, to: Cldr.Unit
defdelegate known_unit_categories, to: Cldr.Unit
defdelegate known_styles, to: Cldr.Unit
defdelegate styles, to: Cldr.Unit, as: :known_styles
defdelegate default_style, to: Cldr.Unit
defdelegate validate_unit(unit), to: Cldr.Unit
defdelegate validate_style(unit), to: Cldr.Unit
defdelegate unit_category(unit), to: Cldr.Unit
defdelegate add(unit_1, unit_2), to: Cldr.Unit.Math
defdelegate sub(unit_1, unit_2), to: Cldr.Unit.Math
defdelegate mult(unit_1, unit_2), to: Cldr.Unit.Math
defdelegate div(unit_1, unit_2), to: Cldr.Unit.Math
defdelegate add!(unit_1, unit_2), to: Cldr.Unit.Math
defdelegate sub!(unit_1, unit_2), to: Cldr.Unit.Math
defdelegate mult!(unit_1, unit_2), to: Cldr.Unit.Math
defdelegate div!(unit_1, unit_2), to: Cldr.Unit.Math
defdelegate round(unit, places, mode), to: Cldr.Unit.Math
defdelegate round(unit, places), to: Cldr.Unit.Math
defdelegate round(unit), to: Cldr.Unit.Math
defdelegate convert(unit_1, to_unit), to: Cldr.Unit.Conversion
defdelegate convert!(unit_1, to_unit), to: Cldr.Unit.Conversion
@doc """
Formats a number into a string according to a unit definition for a locale.
## Arguments
* `list_or_number` is any number (integer, float or Decimal) or a
`t:Cldr.Unit` struct or a list of `t:Cldr.Unit` structs
* `options` is a keyword list
## Options
* `:unit` is any unit returned by `Cldr.Unit.known_units/0`. Ignored if
the number to be formatted is a `t:Cldr.Unit` struct
* `:locale` is any valid locale name returned by `Cldr.known_locale_names/0`
or a `Cldr.LanguageTag` struct. The default is `Cldr.get_locale/0`
* `:style` is one of those returned by `Cldr.Unit.known_styles`.
The current styles are `:long`, `:short` and `:narrow`.
The default is `style: :long`
* `:grammatical_case` indicates that a localisation for the given
locale and given grammatical case should be used. See `Cldr.Unit.known_grammatical_cases/0`
for the list of known grammatical cases. Note that not all locales
define all cases. However all locales do define the `:nominative`
case, which is also the default.
* `:gender` indicates that a localisation for the given
locale and given grammatical gender should be used. See `Cldr.Unit.known_grammatical_genders/0`
for the list of known grammatical genders. Note that not all locales
define all genders. The default gender is `#{inspect __MODULE__}.default_gender/1`
for the given locale.
* `:list_options` is a keyword list of options for formatting a list
which is passed through to `Cldr.List.to_string/3`. This is only
applicable when formatting a list of units.
* Any other options are passed to `Cldr.Number.to_string/2`
which is used to format the `number`
## Returns
* `{:ok, formatted_string}` or
* `{:error, {exception, message}}`
## Examples
iex> #{inspect(__MODULE__)}.to_string Cldr.Unit.new!(:gallon, 123)
{:ok, "123 gallons"}
iex> #{inspect(__MODULE__)}.to_string Cldr.Unit.new!(:gallon, 1)
{:ok, "1 gallon"}
iex> #{inspect(__MODULE__)}.to_string Cldr.Unit.new!(:gallon, 1), locale: "af"
{:ok, "1 gelling"}
iex> #{inspect(__MODULE__)}.to_string Cldr.Unit.new!(:gallon, 1), locale: "af-NA"
{:ok, "1 gelling"}
iex> #{inspect(__MODULE__)}.to_string Cldr.Unit.new!(:gallon, 1), locale: "bs"
{:ok, "1 galon"}
iex> #{inspect(__MODULE__)}.to_string Cldr.Unit.new!(:gallon, 1234), format: :long
{:ok, "1 thousand gallons"}
iex> #{inspect(__MODULE__)}.to_string Cldr.Unit.new!(:gallon, 1234), format: :short
{:ok, "1K gallons"}
iex> #{inspect(__MODULE__)}.to_string Cldr.Unit.new!(:megahertz, 1234)
{:ok, "1,234 megahertz"}
iex> #{inspect(__MODULE__)}.to_string Cldr.Unit.new!(:megahertz, 1234), style: :narrow
{:ok, "1,234MHz"}
iex> #{inspect(__MODULE__)}.to_string Cldr.Unit.new!(:megabyte, 1234), locale: "en", style: :unknown
{:error, {Cldr.UnknownFormatError, "The unit style :unknown is not known."}}
"""
@spec to_string(Cldr.Unit.value() | Cldr.Unit.t() | [Cldr.Unit.t(), ...], Keyword.t()) ::
{:ok, String.t()} | {:error, {atom, binary}}
def to_string(number, options \\ []) do
Cldr.Unit.Format.to_string(number, unquote(backend), options)
end
@doc """
Formats a list using `to_string/3` but raises if there is
an error.
## Arguments
* `list_or_number` is any number (integer, float or Decimal) or a
`t:Cldr.Unit` struct or a list of `t:Cldr.Unit` structs
* `options` is a keyword list
## Options
* `:unit` is any unit returned by `Cldr.Unit.known_units/0`. Ignored if
the number to be formatted is a `t:Cldr.Unit` struct
* `:locale` is any valid locale name returned by `Cldr.known_locale_names/0`
or a `Cldr.LanguageTag` struct. The default is `Cldr.get_locale/0`
* `:style` is one of those returned by `Cldr.Unit.known_styles`.
The current styles are `:long`, `:short` and `:narrow`.
The default is `style: :long`
* `:grammatical_case` indicates that a localisation for the given
locale and given grammatical case should be used. See `Cldr.Unit.known_grammatical_cases/0`
for the list of known grammatical cases. Note that not all locales
define all cases. However all locales do define the `:nominative`
case, which is also the default.
* `:gender` indicates that a localisation for the given
locale and given grammatical gender should be used. See `Cldr.Unit.known_grammatical_genders/0`
for the list of known grammatical genders. Note that not all locales
define all genders. The default gender is `#{inspect __MODULE__}.default_gender/1`
for the given locale.
* `:list_options` is a keyword list of options for formatting a list
which is passed through to `Cldr.List.to_string/3`. This is only
applicable when formatting a list of units.
* Any other options are passed to `Cldr.Number.to_string/2`
which is used to format the `number`
## Returns
* `formatted_string` or
* raises an exception
## Examples
iex> #{inspect(__MODULE__)}.to_string! 123, unit: :gallon
"123 gallons"
iex> #{inspect(__MODULE__)}.to_string! 1, unit: :gallon
"1 gallon"
iex> #{inspect(__MODULE__)}.to_string! 1, unit: :gallon, locale: "af"
"1 gelling"
"""
@spec to_string!(Cldr.Unit.value() | Cldr.Unit.t() | [Cldr.Unit.t(), ...], Keyword.t()) ::
String.t() | no_return()
def to_string!(number, options \\ []) do
Cldr.Unit.Format.to_string!(number, unquote(backend), options)
end
@doc """
Formats a number into an iolist according to a unit definition
for a locale.
## Arguments
* `list_or_number` is any number (integer, float or Decimal) or a
`t:Cldr.Unit` struct or a list of `t:Cldr.Unit` structs
* `options` is a keyword list
## Options
* `:unit` is any unit returned by `Cldr.Unit.known_units/0`. Ignored if
the number to be formatted is a `t:Cldr.Unit` struct
* `:locale` is any valid locale name returned by `Cldr.known_locale_names/0`
or a `Cldr.LanguageTag` struct. The default is `Cldr.get_locale/0`
* `:style` is one of those returned by `Cldr.Unit.known_styles`.
The current styles are `:long`, `:short` and `:narrow`.
The default is `style: :long`
* `:grammatical_case` indicates that a localisation for the given
locale and given grammatical case should be used. See `Cldr.Unit.known_grammatical_cases/0`
for the list of known grammatical cases. Note that not all locales
define all cases. However all locales do define the `:nominative`
case, which is also the default.
* `:gender` indicates that a localisation for the given
locale and given grammatical gender should be used. See `Cldr.Unit.known_grammatical_genders/0`
for the list of known grammatical genders. Note that not all locales
define all genders. The default gender is `#{inspect __MODULE__}.default_gender/1`
for the given locale.
* `:list_options` is a keyword list of options for formatting a list
which is passed through to `Cldr.List.to_string/3`. This is only
applicable when formatting a list of units.
* Any other options are passed to `Cldr.Number.to_string/2`
which is used to format the `number`
## Returns
* `{:ok, io_list}` or
* `{:error, {exception, message}}`
## Examples
iex> #{inspect(__MODULE__)}.to_iolist Cldr.Unit.new!(:gallon, 123)
{:ok, ["123", " gallons"]}
"""
@spec to_iolist(Cldr.Unit.value() | Cldr.Unit.t() | [Cldr.Unit.t(), ...], Keyword.t()) ::
{:ok, list()} | {:error, {atom, binary}}
def to_iolist(number, options \\ []) do
Cldr.Unit.Format.to_iolist(number, unquote(backend), options)
end
@doc """
Formats a unit using `to_iolist/3` but raises if there is
an error.
## Arguments
* `list_or_number` is any number (integer, float or Decimal) or a
`t:Cldr.Unit` struct or a list of `t:Cldr.Unit` structs
* `options` is a keyword list
## Options
* `:unit` is any unit returned by `Cldr.Unit.known_units/0`. Ignored if
the number to be formatted is a `t:Cldr.Unit` struct
* `:locale` is any valid locale name returned by `Cldr.known_locale_names/0`
or a `Cldr.LanguageTag` struct. The default is `Cldr.get_locale/0`
* `:style` is one of those returned by `Cldr.Unit.known_styles/0`.
The current styles are `:long`, `:short` and `:narrow`.
The default is `style: :long`.
* `:grammatical_case` indicates that a localisation for the given
locale and given grammatical case should be used. See `Cldr.Unit.known_grammatical_cases/0`
for the list of known grammatical cases. Note that not all locales
define all cases. However all locales do define the `:nominative`
case, which is also the default.
* `:gender` indicates that a localisation for the given
locale and given grammatical gender should be used. See `Cldr.Unit.known_grammatical_genders/0`
for the list of known grammatical genders. Note that not all locales
define all genders. The default gender is `#{inspect __MODULE__}.default_gender/1`
for the given locale.
* `:list_options` is a keyword list of options for formatting a list
which is passed through to `Cldr.List.to_string/3`. This is only
applicable when formatting a list of units.
* Any other options are passed to `Cldr.Number.to_string/2`
which is used to format the `number`
## Returns
* `io_list` or
* raises an exception
## Examples
iex> #{inspect(__MODULE__)}.to_iolist! 123, unit: :gallon
["123", " gallons"]
"""
@spec to_iolist!(Cldr.Unit.value() | Cldr.Unit.t() | [Cldr.Unit.t(), ...], Keyword.t()) ::
list() | no_return()
def to_iolist!(number, options \\ []) do
Cldr.Unit.Format.to_iolist!(number, unquote(backend), options)
end
@doc """
Returns a list of the preferred units for a given
unit, locale, use case and scope.
The units used to represent length, volume and so on
depend on a given territory, measurement system and usage.
For example, in the US, people height is most commonly
referred to in `inches`, or informally as `feet and inches`.
In most of the rest of the world it is `centimeters`.
## Arguments
* `unit` is any unit returned by `Cldr.Unit.new/2`.
* `backend` is any Cldr backend module. That is, any module
that includes `use Cldr`. The default is `Cldr.default_backend/0`
* `options` is a keyword list of options or a
`Cldr.Unit.Conversion.Options` struct. The default
is `[]`.
## Options
* `:usage` is the unit usage. for example `;person` for a unit
type of length. The available usage for a given unit category can
be seen with `Cldr.Unit.unit_category_usage/0`. The default is `nil`
* `:scope` is either `:small` or `nil`. In some usage, the units
used are different when the unit size is small. It is up to the
developer to determine when `scope: :small` is appropriate.
* `:alt` is either `:informal` or `nil`. Like `:scope`, the units
in use depend on whether they are being used in a formal or informal
context.
* `:locale` is any locale returned by `Cldr.validate_locale/2`
## Returns
* `{:ok, unit_list, formatting_options}` or
* `{:error, {exception, reason}}`
## Notes
`formatting_options` is a keyword list of options
that can be passed to `Cldr.Unit.to_string/3`. Its
primary intended usage is for localizing a unit that
decomposes into more than one unit (for example when
2 meters might become 6 feet 6 inches.) In such
cases, the last unit in the list (in this case the
inches) is formatted with the `formatting_options`.
## Examples
iex> meter = Cldr.Unit.new!(:meter, 1)
iex> #{inspect(__MODULE__)}.preferred_units meter, locale: "en-US", usage: :person_height
{:ok, [:foot, :inch], []}
iex> #{inspect(__MODULE__)}.preferred_units meter, locale: "en-US", usage: :person
{:ok, [:inch], []}
iex> #{inspect(__MODULE__)}.preferred_units meter, locale: "en-AU", usage: :person
{:ok, [:centimeter], []}
iex> #{inspect(__MODULE__)}.preferred_units meter, locale: "en-US", usage: :road
{:ok, [:foot], [round_nearest: 1]}
iex> #{inspect(__MODULE__)}.preferred_units meter, locale: "en-AU", usage: :road
{:ok, [:meter], [round_nearest: 1]}
"""
def preferred_units(unit, options \\ []) do
Cldr.Unit.Preference.preferred_units(unit, unquote(backend), options)
end
@doc """
Returns a list of the preferred units for a given
unit, locale, use case and scope.
The units used to represent length, volume and so on
depend on a given territory, measurement system and usage.
For example, in the US, people height is most commonly
referred to in `inches`, or informally as `feet and inches`.
In most of the rest of the world it is `centimeters`.
## Arguments
* `unit` is any unit returned by `Cldr.Unit.new/2`.
* `backend` is any Cldr backend module. That is, any module
that includes `use Cldr`. The default is `Cldr.default_backend/0`
* `options` is a keyword list of options or a
`Cldr.Unit.Conversion.Options` struct. The default
is `[]`.
## Options
* `:usage` is the unit usage. for example `;person` for a unit
type of length. The available usage for a given unit category can
be seen with `Cldr.Unit.unit_category_usage/0`. The default is `nil`
* `:scope` is either `:small` or `nil`. In some usage, the units
used are different when the unit size is small. It is up to the
developer to determine when `scope: :small` is appropriate.
* `:alt` is either `:informal` or `nil`. Like `:scope`, the units
in use depend on whether they are being used in a formal or informal
context.
* `:locale` is any locale returned by `Cldr.validate_locale/2`
## Returns
* `unit_list` or
* raises an exception
## Examples
iex> meter = Cldr.Unit.new!(:meter, 2)
iex> #{inspect(__MODULE__)}.preferred_units! meter, locale: "en-US", usage: :person_height
[:foot, :inch]
iex> #{inspect(__MODULE__)}.preferred_units! meter, locale: "en-AU", usage: :person
[:centimeter]
iex> #{inspect(__MODULE__)}.preferred_units! meter, locale: "en-US", usage: :road
[:foot]
iex> #{inspect(__MODULE__)}.preferred_units! meter, locale: "en-AU", usage: :road
[:meter]
"""
def preferred_units!(unit, options \\ []) do
Cldr.Unit.Preference.preferred_units!(unit, unquote(backend), options)
end
@grammatical_features Cldr.Config.grammatical_features()
@grammatical_gender Cldr.Config.grammatical_gender()
@default_gender :masculine
# Generate the functions that encapsulate the unit data from CDLR
@doc false
def units_for(locale \\ unquote(backend).get_locale(), style \\ Cldr.Unit.default_style())
for locale_name <- Cldr.Config.known_locale_names(config) do
locale_data =
locale_name
|> Cldr.Locale.Loader.get_locale(config)
|> Map.get(:units)
units_for_style = fn additional_units, style ->
Map.get(locale_data, style)
|> Enum.map(&elem(&1, 1))
|> Cldr.Map.merge_map_list()
|> Map.merge(additional_units)
|> Map.new()
end
for style <- @styles do
additional_units = additional_units.units_for(locale_name, style)
units = units_for_style.(additional_units, style)
def units_for(unquote(locale_name), unquote(style)) do
unquote(Macro.escape(units))
end
end
language_tag = Cldr.Config.language_tag(locale_name)
language = Map.fetch!(language_tag, :language)
grammatical_features = Map.get(@grammatical_features, language, %{})
grammatical_gender = Map.get(@grammatical_gender, language, [@default_gender])
default_gender = Enum.find(grammatical_gender, &(&1 == :neuter)) || @default_gender
def grammatical_features(unquote(locale_name)) do
unquote(Macro.escape(grammatical_features))
end
def grammatical_gender(unquote(locale_name)) do
{:ok, unquote(Macro.escape(grammatical_gender))}
end
def default_gender(unquote(locale_name)) do
{:ok, unquote(default_gender)}
end
unit_strings =
for style <- @styles do
additional_units =
additional_units.units_for(locale_name, style)
units =
units_for_style.(additional_units, style)
|> Cldr.Map.prune(fn
{k, _v} when k in [:per_unit_pattern, :per, :times] ->
true
{k, _v} ->
if String.starts_with?(Atom.to_string(k), "10"), do: true, else: false
_other -> false
end)
|> Enum.map(fn {k, v} -> {k, Cldr.Map.extract_strings(v)} end)
|> Map.new()
end
|> Cldr.Map.merge_map_list(&Cldr.Map.combine_list_resolver/3)
|> Enum.map(fn {k, v} -> {k, Enum.map(v, &String.trim/1)} end)
|> Enum.map(fn {k, v} -> {k, Enum.map(v, &String.downcase/1)} end)
|> Enum.map(fn {k, v} -> {k, Enum.uniq(v)} end)
|> Map.new
|> Cldr.Map.invert(duplicates: :shortest)
def unit_strings_for(unquote(locale_name)) do
{:ok, unquote(Macro.escape(unit_strings))}
end
end
def unit_strings_for(locale) when is_binary(locale) do
{:error, Cldr.Locale.locale_error(locale)}
end
def unit_strings_for(%LanguageTag{cldr_locale_name: cldr_locale_name}) do
unit_strings_for(cldr_locale_name)
end
def units_for(%LanguageTag{cldr_locale_name: cldr_locale_name}, style) do
units_for(cldr_locale_name, style)
end
def grammatical_features(%LanguageTag{language: language}) do
grammatical_features(language)
end
def grammatical_features(language) do
{:error, Cldr.Locale.locale_error(language)}
end
def grammatical_gender(%LanguageTag{language: language}) do
grammatical_gender(language)
end
def grammatical_gender(language) do
{:error, Cldr.Locale.locale_error(language)}
end
def default_gender(%LanguageTag{language: language}) do
default_gender(language)
end
def default_gender(language) do
{:error, Cldr.Locale.locale_error(language)}
end
end
end
end
end