Runtime registry for user-defined units backed by :persistent_term.
Custom units are stored in a single map keyed by unit name. The registry
is checked by Localize.Unit.Data, Localize.Unit.BaseUnit,
Localize.Unit.Conversion, and the unit formatter to overlay
runtime definitions on top of the compile-time CLDR data.
Definition structure
Each definition is a map with the following keys:
:base_unit(required) — the CLDR base unit this custom unit converts to (e.g.,"meter","kilogram","second").:factor(required) — the conversion factor:1 custom_unit = factor * base_unit.:offset(optional) — additive offset for the conversion. Defaults to0.0.:category(required) — the unit category (e.g.,"length","mass").:display(optional) — locale-specific display patterns. A nested map oflocale => style => plural_patterns.
Summary
Functions
Returns all registered custom units as a map of name to definition.
Removes all custom unit registrations. Primarily for testing.
Returns the definition for a custom unit, or nil if not registered.
Loads custom unit definitions from an .exs file.
Registers a custom unit definition.
Registers multiple custom units in a single persistent_term update.
Returns whether a unit name is registered in the custom registry.
Functions
Returns all registered custom units as a map of name to definition.
Returns
- A map of unit name to definition map. Empty when no custom units are registered.
Examples
iex> :ok = Localize.Unit.CustomRegistry.register("smoot", %{base_unit: "meter", factor: 1.7018, category: "length"})
iex> Localize.Unit.CustomRegistry.all()["smoot"].factor
1.7018
@spec clear() :: :ok
Removes all custom unit registrations. Primarily for testing.
Returns
:ok.
Examples
iex> :ok = Localize.Unit.CustomRegistry.register("smoot", %{base_unit: "meter", factor: 1.7018, category: "length"})
iex> Localize.Unit.CustomRegistry.clear()
:ok
iex> Localize.Unit.CustomRegistry.registered?("smoot")
false
Returns the definition for a custom unit, or nil if not registered.
Arguments
unit_name— the unit identifier string.
Returns
- A definition map or
nil.
Examples
iex> :ok = Localize.Unit.CustomRegistry.register("smoot", %{base_unit: "meter", factor: 1.7018, category: "length"})
iex> Localize.Unit.CustomRegistry.get("smoot").base_unit
"meter"
iex> Localize.Unit.CustomRegistry.get("no-such-unit")
nil
@spec load_file(String.t()) :: {:ok, non_neg_integer()} | {:error, String.t()}
Loads custom unit definitions from an .exs file.
The file must evaluate to a list of maps, each with a :unit key
and the standard definition fields.
Security
This function uses Code.eval_file/1 to evaluate the given
file, which executes arbitrary Elixir code. Only load files
from trusted sources. Never call this function with unsanitised
user input or paths derived from external data.
In a :prod Mix environment this function additionally requires
the :localize, :allow_runtime_unit_files config flag to be
explicitly set to true. The flag exists so that an unintended
feature switch in a production deployment cannot accidentally
surface arbitrary code execution. Set it in config/runtime.exs
(not config/config.exs) so the decision is visible at the same
layer as other deployment-time policy.
Arguments
path— path to the.exsfile.
Returns
{:ok, count}with the number of units loaded.{:error, reason}on failure, including a refusal in:prodwhen the flag is not set.
Examples
iex> {:error, message} = Localize.Unit.CustomRegistry.load_file("no/such/file.exs")
iex> message =~ "file not found"
true
Registers a custom unit definition.
Arguments
name— the unit identifier string.definition— a map with:base_unit,:factor, and:categorykeys.
Returns
:okon success.{:error, reason}if validation fails.
Examples
iex> Localize.Unit.CustomRegistry.register("smoot", %{base_unit: "meter", factor: 1.7018, category: "length"})
:ok
iex> {:error, message} = Localize.Unit.CustomRegistry.register("123bad", %{base_unit: "meter", factor: 1.0, category: "length"})
iex> message =~ "invalid unit name"
true
@spec register_batch(%{required(String.t()) => map()}) :: {:ok, non_neg_integer()}
Registers multiple custom units in a single persistent_term update.
This is significantly more memory-efficient than calling register/2
in a loop, because it avoids creating intermediate persistent_term
snapshots for each unit. Each snapshot is stored in the BEAM's literal
area and is not freed until a global garbage collection sweep, so
bulk registration via register/2 can exhaust literal memory.
Arguments
definitions— a map of%{name => definition}where each definition has:base_unit,:factor, and:categorykeys.
Returns
{:ok, count}with the number of units registered.{:error, reason}if any validation fails. No units are registered on error (the operation is atomic).
Examples
iex> definitions = %{
...> "smoot" => %{base_unit: "meter", factor: 1.7018, category: "length"},
...> "sheppey" => %{base_unit: "meter", factor: 1400.0, category: "length"}
...> }
iex> Localize.Unit.CustomRegistry.register_batch(definitions)
{:ok, 2}
Returns whether a unit name is registered in the custom registry.
Arguments
unit_name— the unit identifier string.
Returns
trueif the unit is registered, otherwisefalse.
Examples
iex> Localize.Unit.CustomRegistry.registered?("no-such-unit")
false
iex> :ok = Localize.Unit.CustomRegistry.register("smoot", %{base_unit: "meter", factor: 1.7018, category: "length"})
iex> Localize.Unit.CustomRegistry.registered?("smoot")
true