Software localization

Using libphonenumber for International Phone Numbers

Libphonenumber is Google's formatting, parsing, and validation tool for international phone numbers. Learn how to use it in your global apps with this comprehensive guide.
Software localization blog category featured image | Phrase

Phone numbers vary in form and are handled differently from country to country. Google's open-source libphonenumber library can help us properly format phone numbers along with country codes and also validate input fields in web applications.

Google has its own Clojure-based JavaScript version of the library, but we'll be using another library, catamphetamine/libphonenumber-js, which is a simpler and smaller rewrite of Google's libphonenumber in JavaScript. This particular library can be integrated into any preferred framework.

For this tutorial, we'll build a framework-agnostic demo app so it can be useful for as many developers as possible.

🗒 Note » Get the code for the demo app in our GitHub repo.

libphonenumber demo app | Phrase

🔗 Resource » Make sure to stop by our Ultimate Guide to JavaScript Localization and learn everything you need to make your JS applications accessible to international users.

Basic setup and installation

Let's create a project and two separate files: index.html and app.js.

For simplicity, we'll be using the CDN version of the library. The library comes in multiple variations, such as min, max, mobile, so choose the one that best suits your requirements.

  • max — the complete metadata set is about 145 kilobytes in size (libphonenumber-js/metadata.full.json); go for this one when you need the most strict version of isValid() or if you need to detect the phone number type ("landline", "mobile", etc).
  • min — the smallest metadata set, about 80 kilobytes in size (libphonenumber-js/metadata.min.json); it gets chosen by default when you don't need to detect the phone number type or when a basic version of isValid() is enough; the min metadata set doesn't contain the regular expressions for phone number digits validation (via .isValid()) and detecting phone number type (via .getType()) for most countries; in this case, .isValid() still performs some basic phone number validation (e.g. checks phone number length), but it doesn't validate phone number digits themselves the way max metadata validation does.
  • mobile — the complete metadata set for dealing with mobile numbers only, about 95 kilobytes in size (libphonenumber-js/metadata.mobile.json); make use of it when you need max metadata and when you only accept mobile numbers; other phone number types will still be parseable, but they won't be recognized as being "valid" (.isValid() will return false).

In this tutorial, we'll use the max variation as it provides all features available.

// CDN format

https://unpkg.com/libphonenumber-js@[version]/bundle/libphonenumber-[variation].js
<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  < script src="https://unpkg.com/libphonenumber-js@1.9.6/bundle/libphonenumber-max.js"></script>

  < script src="app.js"></script>

  <title>Demo</title>

</head>

</html>

That's it, our setup is complete. All the library's methods are exposed on the global libphonenumber  object. We can now start writing the code in the app.js file and use our good old console to see the results in action.

Parsing a phone number

Let's see how we can use the parsePhoneNumber() method in some scenarios. First, let's pass the full phone number including the country code.

libphonenumber.parsePhoneNumber('+919098765432')

The method returns the data below. The library can accurately detect the country, country calling code, and the number without the country calling code.

{ 

 country: "IN",

 countryCallingCode: "91", 

 nationalNumber: "9098765432", 

 number: "+919098765432" 

}

parsePhoneNumber() also takes the country code (a two-character alphanumeric code) as the second parameter. When we are sending the country code, we can pass the number without the country code as well.

libphonenumber.parsePhoneNumber('9098765432', 'IN')

The output would be exactly the same; the method also returns some useful functions that we'll go through in detail. Some important methods include:

  1. format()
  2. formatInternational()
  3. formatNational()
  4. getType()
  5. isEqual()
  6. getURI()
  7. isNonGeographic()
  8. isPossible()
  9. isValid()

1. format(format: string, [options]): string

This method takes two arguments: the formatted type and a few additional arguments (see the example below).

let numberObj = libphonenumber.parsePhoneNumber('9098765432', 'IN')

numberObj.format('NATIONAL')

numberObj.format('INTERNATIONAL')

numberObj.format('RFC3966')

numberObj.format('IDD', {fromCountry: 'US'})

numberObj.format('NATIONAL', {nationalPrefix: false})
090987 65432 // national format (with national prefix)

+91 90987 65432  // international format

tel:+919098765432 // text that can be used for html anchor tags for call function

011 91 90987 65432 // Out of country dialing format (fromCountry option is mandatory)

90987 65432 // National number without national prefix

2. formatInternational(): string

This method is an alias of format('INTERNATIONAL') and returns the same output.

3. formatNational(): string

An alias of format('NATIONAL') and returns the same output.

4. getType(): string?

GetType will return the type of number (e.g. landline, mobile, toll-free, etc) and undefined if the number is invalid or not available for that country. Here's an example:

let numberObj1 = libphonenumber.parsePhoneNumber('9098765432', 'IN')

let numberObj2 = libphonenumber.parsePhoneNumber('08212345654', 'IN')

let numberObj3 = libphonenumber.parsePhoneNumber('+80030009009')

numberObj1.getType()

numberObj2.getType()

numberObj3.getType()
MOBILE

FIXED_LINE

TOLL_FREE

5. isEqual({number}): boolean

isEqual will return true if the number passed matches, else returns false; this method just does normal string comparison with the international format.

numberObj1.isEqual({number:"+919098765432"}) // true

6. getURI(): string?

This method is an alias of format('RFC3966') and returns the same output.

7. isNonGeographic(): boolean

Returns true if the number belongs to a non-geographic numbering plan.

let numberObj1 = libphonenumber.parsePhoneNumber('9098765432', 'IN')

let numberObj2 = libphonenumber.parsePhoneNumber('08212345654', 'IN')

let numberObj3 = libphonenumber.parsePhoneNumber('+80030009009')

numberObj1.isNonGeographic() // false

