Search documentation of Localize

# Localize.Unit.CustomRegistry (Localize v0.39.0)

Copy Markdown View Source

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 to `0.0`.

- `:category` (required) — the unit category (e.g., `"length"`, `"mass"`).

- `:display` (optional) — locale-specific display patterns. A nested map of `locale => style => plural_patterns`.

## Summary

### Functions

all()

Returns all registered custom units as a map of name to definition.

clear()

Removes all custom unit registrations. Primarily for testing.

get(unit_name)

Returns the definition for a custom unit, or `nil` if not registered.

load_file(path)

Loads custom unit definitions from an `.exs` file.

register(name, definition)

Registers a custom unit definition.

register_batch(definitions)

Registers multiple custom units in a single `persistent_term` update.

registered?(unit_name)

Returns whether a unit name is registered in the custom registry.

## Functions

# all()

    @spec all() :: %{required(String.t()) => map()}

Returns all registered custom units as a map of name to definition.

# clear()

    @spec clear() :: :ok

Removes all custom unit registrations. Primarily for testing.

# get(unit_name)

    @spec get(String.t()) :: map() | nil

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`.

# load_file(path)

    @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 `.exs` file.

### Returns

- `{:ok, count}` with the number of units loaded.

- `{:error, reason}` on failure, including a refusal in `:prod` when the flag is not set.

# register(name, definition)

    @spec register(String.t(), map()) :: :ok | {:error, String.t()}

Registers a custom unit definition.

### Arguments

- `name` — the unit identifier string.

- `definition` — a map with `:base_unit`, `:factor`, and `:category` keys.

### Returns

- `:ok` on success.

- `{:error, reason}` if validation fails.

# register_batch(definitions)

    @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 `:category` keys.

### 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).

# registered?(unit_name)

    @spec registered?(String.t()) :: boolean()

Returns whether a unit name is registered in the custom registry.