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 unit this custom unit converts to (e.g.,"meter","kilogram","second"). It need not be a fundamental base unit: a derived unit such as"day"is accepted and folded down to its fundamental base ("second") at registration, with the factor and offset adjusted accordingly, so the custom unit stays convertible against every CLDR unit in the same category.: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