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
6502530000

Parse 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)
true

Detect 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:

  1. Explicit :territory option — validated via Localize.validate_territory/1.
  2. Extracted from the :locale option — resolved via Localize.Territory.territory_from_locale/1. Accepts a string ("en-GB"), atom (:en_GB), or Localize.LanguageTag.t() struct.
  3. 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-devel

From 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"}
  ]
end

The library depends on:

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:

License

Apache-2.0