Localize.PhoneNumber
Elixir interface to Google's libphonenumber C++ library via NIF. Provides phone number parsing, formatting, validation, type detection, and territory resolution with locale-aware defaults powered by Localize.
Usage
Parse a phone number in international format:
iex> {:ok, phone_number} = Localize.PhoneNumber.parse("+1 650-253-0000")
iex> phone_number.country_code
1
iex> phone_number.national_number
6502530000Parse a national number by supplying a territory or locale:
iex> {:ok, phone_number} = Localize.PhoneNumber.parse("020 7946 0958", territory: "GB")
iex> phone_number.country_code
44
iex> {:ok, phone_number} = Localize.PhoneNumber.parse("020 7946 0958", locale: "en-GB")
iex> phone_number.country_code
44
When neither :territory nor :locale is given, the default territory is derived from Localize.get_locale/0.
Format a parsed number:
iex> {:ok, phone_number} = Localize.PhoneNumber.parse("+1 650-253-0000")
iex> Localize.PhoneNumber.to_string(phone_number, :e164)
{:ok, "+16502530000"}
iex> Localize.PhoneNumber.to_string(phone_number, :international)
{:ok, "+1 650-253-0000"}
iex> Localize.PhoneNumber.to_string(phone_number, :national)
{:ok, "(650) 253-0000"}
iex> Localize.PhoneNumber.to_string(phone_number, :rfc3966)
{:ok, "tel:+1-650-253-0000"}Validate a number:
iex> {:ok, phone_number} = Localize.PhoneNumber.parse("+1 650-253-0000")
iex> Localize.PhoneNumber.valid?(phone_number)
true
iex> Localize.PhoneNumber.valid_for_territory?(phone_number, "US")
true
iex> Localize.PhoneNumber.possible?(phone_number)
trueDetect the number type and territory:
iex> {:ok, phone_number} = Localize.PhoneNumber.parse("+1 800-555-0199")
iex> Localize.PhoneNumber.type(phone_number)
:toll_free
iex> Localize.PhoneNumber.territory(phone_number)
"US"See the Parsing and Formatting Guide for detailed usage examples.
API Summary
| Function | Description |
|---|---|
parse/2 | Parse a phone number string into a struct. |
to_string/2 |
Format a parsed number (:e164, :international, :national, :rfc3966). Default is :international. |
valid?/1 | Check if a parsed number is valid. |
valid_for_territory?/2 | Check if a parsed number is valid for a specific territory. |
possible?/1 | Quick check if a number has a plausible length. |
type/1 |
Detect the number type (:mobile, :fixed_line, :toll_free, etc.). |
territory/1 | Get the ISO 3166-1 alpha-2 territory code for a number. |
Territory Resolution
The parse/2 function needs a default territory to interpret national-format numbers. The territory is resolved in priority order:
-
Explicit
:territoryoption — validated viaLocalize.validate_territory/1. -
Extracted from the
:localeoption — resolved viaLocalize.Territory.territory_from_locale/1. Accepts a string ("en-GB"), atom (:en_GB), orLocalize.LanguageTag.t()struct. -
Derived from the current process locale via
Localize.get_locale/0.
Prerequisites
System Libraries
Google's libphonenumber C++ library must be installed on the build machine. The NIF is compiled automatically by elixir_make during mix compile.
macOS (Homebrew):
brew install libphonenumber
This installs libphonenumber along with its transitive dependencies (protobuf, abseil, boost, and ICU). The Makefile detects the Homebrew prefix automatically on both Apple Silicon (/opt/homebrew) and Intel (/usr/local) Macs.
Linux (Debian/Ubuntu):
sudo apt-get install libphonenumber-dev libprotobuf-dev protobuf-compiler
On distributions that provide a pkg-config file for libphonenumber, the Makefile uses it automatically. Otherwise it falls back to standard system library paths.
Linux (Fedora/RHEL):
sudo dnf install libphonenumber-devel protobuf-develFrom source:
If your distribution does not package libphonenumber, build it from source. Ensure the headers are installed to a standard include path and the shared library is on the linker search path.
Elixir Dependencies
Add localize_phone_number to your mix.exs:
def deps do
[
{:localize_phone_number, "~> 0.1.0"}
]
endThe library depends on:
localize— locale and territory resolution.elixir_make— compiles the C++ NIF duringmix compile.
Architecture
NIF Design
The library is a thin Elixir wrapper around a C++ NIF that calls libphonenumber's PhoneNumberUtil singleton directly. The NIF is compiled automatically by elixir_make during mix compile.
Localize.PhoneNumber Public API (parse, to_string, valid?, type, territory)
│
├── Localize.PhoneNumber.Territory Locale-to-territory resolution via Localize
│
└── Localize.PhoneNumber.Nif NIF loader and Elixir stubs
│
└── localize_phone_number_nif.cpp
│
└── libphonenumber (PhoneNumberUtil)Parsed Phone Number Struct
Localize.PhoneNumber.parse/2 returns a Localize.PhoneNumber.Number struct:
%Localize.PhoneNumber.Number{
country_code: 1,
national_number: 6502530000,
extension: nil,
raw_input: "+1 650-253-0000"
}
The struct also carries an opaque __native__ field containing the serialized protobuf representation of the phone number. This binary is passed back to the NIF for all subsequent operations (to_string/2, valid?/1, type/1, etc.), ensuring lossless round-trips that preserve metadata such as Italian leading zeros and preferred formatting hints. The __native__ field is excluded from Inspect output.
Thread Safety
PhoneNumberUtil::GetInstance() returns a process-global singleton that is thread-safe for all read operations. No pooling or mutexes are needed — every NIF call can run concurrently on any BEAM scheduler.
Build System
The c_src/Makefile follows the same pattern used by the localize project:
-
Generates
env.mkat build time to discover ERTS include paths. - Detects the platform (macOS ARM/x86, Linux, FreeBSD) and sets appropriate compiler flags.
-
Finds libphonenumber, protobuf, and abseil via
pkg-configwith Homebrew fallback paths on macOS. -
Compiles C++17 with
-O3 -fPICand produces a shared library atpriv/localize_phone_number_nif.so.