UNPKG

libphonenumber-js

Version:

A simpler (and smaller) rewrite of Google Android's popular libphonenumber library

820 lines (589 loc) 39.5 kB
# libphonenumber-js [![npm version](https://img.shields.io/npm/v/libphonenumber-js.svg?style=flat-square)](https://www.npmjs.com/package/libphonenumber-js) [![npm downloads](https://img.shields.io/npm/dm/libphonenumber-js.svg?style=flat-square)](https://www.npmjs.com/package/libphonenumber-js) [![coverage](https://img.shields.io/coveralls/catamphetamine/libphonenumber-js/master.svg?style=flat-square)](https://coveralls.io/r/catamphetamine/libphonenumber-js?branch=master) A simpler and smaller rewrite of Google Android's `libphonenumber` library: easy phone number parsing and formatting in javascript. [See Demo](https://catamphetamine.github.io/libphonenumber-js/) ## LibPhoneNumber [`libphonenumber`](https://github.com/googlei18n/libphonenumber) is a phone number formatting and parsing library released by Google, originally developed for (and currently used in) Google's [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) mobile phone operating system. `libphonenumber-js` is a "lighter" pure javascript rewrite of the original `libphonenumber` library (written in C++ and Java because those are the programming languages used in Android OS). While `libphonenumber` has an [official autogenerated javascript port](https://github.com/googlei18n/libphonenumber/tree/master/javascript) which is being maintained by Google, it is tightly coupled to Google's `closure` javascript utility framework. It can still be compiled into [one gigantic bundle](https://github.com/ruimarinho/google-libphonenumber) which weighs 530 kilobytes (330 kB code + 200 kB metadata) — quite a size for a simple phone number input field on a casual website. One part of me was curious about how all this phone number parsing and formatting machinery worked, and another part of me was curious if there was a way to reduce those 530 kilobytes to something more reasonable while also getting rid of all the unnecessary bulk and rewriting it all in pure javascript. The resulting library does everything a modern web application needs while maintaining a much smaller size of about 110 kilobytes. ## Difference from Google's `libphonenumber` * Much smaller footprint: 110 kilobytes (30 kB code + 80 kB sufficient metadata) vs the original Google's 530 kilobytes (330 kB code + 200 kB full metadata). * Can search for phone numbers in text (Google's autogenerated javascript port can't). * Doesn't parse alphabetic phone numbers like `1-800-GOT-MILK`. * Doesn't use ["carrier codes"](https://github.com/googlei18n/libphonenumber/blob/master/FALSEHOODS.md) when formatting numbers: "carrier codes" are only used in Colombia and Brazil and only when dialing within those countries from a mobile phone to a fixed line number. * Doesn't parse or format special local-only phone numbers: emergency phone numbers like `911`, ["short codes"](https://support.twilio.com/hc/en-us/articles/223182068-What-is-a-short-code-), numbers starting with a [`*`](https://github.com/googlei18n/libphonenumber/blob/master/FALSEHOODS.md), etc. ## Installation via [npm](https://npmjs.org/) ```sh $ npm install libphonenumber-js --save ``` via [yarn](https://yarnpkg.com) ```sh $ yarn add libphonenumber-js ``` ## Usage ### Parse phone number ```js import { parseNumber } from 'libphonenumber-js' parseNumber('Phone: 8 (800) 555 35 35.', 'RU') // Outputs: { country: 'RU', phone: '8005553535' } ``` ### Format phone number ```js import { formatNumber } from 'libphonenumber-js' formatNumber('+12133734253', 'International') // Outputs: '+1 213 373 4253' formatNumber('+12133734253', 'National') // Outputs: '(213) 373-4253' formatNumber({ country: 'US', phone: '2133734253' }, 'International') // Outputs: '+1 213 373 4253' formatNumber({ country: 'US', phone: '2133734253' }, 'National') // Outputs: '(213) 373-4253' ``` ### "As You Type" formatter ```js import { AsYouType } from 'libphonenumber-js' new AsYouType().input('+12133734') // Outputs: '+1 213 373 4' new AsYouType('US').input('2133734') // Outputs: '(213) 373-4' ``` ### Full-text search ```js import { findPhoneNumbers } from 'libphonenumber-js' findPhoneNumbers(` The number is +7 (800) 555-35-35 and not (213) 373-4253 as written in the document. `, 'US') // Outputs: // // [{ // phone : '8005553535', // country : 'RU', // startsAt : 14, // endsAt : 32 // }, { // phone : '2133734253', // country : 'US', // startsAt : 41, // endsAt : 55 // }] ``` ## Definitions ### Country code "Country code" means either a [two-letter ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) (like `US`) or a special `001` country code used for non-geographical entities (as per [Google's libphonenumber library](https://github.com/googlei18n/libphonenumber/blob/0068d861a68d3d4612f7bf8646ab844dd3cefce5/java/libphonenumber/test/com/google/i18n/phonenumbers/RegionCode.java#L23-L24)). For example, `+7 800 555 35 35` phone number belongs to Russia so it has `RU` country code where as `+800 1 1111 1111` phone number could belong to any country so it has `001` country code. ### National (significant) number "National (significant) number" are the national phone number digits (without "national prefix"). For example, `+1 213 373 4253` (or `(213) 373-4253` in national format) is a US phone number and its national (significant) number is `213 373 4253`. Another example is `+33 1 45 45 32 45` (or `01 45 45 32 45` in national format) which is a [French](https://en.wikipedia.org/wiki/Telephone_numbers_in_France) phone number where they add `0` "national prefix" when writing phone numbers in national format; in this case the national (significant) number is `1 45 45 32 45`. ### Country calling code "Country calling code" are the digits between the `+` and the national (significant) number when the number is written in international format. E.g. for US country calling code is `1` and for France it's `33`. ## API ### parseNumber(text, [defaultCountry], [options]) Attempts to parse a phone number from `text`. If [`defaultCountry`](https://github.com/catamphetamine/libphonenumber-js#country-code) is passed then it's gonna be the default country for parsing non-international phone numbers. Returns `{ country, phone, ext }` object where * `country` is a [country code](https://github.com/catamphetamine/libphonenumber-js#country-code). * `phone` is a [national (significant) number](https://github.com/catamphetamine/libphonenumber-js#national-significant-number). * `ext` is a [phone number extension](https://en.wikipedia.org/wiki/Extension_(telephone)). ```js // Parses international numbers. parseNumber('+1 213 373 4253') === { country: 'US', phone: '2133734253' } parseNumber('Phone: +1-213-373-4253.') === { country: 'US', phone: '2133734253' } parseNumber('+12133734253') === { country: 'US', phone: '2133734253' } // Parses national numbers provided a default country. parseNumber('Phone: (213) 373-4253.', 'US') === { country: 'US', phone: '2133734253' } // Parses phone number extensions. parseNumber('(213) 373-4253 ext. 123', 'US') === { country: 'US', phone: '2133734253', ext: '123' } // Parses RFC 3966 phone number URIs. parseNumber('tel:+78005553535;ext=123') === { country: 'RU', phone: '8005553535', ext: '123' } ``` If the phone number supplied isn't valid then an empty object `{}` is returned. ```js parseNumber('+1 111 111 1111') === {} parseNumber('(111) 111-1111', 'US') === {} parseNumber('abcdefg') === {} ``` Available `options`: * `defaultCountry : string` — Same as the `defaultCountry` argument. * `extended : boolean` — If set to `true` then `parseNumber()` will attempt to parse even a remotely hypothetical phone number even if it is considered "invalid". The result of "extended" parsing is an object where * `country` is a [country code](https://github.com/catamphetamine/libphonenumber-js#country-code). * `phone` is a [national (significant) number](https://github.com/catamphetamine/libphonenumber-js#national-significant-number). * `ext` is a [phone number extension](https://en.wikipedia.org/wiki/Extension_(telephone)). * `countryCallingCode` is a [country calling code](https://github.com/catamphetamine/libphonenumber-js#country-calling-code). * [`carrierCode`](https://www.voip-info.org/carrier-identification-codes/)s are only used in Colombia and Brazil and only when dialing within those countries from a mobile phone to a fixed line number. * `valid: boolean` — whether it's a "valid" (real) phone number. * `possible: boolean` — a phone number is considered "possible" when it fits the phone number length rules for a given country. E.g. for US national (significant) number regexp is `[2-9]\d{9}` and possible national (significant) number length is `10` so a phone number `(111) 111-1111` is not a "valid" number because it doesn't match the US national (significant) number regexp but it is a "possible" number because it's `10` digits long. * Some or all of these properties may be absent from the result object. ```js // If the number is valid. parseNumber('Phone: (213) 373-4253.', 'US', { extended: true }) === { country: 'US', phone: '2133734253', ext: undefined, countryCallingCode: 1, carrierCode: undefined, valid: true, possible: true } // If the number is not "valid" but "possible". parseNumber('(111) 111-1111', 'US', { extended: true }) === { country: 'US', phone: '1111111111', ext: undefined, countryCallingCode: 1, carrierCode: undefined, valid: false, possible: true } // If the number is not "valid" but "possible" // and country can't be derived from it. // (e.g. can't tell if it's a US number or a Canadian number) parseNumber('+1 111 111 1111', { extended: true }) === { country: undefined, phone: '1111111111', ext: undefined, countryCallingCode: 1, carrierCode: undefined, valid: false, possible: true } // If the number is not "possible" (invalid length). parseNumber('(213) 373', 'US', { extended: true }) === { country: 'US', phone: '213373', ext: undefined, countryCallingCode: 1, carrierCode: undefined, valid: false, possible: false } // In some cases if the number is extremely not "possible" // then an empty object `{}` is returned. // // Too short (or too long) for any country's phone number. parseNumber('1', 'US', { extended: true }) === {} // Non-existent country calling code. parseNumber('+210', { extended: true }) === {} // No phone number found. parseNumber('abcdefg', 'US', { extended: true }) === {} ``` The "extended" parsing mode is the default behaviour of the original Google's `libphonenumber`: it still returns parsed data even if the phone number being parsed is not considered valid (but is kinda "possible"). I guess this kind of behaviour is better for crawling websites for phone numbers because when mining "big data" it is better to extract all possible info rather than discard some pieces of it prematurely, e.g. when national (significant) number regexp for some country gets outdated which might very well happen because phone numbering plans are changing constantly around the world. Maybe after all it would make sense to make the "extended" parsing mode the default one in the next major version. I guess it would. Sometimes users icorrectly input phone numbers in ["out-of-country" dialing](https://en.wikipedia.org/wiki/International_direct_dialing) (IDD-prefixed) format instead of the proper international phone number format (the "+" notation). In such cases `parseNumber()` will attempt to parse such IDD-prefixed numbers if "default country" is provided: ```js // International format. parseNumber('+61 2 3456 7890') === { country: 'AU', phone: '234567890' } // IDD-prefixed format. parseNumber('011 61 2 3456 7890', 'US') === { country: 'AU', phone: '234567890' } ``` ### formatNumber(number, format, [options]) Formats a `number` into a string according to a `format`. Available `format`s: * `National` — e.g. `(213) 373-4253` * `International` — e.g. `+1 213 373 4253` * [`E.164`](https://en.wikipedia.org/wiki/E.164) — e.g. `+12133734253` * [`RFC3966`](https://www.ietf.org/rfc/rfc3966.txt) (the phone number URI) — e.g. `tel:+12133734253;ext=123` * `IDD` — ["Out-of-country" dialing](https://en.wikipedia.org/wiki/International_direct_dialing) format, e.g. `01178005553535` for `+7 800 555 35 35` being called out of `options.fromCountry === US`. If no `options.fromCountry` was passed or if there's no default IDD prefix for `options.fromCountry` then returns `undefined`. Pass `options.humanReadable: true` for a human-readable output (same output as Google `liphonenumber`'s `formatOutOfCountryCallingNumber()`). The `number` argument must be either a result of `parseNumber()` function call (to strip national prefix) or an E.164 phone number string (e.g. `+12133734253`). ```js // Formats E.164 phone numbers. formatNumber('+12133734253', 'National') === '(213) 373-4253' formatNumber('+12133734253', 'International') === '+1 213 373 4253' // Formats E.164 phone numbers when // they're not "valid" but still "possible". formatNumber('+11111111111', 'National') === '(111) 111-1111' formatNumber('+11111111111', 'International') === '+1 111 111 1111' // Formats E.164 phone numbers when // they're not "valid" and not "possible" (invalid length). formatNumber('+11111', 'National') === '1111' formatNumber('+11111', 'International') === '+1 1111' // Formats a result of `parseNumber()` function call. const parsedNumber = parseNumber('2133734253', 'US') formatNumber(parsedNumber, 'National') === '(213) 373-4253' formatNumber(parsedNumber, 'International') === '+1 213 373 4253' // Formats a result of `parseNumber()` function call in "extended" mode // when it's not a "valid" number but is still a "possible" one. const possibleNumber = parseNumber('+11111111111', { extended: true }) formatNumber(possibleNumber, 'National') === '(111) 111-1111' formatNumber(possibleNumber, 'International') === '+1 111 111 1111' // Formats a result of `parseNumber()` function call in "extended" mode // when it's neither a "valid" number nor a "possible" one (invalid length). const possibleNumber = parseNumber('+11111', { extended: true }) formatNumber(possibleNumber, 'National') === '1111' formatNumber(possibleNumber, 'International') === '+1 1111' // Formats phone number extensions. formatNumber({ country: 'US', phone: '2133734253', ext: '123' }, 'National') === '(213) 373-4253 ext. 123' // When given an object not having `phone` property // (e.g. a empty object `{}`) it will throw. formatNumber({}) throws Error ``` Available `options`: ```js { formatExtension(number, extension) — Formats `number` and `extension` into a string. By default returns `${number} ext. ${extension}` for almost all countries with rare exceptions of some special cases like `${number} x${extension}` for UK. } ``` ### `class` AsYouType(defaultCountry) Creates a formatter for a partially entered phone number. The [`defaultCountry`](https://github.com/catamphetamine/libphonenumber-js#country-code) is optional and, if specified, is gonna be the default country for formatting non-international phone numbers. The formatter instance provides two methods: * `input(text)` — Takes any text and appends it to the input. Returns the formatted phone number. * `reset()` — Resets the input. ```js new AsYouType().input('+12133734') === '+1 213 373 4' new AsYouType('US').input('2133734') === '(213) 373-4' ``` The formatter also provides the following getters: * `country` — Phone number [country](https://github.com/catamphetamine/libphonenumber-js#country-code). * `getNationalNumber()` — Returns the national (significant) number part of the phone number. * `template` — The template used to format the phone number. Digits (and the `+` sign, if present) are denoted by `x`-es. ```js const asYouType = new AsYouType() asYouType.input('+1-213-373-4253') === '+1 213 373 4253' asYouType.country === 'US' asYouType.getNationalNumber() === '2133734253' asYouType.template === 'xx xxx xxx xxxx' ``` "As You Type" formatter was created by Google as part of their Android OS and therefore only works for numerical keyboard input, i.e. it can only accept digits (and a `+` sign in the start of an international number). When used on desktops where a user can input all kinds of punctuation (spaces, dashes, parens, etc) it simply ignores everything except digits (and a `+` sign in the start of an international number). Google's "As You Type" formatter does not support entering phone number extensions. If your project requires phone number extensions input then use a separate input field for that. ### findPhoneNumbers(text, [defaultCountry], [options]) Searches for phone numbers in a given text. ```js import { findPhoneNumbers } from 'libphonenumber-js' findPhoneNumbers(` The number is +7 (800) 555-35-35 and not (213) 373-4253 as written in the document. `, 'US') // Outputs: // // [{ // phone : '8005553535', // country : 'RU', // startsAt : 14, // endsAt : 32 // }, // { // phone : '2133734253', // country : 'US', // startsAt : 41, // endsAt : 55 // }] ``` By default it processes the whole text and then outputs the phone numbers found. If the text is very big (say, a hundred thousand characters) then it might freeze the user interface for a couple of seconds. To avoid such lags one can employ iterators to perform the search asynchronously (e.g. using `requestIdleCallback` or `requestAnimationFrame`). ES6 iterator: ```js import { searchPhoneNumbers } from 'libphonenumber-js' const text = ` The number is +7 (800) 555-35-35 and not (213) 373-4253 as written in the document. ` async function() { for (const number of searchPhoneNumbers(text, 'US')) { console.log(number) await new Promise(resolve => setTimeout(resolve, 0)) } console.log('Finished') } ``` Java-style iterator (for those still not using ES6): ```js import { PhoneNumberSearch } from 'libphonenumber-js' const search = new PhoneNumberSearch(` The number is +7 (800) 555-35-35 and not (213) 373-4253 as written in the document. `, { defaultCountry: 'US' }) // Search cycle iteration. const iteration = () => { if (search.hasNext()) { console.log(search.next()) setTimeout(iteration, 0) } else { console.log('Finished') } } // Run the search. iteration() ``` ### findNumbers(text, [defaultCountry], [options]) Searches for phone numbers in a given text. This is the Google's original implementation and it's not implemented in this library (though `findPhoneNumbers()` uses some parts of it). Although Google's javascript port doesn't support this functionality the Java and C++ ports do. I guess Google just doesn't need to crawl phone numbers on Node.js because they can afford to hire a Java/C++ developer to do that. Still, javascript nowadays is the most popular programming language given its simplicity and user-friendliness. I made my take on porting Google's `PhoneNumberMatcher.java` into javascript. The overall syntax has mostly been ported: see `findNumbers.js`, `src/PhoneNumberMatcher.js` and `src/PhoneNumberMatcher.test.js`. If someone wants they can finish the porting process and submit a pull request. Use `findPhoneNumbers()` instead. ### getNumberType(number, [defaultCountry]) Determines phone number type (fixed line, mobile, toll free, etc). This function will work if `--extended` (or relevant `--types`) metadata is available (see [Metadata](#metadata) section of this document). The regular expressions used to differentiate between various phone number types consume a lot of space (two thirds of the total size of the `--extended` library build) therefore they're not included in the bundle by default. The `number` argument can be either a result of the `parseNumber()` function call — `{ country, phone }`or a string possibly accompanied with the second `defaultCountry` argument (the string is gonna be parsed in this case). ```js getNumberType('9160151539', 'RU') === 'MOBILE' getNumberType({ phone: '9160151539', country: 'RU' }) === 'MOBILE' ``` ### isValidNumber(number, [defaultCountry]) Checks if a phone number is valid, the validation is more strict than `parseNumber()`. The `number` argument can be either a result of the `parseNumber()` function call — `{ country, phone }`or a string possibly accompanied with the second `defaultCountry` argument (the string is gonna be parsed in this case). ```js isValidNumber('+1-213-373-4253') === true isValidNumber('+1-213-373') === false isValidNumber('(213) 373-4253', 'US') === true isValidNumber('(213) 37', 'US') === false isValidNumber({ phone: '2133734253', country: 'US' }) === true ``` The difference between using `parseNumber()` and `isValidNumber()` for phone number validation is that `isValidNumber()` also checks the precise regular expressions of possible phone numbers for a country. For example, for Germany `parseNumber('123456', 'DE')` would return `{ country: 'DE', phone: '123456' }` because this phone number matches the general phone number rules for Germany. But, if the metadata is compiled with `--extended` (or relevant `--types`) flag (see [Metadata](#metadata) section of this document) then `isValidNumber()` is gonna use those precise regular expressions for extensive validation and `isValid('123456', 'DE')` will return `false` because the phone number `123456` doesn't actually exist in Germany. This is how it is implemented in the original Google's [`libphonenumber`](https://static.javadoc.io/com.googlecode.libphonenumber/libphonenumber/8.9.1/com/google/i18n/phonenumbers/PhoneNumberUtil.html#parse-java.lang.CharSequence-java.lang.String-): `parseNumber()` parses phone numbers and loosely validates them while `isValidNumber()` validates phone numbers precisely (provided the precise regular expressions are included in metadata). The precise regular expressions aren't included in the default metadata because that would cause the default metadata to grow twice in its size: the complete ("full") metadata size is about 145 kilobytes while the reduced ("default") metadata size is about 77 kilobytes. Hence in the default configuration `isValidNumber()` performs absolutely the same "lite" validation as `parseNumber()`. For enabling extensive phone number validation the simplest way is to import functions from `libphonenumber-js/custom` module and supply them with `libphonenumber-js/metadata.full.json`. For generating custom metadata see the instructions provided in the [Customizing metadata](#customizing-metadata) section of this document. #### Using phone number validation feature I personally wouldn't rely on strict phone number validation too much because it might get outdated: * First, new phone number rules are added to Google's `libphonenumber` library after they have already been implemented in real life (which introduces a delay). <!-- * Then those new rules from Google's `libphonenumber` are updated automatically in this library (the scheduled update script introduces a small delay of 1 day, unless it malfunctions). --> * From time to time those new rules from Google's `libphonenumber` are updated in this library. * And then there's still the web application itself using this library and until a developer installs `libphonenumber-js@latest` manually and redeploys the web application it's gonna use the old (potentially outdated) phone number validation rules which could result in losing customers with perfectly valid (but not yet supported) phone numbers if a website form is too strict about validating user's input. Phone number validation rules are [constantly changing](https://github.com/googlei18n/libphonenumber/commits/master/resources/PhoneNumberMetadata.xml) for `--extended` rules and are fairly static for "general" ones. Still imagine a web application (e.g. a promosite or a "personal website") being deployed once and then running for years without any maintenance. ### getCountryCallingCode(country) There have been requests for a function returning a [country calling code](https://github.com/catamphetamine/libphonenumber-js#country-calling-code) by [country code](https://github.com/catamphetamine/libphonenumber-js#country-code). ```js getCountryCallingCode('RU') === '7' getCountryCallingCode('IL') === '972' ``` ### getExtPrefix(country) Returns phone number extension prefix for a given `country`. If no custom ext prefix is defined for a `country` then the default `" ext. "` prefix is returned. ```js getExtPrefix('US') === ' ext. ' getExtPrefix('GB') === ' x' ``` <!-- ### parsePhoneNumberCharacter(nextCharacter, value) Parses next character while parsing `value` from text: discards everything except `+` and digits, and `+` is only allowed at the start of a phone number. Can be used for [`input-format`](https://github.com/catamphetamine/input-format). ```js parsePhoneNumberCharacter('5', '880055') === '5' parsePhoneNumberCharacter('+', '') === '+' parsePhoneNumberCharacter('+', '+7800') === '' parsePhoneNumberCharacter('a', '') === '' ``` --> ### parseIncompletePhoneNumber(text) Parses phone number characters (`+` and digits). Can be used for building a phone number input component (e.g. [react-phone-number-input](https://github.com/catamphetamine/react-phone-number-input/)). ```js parseIncompletePhoneNumber('8 (800) 555') === '8800555' parseIncompletePhoneNumber('+7 800 555') === '+7800555' parseIncompletePhoneNumber('+٤٤٢٣٢٣٢٣٤') === '+442323234' ``` ### formatIncompletePhoneNumber(value, country, metadata) Formats incomplete phone number as a national one for a given `country`. If `country` is not specified then outputs the phone number in international format. This is just an alias for `new AsYouType(country, metadata).input(value)`. Can be used for building a phone number input component (e.g. [react-phone-number-input](https://github.com/catamphetamine/react-phone-number-input/)). ```js formatIncompletePhoneNumber('8800555', 'RU', metadata) === '8 (800) 555' formatIncompletePhoneNumber('+7800555', null, metadata) === '+7 800 555' ``` ## React There's also a React component utilizing this library — [`react-phone-number-input`](https://github.com/catamphetamine/react-phone-number-input) (or [without country select](https://github.com/catamphetamine/react-phone-number-input#without-country-select)). ## Examples For those asking for phone number examples for use in `<input placeholder/>`s: see `libphonenumber-js/examples.mobile.json`. <!-- ## To do Everything's done --> ## Bug reporting When reporting an issue one must also provide a link to [Google's `libphonenumber` demo page](https://libphonenumber.appspot.com/) illustrating the expected behaviour. This includes validation, parsing, formatting and "as you type" formatting. For example, for an Australian number `438 331 999` Google's demo [outputs four sections](https://libphonenumber.appspot.com/phonenumberparser?number=438331999&country=AU) — "Parsing Result", "Validation Results", "Formatting Results" and "AsYouTypeFormatter Results". In a bug report, first describe the observed `libphonenumber-js` demo result and then Google's demo result (with a link to it) which must differ from the observed `libphonenumber-js` demo result. If the observed `libphonenumber-js` demo result is the same as Google's demo result and you don't agree with Google's demo result then create an issue in [Google's repo](https://github.com/googlei18n/libphonenumber). Phone number validation bugs should **only** be reported if they appear when using [custom metadata functions](#customizing-metadata) fed with `metadata.full.json` because by default all functions in this library use the reduced metadata set which results in looser validation than the original Google `libphonenumber`'s. The [demo page](https://catamphetamine.github.io/libphonenumber-js/) also uses the reduced metadata set and therefore its validation is also looser than the original Google `libphonenumber`'s. ## TypeScript [TypeScript support](https://github.com/catamphetamine/libphonenumber-js/blob/master/index.d.ts) for this library is entirely community-driven. I myself don't use TypeScript. Send your pull requests. ## CDN One can use any npm CDN service, e.g. [unpkg.com](https://unpkg.com) or [jsdelivr.net](https://jsdelivr.net) ```html <!-- `libphonenumber-js` (is used internally by `react-phone-number-input`). --> <script src="https://unpkg.com/libphonenumber-js/bundle/libphonenumber-js.min.js"></script> <script> alert(new libphonenumber.AsYouType('US').input('213-373-4253')) </script> ``` <!-- ## Standalone For those who aren't using bundlers for some reason there's a way to build a standalone version of the library * `git clone https://github.com/catamphetamine/libphonenumber-js.git` * `npm install` * `npm run build` * See the `bundle` folder for `libphonenumber-js.min.js` ```html <script src="/scripts/libphonenumber-js.min.js"></script> <script> alert(new libphonenumber.AsYouType('US').input('213-373-4253')) </script> ``` --> ## Metadata Metadata is generated from Google's original [`PhoneNumberMetadata.xml`](https://github.com/googlei18n/libphonenumber/blob/master/resources/PhoneNumberMetadata.xml) by transforming XML into JSON and removing unnecessary fields. <!-- Currently I have a script set up monitoring changes to `PhoneNumberMetadata.xml` in Google's repo and automatically releasing new versions of this library when metadata in Google's repo gets updated. So this library's metadata is supposed to be up-to-date. Still, in case the automatic metadata update script malfunctions some day, anyone can request metadata update via a Pull Request here on GitHub: --> The metadata update process is automated through an "autoupdate" script (see `./autoupdate.sh` or `./autoupdate.cmd`). The script detects changes to `PhoneNumberMetadata.xml` in Google `libphonenumber`'s repo and if there are changes then it pulls the latest metadata, processes it, commits the changes to GitHub, builds a new version of the library and releases it to NPM. So this library's metadata is supposed to be up-to-date. I could set up this script to run automatically but on my Windows machine `ssh-agent` doesn't work properly so I run the "autoupdate" script manually from time to time. <!-- In case I forget to run the "autoupdate" script for a long time anyone can request metadata update via a Pull Request here on GitHub: * Fork this repo * `npm install` * `npm run metadata:update:branch` * Submit a Pull Request to this repo from the `update-metadata` branch of your fork `npm run metadata:update:branch` command creates a new `update-metadata` branch, downloads the new [`PhoneNumberMetadata.xml`](https://github.com/googlei18n/libphonenumber/blob/master/resources/PhoneNumberMetadata.xml) into the project folder replacing the old one, generates JSON metadata out of the XML one, checks if the metadata has changed, runs the tests, commits the new metadata and pushes the commit to the remote `update-metadata` branch of your fork. Alternatively, a developer may wish to update metadata urgently, without waiting for a pull request approval. In this case just perform the steps described in the [Customizing metadata](#customizing-metadata) section of this document. --> ## Customizing metadata This library comes prepackaged with three flavours of metadata * `metadata.full.json` — contains everything, including all regular expressions for precise phone number validation and getting phone number type, but weighs `145 kilobytes`. * `metadata.min.json` — (default) the minimal one, doesn't contain regular expressions for precise phone number validation and getting phone number type, weighs `80 kilobytes`. * `metadata.mobile.json` — contains regular expressions for precise **mobile** phone number validation, weighs `105 kilobytes`. Furthermore, if only a specific set of countries is needed in a project, and a developer really wants to reduce the resulting bundle size, say, by 50 kilobytes (even when including all regular expressions for precise phone number validation and getting phone number type), then he can generate such custom metadata and pass it as an extra argument to this library's functions. First, add metadata generation script to **your project's** `package.json` ```js { "scripts": { "libphonenumber-metadata": "libphonenumber-generate-metadata metadata.min.json --countries RU,DE --extended", } } ``` And then run it like `npm run libphonenumber-metadata`. The arguments are * The first argument is the output metadata file path. * `--countries` argument is a comma-separated list of the countries included (if `--countries` is omitted then all countries are included). * `--extended` argument may be passed to include all regular expressions for precise phone number validation and getting phone number type, which increases the precision of phone number validation but at the same time it will enlarge the resulting metadata size approximately twice. * `--types ...` argument may be passed instead of `--extended` to only include the precise phone number type regular expressions for a specific set of phone number types (a comma-separated list, e.g. `--types mobile,fixed_line`). [The complete list of phone number types](https://github.com/catamphetamine/libphonenumber-js/blob/master/source/tools/generate.js#L6-L18). Then use the generated `metadata.min.json` with the exported "custom" functions. In ES6 that would be ```js import { parseNumber, formatNumber, isValidNumber, getNumberType, AsYouType } from 'libphonenumber-js/custom' import metadata from 'libphonenumber-js/metadata.full.json' parseNumber('+78005553535', metadata) formatNumber({ phone: '8005553535', country: 'RU' }, metadata) isValidNumber('+78005553535', metadata) getNumberType('+78005553535', metadata) new AsYouType('RU', metadata).input('+78005553535') ``` or ```js import { parseNumber as parseNumberCustom, formatNumber as formatNumberCustom, isValidNumber as isValidNumberCustom, getNumberType as getNumberTypeCustom, AsYouType as AsYouTypeCustom } from 'libphonenumber-js/custom' export const parseNumber = (...args) => parseNumberCustom(...args, metadata) export const formatNumber = (...args) => formatNumberCustom(...args, metadata) export const isValidNumber = (...args) => isValidNumberCustom(...args, metadata) export const getNumberType = (...args) => getNumberTypeCustom(...args, metadata) export class AsYouType extends AsYouTypeCustom { constructor(country) { super(country, metadata) } } ``` And for [Common.js](https://auth0.com/blog/javascript-module-systems-showdown/) environment that would be ```js var custom = require('libphonenumber-js/custom') var metadata = require(libphonenumber-js/metadata.full.json) exports.parseNumber = function parseNumber() { var parameters = Array.prototype.slice.call(arguments) parameters.push(metadata) return custom.parseNumber.apply(this, parameters) } exports.formatNumber = function formatNumber() { var parameters = Array.prototype.slice.call(arguments) parameters.push(metadata) return custom.formatNumber.apply(this, parameters) } exports.isValidNumber = function isValidNumber() { var parameters = Array.prototype.slice.call(arguments) parameters.push(metadata) return custom.isValidNumber.apply(this, parameters) } exports.getNumberType = function isValidNumber() { var parameters = Array.prototype.slice.call(arguments) parameters.push(metadata) return custom.getNumberType.apply(this, parameters) } exports.AsYouType = function AsYouType(country) { custom.AsYouType.call(this, country, metadata) } exports.AsYouType.prototype = Object.create(custom.AsYouType.prototype, {}) exports.AsYouType.prototype.constructor = exports.AsYouType ``` Metadata should be re-generated each time the project is being deployed because Google constantly updates their metadata. <!-- ## To do --> <!-- ## Automatic metadata update setup Create a daily (24 * 60 * 60) `launchd` job http://alvinalexander.com/mac-os-x/mac-osx-startup-crontab-launchd-jobs ```sh mkdir /Users/kuchumovn/work/libphonenumber-js-autoupdate git clone https://github.com/catamphetamine/libphonenumber-js.git /Users/kuchumovn/work/libphonenumber-js-autoupdate cd /Users/kuchumovn/work/libphonenumber-js-autoupdate npm install chmod u+x /Users/kuchumovn/work/libphonenumber-js-autoupdate/autoupdate.sh nano ~/Library/LaunchAgents/com.github.catamphetamine.libphonenumber-js.metadata-update.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.github.catamphetamine.libphonenumber-js.metadata-update</string> <key>ProgramArguments</key> <array> <string>/Users/kuchumovn/work/libphonenumber-js-autoupdate/autoupdate.sh</string> </array> <key>Nice</key> <integer>1</integer> <key>StartInterval</key> <integer>86400</integer> <key>RunAtLoad</key> <true/> <key>StandardErrorPath</key> <string>/tmp/libphonenumber.errors.txt</string> <key>StandardOutPath</key> <string>/tmp/libphonenumber.output.txt</string> </dict> </plist> launchctl load ~/Library/LaunchAgents/com.github.catamphetamine.libphonenumber-js.metadata-update.plist launchctl list | grep 'libphonenumber-js' ``` --> ## Contributing After cloning this repo, ensure dependencies are installed by running: ```sh npm install ``` This module is written in ES6 and uses [Babel](http://babeljs.io/) for ES5 transpilation. Widely consumable JavaScript can be produced by running: ```sh npm run build ``` Once `npm run build` has run, you may `import` or `require()` directly from node. After developing, the full test suite can be evaluated by running: ```sh npm test ``` When you're ready to test your new functionality on a real project, you can run ```sh npm pack ``` It will `build`, `test` and then create a `.tgz` archive which you can then install in your project folder ```sh npm install [module name with version].tar.gz ``` ## Advertisement If you're looking for an international "2 days ago" javascript solution then check out [`javascript-time-ago`](https://github.com/catamphetamine/javascript-time-ago). ## License [MIT](LICENSE)