numberObj2.isNonGeographic() // false

numberObj3.isNonGeographic() // true

There are several codes that don't belong to any country, e.g. +800, +808, +870, etc. Such phone numbering plans are called "non-geographic", and their phone numbers have country set to undefined.

8. isPossible(): boolean

This method checks if the phone number is "possible". The result is purely based on the number length.

let numberObj1 = libphonenumber.parsePhoneNumber('1098765432', 'IN')

let numberObj2 = libphonenumber.parsePhoneNumber('0821564', 'IN')

numberObj1.isPossible() // true

numberObj2.isPossible() // false

9. isValid(): boolean

This method first checks isPossible(). If that's true, it then checks the phone numbers with regular expressions to see if the number is actually valid. Let's use the numbers from our previous example:

let numberObj1 = libphonenumber.parsePhoneNumber('1098765432', 'IN')

let numberObj2 = libphonenumber.parsePhoneNumber('0821564', 'IN')

numberObj1.isValid() // false

numberObj2.isValid() // false

As we can see, the number 1098765432, which was possible, is not actually a valid phone number.

Parsing and formatting incomplete phone numbers

parseIncompletePhoneNumber() and formatIncompletePhoneNumber(), as the names suggest, are used when we deal with incomplete phone numbers; both are mainly used while building other libraries on top of libphonenumber

libphonenumber.parseIncompletePhoneNumber('8 (800) 555')

libphonenumber.parseIncompletePhoneNumber('+7 800 555')

libphonenumber.formatIncompletePhoneNumber('8800555', 'RU')

libphonenumber.formatIncompletePhoneNumber('+7800555')
8800555

+7800555

8 (800) 555

+7 800 555

The asYouType() Formatter

asYouType() is mostly used to format/parse a partial number as soon as the user starts typing in an input field. Since the formatting is done on the fly, it's important to pass the 2-digit alphabet country code for it to properly format if we're not entering the country code along with the number. Check the example below:

const asYouType = new libphonenumber.AsYouType('US')

asYouType.input('2133734')

asYouType.getChars()

asYouType.getTemplate()
// output

(213) 373-4

2133734

(xxx) xxx-x

Finding phone numbers in text blocks

findPhoneNumbersInText() can be used to find phone numbers that are present in a block of text. The method takes the string of text as a first argument and an optional country parameter. If the number in the text block has a numeric country code already appended, we can skip the optional parameter, else it's required to pass the country code for the method to work.

let text = `

For tech support call +7 (800) 555-35-35 internationally

or reach a local US branch at (213) 373-4253 ext. 1234.

`

libphonenumber.findPhoneNumbersInText(text, 'US')

This method returns an array of objects where each object has details of the number found in the text.

[{

  endsAt: 41,

  startsAt: 23,

  number: {

    country: "RU",

    countryCallingCode: "7",

    metadata: {...},

    nationalNumber: "8005553535",

    number: "+78005553535",

  }

},

{

  endsAt: 112,

  startsAt: 88,

  number: {

    country: "US",

    countryCallingCode: "1",

    ext: "1234",

    metadata: {...},

    nationalNumber: "2133734253",

    number: "+12133734253",

  }

}]

isSupportedCountry(country: string): boolean

We can use the isSupportedCountry() method to check if the library supports a particular country. The method takes the 2-character country code as an argument and returns a boolean value:

libphonenumber.isSupportedCountry('US') // true

getCountries(): string[]

To fetch the list of countries supported by the library, we can use the getCountries() method:

libphonenumber.getCountries()

getExtPrefix(country: string): string

Extensions are basically 4-digit numbers that are used to route to a particular user/service among a group of users/services sharing the same main phone number.

For better readability, these extensions are generally prefixed with specific characters. To get the extension prefix of a country, we can use the getExPrefix() method:

libphonenumber.getExtPrefix('US')

libphonenumber.getExtPrefix('GB')

libphonenumber.getExtPrefix('IN')
ext prefix of US  ext.

ext prefix of UK  x

ext prefix of AU  ext.

getExampleNumber(country: string, examples: object): PhoneNumber

The library also provides a method to get a sample valid phone number of any supported country. The examples aren't included in the main library, so we need to import a JSON file that has all the example phone numbers. Follow the example below:

fetch('https://unpkg.com/libphonenumber-js@1.9.6/examples.mobile.json')

  .then(response => response.json())

  .then(examples => {

     libphonenumber.getExampleNumber('US', examples)

   }

  }

};

We'll get the following response:

country: "US"

countryCallingCode: "1"

nationalNumber: "2015550123"

number: "+12015550123"

Metadata

The libphonenumber library also provides useful metadata like 2-letter country codes, non-geographic phone number codes, codes associated with different countries etc. This metadata can be accessed programmatically by using the exported Metadata class.

libphonenumber.Metadata()
{

  metadata: {

   countries: {…},

   country_calling_codes: {…},

   nonGeographic:{…},

  }

}

Wrapping it up

The libphonenumber library is Google's ultimate formatting, parsing, and validation tool for international phone numbers. By employing this open-source library, we can avoid paid APIs that provide phone number validation. The library makes it a lot easier to format phone numbers, so they are better readable to users across the globe. The library's JavaScript version, which we used in this tutorial, is highly efficient and can be used in multiple framework-specific plugins such as react-phone-number-input or vue-phone-number-input.

If you feel ready and want to let your team localize at scale, check out Phrase, the most comprehensive localization solution on the market. The Phrase Localization Suite features a flexible API and CLI, and a beautiful web platform for your translators. With GitHub, GitLab, and Bitbucket sync, Phrase does the heavy lifting in your localization pipeline, so you can stay focused on the code you love.

Check out all Phrase features for developers and see for yourself how it can help you take your apps global.