Current section

Files

Jump to
localize lib localize unit data.ex
Raw

lib/localize/unit/data.ex

defmodule Localize.Unit.Data do
@moduledoc """
Compile-time extraction of CLDR unit data from a pre-built ETF file.
This module loads unit data from `priv/unit/unit_data.etf`, which is
generated by `scripts/extract_unit_data.exs` from the CLDR supplemental
units XML and validity XML files. Run that script whenever the CLDR
data is updated.
"""
# alias Localize.Unit.Data.Expression
@etf_path Application.app_dir(:localize, "priv/localize/supplemental_data/unit_data.etf")
@external_resource @etf_path
@data @etf_path
|> File.read!()
|> :erlang.binary_to_term()
@prefix_components @data.prefix_components
@suffix_components @data.suffix_components
@power_components @data.power_components
@si_prefix_data @data.si_prefix_data
@si_prefix_names @data.si_prefix_names
@si_prefix_multipliers @data.si_prefix_multipliers
@base_units @data.base_units
@conversions @data.conversions
# @unit_quantities @data.unit_quantities
@simple_base_units @data.simple_base_units
@base_unit_order @data.base_unit_order
@base_unit_to_quantity @data.base_unit_to_quantity
@unit_preferences @data.unit_preferences
@unit_constants @data.unit_constants
@conversion_factors @data.conversion_factors
@valid_unit_identifiers @data.valid_unit_identifiers
@deprecated_unit_identifiers @data.deprecated_unit_identifiers
@categories @data.categories
# ── Public API ──────────────────────────────────────────────────────
@doc """
Returns the list of unit ID prefix components.
### Returns
* A list of strings such as `["arc", "british", "dessert", ...]`.
"""
@spec prefix_components() :: [String.t()]
def prefix_components, do: @prefix_components
@doc """
Returns the list of unit ID suffix components.
### Returns
* A list of strings such as `["force", "imperial", ...]`.
"""
@spec suffix_components() :: [String.t()]
def suffix_components, do: @suffix_components
@doc """
Returns the list of power components.
### Returns
* A list of strings such as `["square", "cubic", "pow2", ...]`.
"""
@spec power_components() :: [String.t()]
def power_components, do: @power_components
@doc """
Returns the list of SI prefix names.
### Returns
* A list of strings such as `["kilo", "milli", "mega", ...]`.
"""
@spec si_prefix_names() :: [String.t()]
def si_prefix_names, do: @si_prefix_names
@doc """
Returns detailed SI prefix data including symbols and powers.
### Returns
* A list of maps with keys `:type`, `:symbol`, `:power10`, and `:power2`.
"""
@spec si_prefix_data() :: [
%{type: String.t(), symbol: String.t(), power10: String.t(), power2: String.t()},
...
]
def si_prefix_data, do: @si_prefix_data
@doc """
Returns the list of known base unit names from CLDR conversion data.
### Returns
* A list of strings such as `["meter", "kilogram", "second", ...]`.
"""
@spec base_units() :: [String.t()]
def base_units, do: @base_units
@doc """
Returns all valid CLDR unit identifiers with regular status.
### Returns
* A list of strings such as `["length-kilometer", "mass-kilogram", ...]`.
"""
@spec valid_unit_identifiers() :: [String.t()]
def valid_unit_identifiers, do: @valid_unit_identifiers
@doc """
Returns all deprecated CLDR unit identifiers.
### Returns
* A list of strings.
"""
@spec deprecated_unit_identifiers() :: [String.t()]
def deprecated_unit_identifiers, do: @deprecated_unit_identifiers
@doc """
Returns the list of known unit categories.
### Returns
* A list of strings such as `["acceleration", "angle", "area", ...]`.
"""
@spec categories() :: [String.t()]
def categories, do: @categories
@doc """
Returns a map from source unit name to its CLDR base unit string.
### Returns
* A map such as `%{"foot" => "meter", "newton" => "kilogram-meter-per-square-second", ...}`.
"""
@spec conversions() :: %{String.t() => String.t()}
def conversions, do: @conversions
@doc """
Returns the list of fundamental (simple) base units.
These are the units with `status='simple'` in the CLDR `unitQuantities`
data and form the atoms of the dimensional decomposition system.
### Returns
* A list of strings such as `["candela", "kilogram", "meter", "second", ...]`.
"""
@spec simple_base_units() :: [String.t()]
def simple_base_units, do: @simple_base_units
@doc """
Returns the canonical ordering of base units from `unitQuantities`.
This ordering is used to reconstruct canonical base unit strings.
### Returns
* A list of base unit strings in canonical order.
"""
@spec base_unit_order() :: [String.t()]
def base_unit_order, do: @base_unit_order
@doc """
Returns the resolved unit constants as a map of name to float value.
### Returns
* A map such as `%{"ft_to_m" => 0.3048, "lb_to_kg" => 0.45359237, ...}`.
"""
@spec unit_constants() :: %{String.t() => float()}
def unit_constants, do: @unit_constants
@doc """
Returns the conversion factor and offset for each source unit.
The conversion from source to base unit is: `base_value = value * factor + offset`.
### Returns
* A map such as `%{"foot" => %{factor: 0.3048, offset: 0.0}, ...}`.
"""
@spec conversion_factors() :: %{String.t() => %{factor: float() | :special, offset: float()}}
def conversion_factors, do: @conversion_factors
@doc """
Returns SI prefix multipliers as a map of prefix name to numeric multiplier.
### Returns
* A map such as `%{"kilo" => 1000.0, "milli" => 0.001, ...}`.
"""
@spec si_prefix_multipliers() :: %{String.t() => float()}
def si_prefix_multipliers, do: @si_prefix_multipliers
@doc """
Returns a map from base unit string to its CLDR quantity name.
### Returns
* A map such as `%{"meter" => "length", "kilogram" => "mass", ...}`.
"""
@spec base_unit_to_quantity() :: %{String.t() => String.t()}
def base_unit_to_quantity, do: @base_unit_to_quantity
@doc """
Returns the CLDR unit preference data.
Each entry specifies preferred units for a category/usage/region
combination.
### Returns
* A list of maps with keys `:category`, `:usage`, and `:preferences`.
"""
@spec unit_preferences() :: [
%{category: String.t(), usage: String.t(), preferences: [map(), ...]},
...
]
def unit_preferences, do: @unit_preferences
# ── Runtime overlay functions ──────────────────────────────────────
#
# These functions check the custom unit registry first, then fall back
# to the compile-time data. Used by Conversion and BaseUnit modules
# to support user-defined units.
@doc """
Returns the conversion factor for a unit, checking custom registry first.
"""
@spec conversion_factor(String.t()) :: %{factor: number() | :special, offset: number()} | nil
def conversion_factor(unit_name) do
case Localize.Unit.CustomRegistry.get(unit_name) do
nil ->
Map.get(@conversion_factors, unit_name)
definition ->
%{factor: definition.factor, offset: Map.get(definition, :offset, 0.0)}
end
end
@doc """
Returns the base unit for a unit, checking custom registry first.
"""
@spec conversion(String.t()) :: String.t() | nil
def conversion(unit_name) do
case Localize.Unit.CustomRegistry.get(unit_name) do
nil ->
Map.get(@conversions, unit_name)
definition ->
definition.base_unit
end
end
end