├── .gitattributes ├── .bowerrc ├── gjslint.conf ├── .gitignore ├── .editorconfig ├── src ├── template │ └── country.metadata.tmpl ├── format.js ├── validation.result.js ├── error.js ├── asyoutypeformatter.js ├── type.js ├── filter │ └── metadata.js └── phonenumbers.js ├── Makefile ├── bower.json ├── package.json ├── demo ├── getexample.html ├── genmeta.html └── demo.html ├── README.md ├── gulpfile.js └── dist └── eu.extended.i18n.phonenumbers.min.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.conf linguist-documentation 2 | demo/* linguist-documentation 3 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "json": "bower.json" 4 | } 5 | -------------------------------------------------------------------------------- /gjslint.conf: -------------------------------------------------------------------------------- 1 | --strict 2 | --jsdoc 3 | --summary 4 | --max_line_length=120 5 | --custom_jsdoc_tags=property,nocollapse 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | *.log 3 | 4 | # Node 5 | /node_modules 6 | 7 | # Bower 8 | /bower_components 9 | 10 | # IDEs 11 | /.idea 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | 5 | indent_style = space 6 | indent_size = 2 7 | 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /src/template/country.metadata.tmpl: -------------------------------------------------------------------------------- 1 | <% 2 | %output% 3 | var meta = new leodido.i18n.MetadataFilter(countries); 4 | %> 5 | goog.provide('<%= namespace %>'); 6 | 7 | 8 | <%= mapJsdoc %> 9 | <%= namespace %>.countryCodeToRegionCodeMap = <% print(JSON.stringify(meta.map, null, 2)); %>; 10 | 11 | 12 | <%= dataJsdoc %> 13 | <%= namespace %>.countryToMetadata = <% print(JSON.stringify(meta.data, null, 2)); %>; 14 | -------------------------------------------------------------------------------- /src/format.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.i18n.PhoneNumbers.FORMAT'); 4 | 5 | goog.require('i18n.phonenumbers.PhoneNumberFormat'); 6 | 7 | 8 | /** 9 | * Available formats 10 | * @enum {number} 11 | */ 12 | var formats = {}; 13 | formats['E164'] = i18n.phonenumbers.PhoneNumberFormat.E164; 14 | formats['INTERNATIONAL'] = i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL; 15 | formats['NATIONAL'] = i18n.phonenumbers.PhoneNumberFormat.NATIONAL; 16 | formats['RFC3966'] = i18n.phonenumbers.PhoneNumberFormat.RFC3966; 17 | -------------------------------------------------------------------------------- /src/validation.result.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.i18n.PhoneNumbers.RESULT'); 4 | 5 | goog.require('i18n.phonenumbers.PhoneNumberUtil.ValidationResult'); 6 | 7 | 8 | /** 9 | * Validation results hash 10 | * @enum {number} 11 | */ 12 | var results = {}; 13 | results['IS_POSSIBLE'] = i18n.phonenumbers.PhoneNumberUtil.ValidationResult.IS_POSSIBLE; 14 | results['INVALID_COUNTRY_CODE'] = i18n.phonenumbers.PhoneNumberUtil.ValidationResult.INVALID_COUNTRY_CODE; 15 | results['TOO_SHORT'] = i18n.phonenumbers.PhoneNumberUtil.ValidationResult.TOO_SHORT; 16 | results['TOO_LONG'] = i18n.phonenumbers.PhoneNumberUtil.ValidationResult.TOO_LONG; 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: minor major patch release all 2 | 3 | release: 4 | npm run release 5 | npm run release-eu-extended 6 | 7 | all: release 8 | npm run release-eurozone19 9 | npm run release-schengen26 10 | npm run release-eu28 11 | npm run release-efta 12 | npm run release-eea 13 | npm run release-nordiccouncil 14 | npm run release-bsec12 15 | npm run release-visegradgroup 16 | npm run release-cefta 17 | npm run release-balticassembly 18 | npm run release-guam 19 | npm run release-benelux 20 | npm run release-eucouncil 21 | 22 | major: 23 | gulp bump --level=major 24 | 25 | minor: 26 | gulp bump --level=minor 27 | 28 | patch: 29 | gulp bump --level=patch 30 | -------------------------------------------------------------------------------- /src/error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.i18n.PhoneNumbers.ERROR'); 4 | 5 | goog.require('i18n.phonenumbers.Error'); 6 | 7 | 8 | /** 9 | * Error messages 10 | * @enum {string} 11 | */ 12 | var errors = {}; 13 | 14 | 15 | /** 16 | * Self explanatory. 17 | */ 18 | errors['INVALID_COUNTRY_CODE'] = i18n.phonenumbers.Error.INVALID_COUNTRY_CODE; 19 | 20 | 21 | /** 22 | * This generally indicates the string passed in had less than 3 digits in it. 23 | * More specifically, the number failed to match the pattern. 24 | */ 25 | errors['NOT_A_NUMBER'] = i18n.phonenumbers.Error.NOT_A_NUMBER; 26 | 27 | 28 | /** 29 | * This indicates the string started with an international dialing prefix, 30 | * but after this was stripped from the number, 31 | * had less digits than any valid phone number (including country calling code) could have. 32 | */ 33 | errors['TOO_SHORT_AFTER_IDD'] = i18n.phonenumbers.Error.TOO_SHORT_AFTER_IDD; 34 | 35 | 36 | /** 37 | * This indicates the string, after any country calling code has been stripped, 38 | * had less digits than any valid phone number could have. 39 | */ 40 | errors['TOO_SHORT_NSN'] = i18n.phonenumbers.Error.TOO_SHORT_NSN; 41 | 42 | 43 | /** 44 | * This indicates the string had more digits than any valid phone number could have. 45 | */ 46 | errors['TOO_LONG'] = i18n.phonenumbers.Error.TOO_LONG; 47 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18n-phonenumbers", 3 | "version": "0.704.0", 4 | "description": "Parse, format, and validate international phone numbers through Google's libphonenumber", 5 | "authors": [ 6 | "Leo Di Donato (http://git.io/leodido)" 7 | ], 8 | "main": [ 9 | "i18n.phonenumbers.min.js", 10 | "lite.i18n.phonenumbers.min.js", 11 | "eu.extended.i18n.phonenumbers.min.js" 12 | ], 13 | "license": "Apache-2.0", 14 | "repository": { 15 | "type": "git", 16 | "url": "http://github.com/leodido/i18n.phonenumbers.js.git" 17 | }, 18 | "homepage": "http://github.com/leodido/i18n.phonenumbers.js", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "gulpfile.js", 24 | "bower.json", 25 | "package.json", 26 | "gjslint.conf", 27 | "src" 28 | ], 29 | "devDependencies": { 30 | "closure-library": "git@github.com:google/closure-library.git#eb26f425dbd99a70d2955d8fcc892f2fd0178acb", 31 | "closure-compiler": "http://dl.google.com/closure-compiler/compiler-latest.tar.gz", 32 | "libphonenumber": "git@github.com:googlei18n/libphonenumber.git#libphonenumber-7.0.6" 33 | }, 34 | "keywords": [ 35 | "phone", 36 | "telephone", 37 | "phone number", 38 | "mobile phone", 39 | "i18n", 40 | "validator", 41 | "formatter" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /src/asyoutypeformatter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.i18n.AsYoutTypeFormatter'); 4 | 5 | goog.require('i18n.phonenumbers.AsYouTypeFormatter'); 6 | 7 | 8 | 9 | /** 10 | * Real-time phone number formatter 11 | * 12 | * @param {i18n.phonenumbers.RegionCode} regionCode 13 | * @constructor 14 | */ 15 | var Formatter = function(regionCode) { 16 | this.formatter = new i18n.phonenumbers.AsYouTypeFormatter(regionCode); 17 | }; 18 | 19 | 20 | /** 21 | * Formats a phone number on-the-fly as each digit is entered. 22 | * @param {string} nextChar 23 | * @return {string} 24 | */ 25 | Formatter.prototype.inputDigit = function(nextChar) { 26 | return this.formatter.inputDigit(nextChar); 27 | }; 28 | 29 | 30 | /** 31 | Same as {@link #inputDigit}, but remembers the position where 32 | * {@code nextChar} is inserted, so that it can be retrieved later by using 33 | * {@link #getRememberedPosition}. The remembered position will be automatically 34 | * adjusted if additional formatting characters are later inserted/removed in 35 | * front of {@code nextChar}. 36 | * 37 | * @param {string} nextChar 38 | * @return {string} 39 | */ 40 | Formatter.prototype.inputDigitAndRememberPosition = function(nextChar) { 41 | return this.formatter.inputDigitAndRememberPosition(nextChar); 42 | }; 43 | 44 | 45 | /** 46 | * Returns the current position in the partially formatted phone number of the 47 | * character which was previously passed in as the parameter of 48 | * {@link #inputDigitAndRememberPosition}. 49 | * 50 | * @return {number} 51 | */ 52 | Formatter.prototype.getRememberedPosition = function() { 53 | return this.formatter.getRememberedPosition(); 54 | }; 55 | 56 | 57 | goog.exportSymbol('leodido.i18n.AsYouTypeFormatter', Formatter); 58 | goog.exportSymbol('leodido.i18n.AsYouTypeFormatter.prototype.inputDigit', Formatter.prototype.inputDigit); 59 | goog.exportSymbol( 60 | 'leodido.i18n.AsYouTypeFormatter.prototype.inputDigitAndRememberPosition', 61 | Formatter.prototype.inputDigitAndRememberPosition 62 | ); 63 | goog.exportSymbol( 64 | 'leodido.i18n.AsYouTypeFormatter.prototype.getRememberedPosition', 65 | Formatter.prototype.getRememberedPosition 66 | ); 67 | 68 | -------------------------------------------------------------------------------- /src/type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.i18n.PhoneNumbers.TYPE'); 4 | 5 | goog.require('i18n.phonenumbers.PhoneNumberType'); 6 | 7 | 8 | /** 9 | * Type of phone numbers. 10 | * @enum {number} 11 | */ 12 | var types = {}; 13 | types['FIXED_LINE'] = i18n.phonenumbers.PhoneNumberType.FIXED_LINE; 14 | types['MOBILE'] = i18n.phonenumbers.PhoneNumberType.MOBILE; 15 | 16 | 17 | /** 18 | * In some regions (e.g. the USA), 19 | * it is impossible to distinguish between fixed-line and mobile numbers by looking at the phone number itself 20 | */ 21 | types['FIXED_LINE_OR_MOBILE'] = i18n.phonenumbers.PhoneNumberType.FIXED_LINE_OR_MOBILE; 22 | 23 | 24 | /** 25 | * Freephone lines 26 | */ 27 | types['TOLL_FREE'] = i18n.phonenumbers.PhoneNumberType.TOLL_FREE; 28 | types['PREMIUM_RATE'] = i18n.phonenumbers.PhoneNumberType.PREMIUM_RATE; 29 | 30 | 31 | /** 32 | * The cost of this call is shared between the caller and the recipient, 33 | * and is hence typically less than PREMIUM_RATE calls. 34 | * See http://en.wikipedia.org/wiki/Shared_Cost_Service for more information. 35 | */ 36 | types['SHARED_COST'] = i18n.phonenumbers.PhoneNumberType.SHARED_COST; 37 | 38 | 39 | /** 40 | * Voice over IP numbers. This includes TSoIP (Telephony Service over IP). 41 | */ 42 | types['VOIP'] = i18n.phonenumbers.PhoneNumberType.VOIP; 43 | 44 | 45 | /** 46 | * A personal number is associated with a person, and may be routed to either a MOBILE or FIXED_LINE number. 47 | * Some more information can be found here = http://en.wikipedia.org/wiki/Personal_Numbers 48 | */ 49 | types['PERSONAL_NUMBER'] = i18n.phonenumbers.PhoneNumberType.PERSONAL_NUMBER; 50 | types['PAGER'] = i18n.phonenumbers.PhoneNumberType.PAGER; 51 | 52 | 53 | /** 54 | * Used for 'Universal Access Numbers' or 'Company Numbers'. 55 | * They may be further routed to specific offices, but allow one number to be used for a company. 56 | */ 57 | types['UAN'] = i18n.phonenumbers.PhoneNumberType.UAN; 58 | 59 | 60 | /** 61 | * Used for 'Voice Mail Access Numbers'. 62 | */ 63 | types['VOICEMAIL'] = i18n.phonenumbers.PhoneNumberType.VOICEMAIL; 64 | 65 | 66 | /** 67 | * A phone number is of type UNKNOWN when it does not fit any of the known patterns for a specific region. 68 | */ 69 | types['UNKNOWN'] = i18n.phonenumbers.PhoneNumberType.UNKNOWN; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18n-phonenumbers", 3 | "version": "0.704.0", 4 | "description": "Parse, format, and validate international phone numbers through Google's libphonenumber", 5 | "main": "i18n.phonenumbers.min.js", 6 | "author": { 7 | "name": "Leo Di Donato", 8 | "email": "leodidonato@gmail.com", 9 | "url": "http://git.io/leodido" 10 | }, 11 | "license": "Apache-2.0", 12 | "repository": { 13 | "type": "git", 14 | "url": "http://github.com/leodido/i18n.phonenumbers.js.git" 15 | }, 16 | "homepage": "http://github.com/leodido/i18n.phonenumbers.js", 17 | "devDependencies": { 18 | "bower": "~1.3.12", 19 | "calcdeps": "0.2.0", 20 | "del": "^1.0.0", 21 | "gulp": "~3.8.10", 22 | "gulp-bump": "^0.1.13", 23 | "gulp-closure-compiler": "~0.2.17", 24 | "gulp-debug": "^2.0.0", 25 | "gulp-filter": "^2.0.2", 26 | "gulp-gjslint": "^0.1.4", 27 | "gulp-help": "^1.3.1", 28 | "gulp-rename": "^1.2.0", 29 | "gulp-replace": "^0.5.3", 30 | "gulp-template": "^3.0.0", 31 | "gulp-util": "^3.0.4", 32 | "minimist": "^1.1.0", 33 | "run-sequence": "^1.0.2" 34 | }, 35 | "scripts": { 36 | "postinstall": "./node_modules/.bin/bower install", 37 | "release": "./node_modules/.bin/gulp build -t full && ./node_modules/.bin/gulp build -t lite", 38 | "release-eurozone19": "./node_modules/.bin/gulp countrybuild -c at,be,cy,ee,fi,fr,de,gr,ie,it,lv,lt,lu,mt,nl,pt,sk,si,es -p eurozone19", 39 | "release-schengen26": "./node_modules/.bin/gulp countrybuild -c at,be,cz,dk,ee,fi,fr,de,gr,hu,is,it,lv,li,lt,lu,mt,nl,no,pl,pt,sk,si,es,se,ch -p schengen26", 40 | "release-eu28": "./node_modules/.bin/gulp countrybuild -c at,be,bg,hr,cy,cz,dk,ee,fi,fr,de,gr,hu,ie,it,lv,lt,lu,mt,nl,pl,pt,ro,sk,si,es,se,gb -p eu28", 41 | "release-eu-extended": "./node_modules/.bin/gulp countrybuild -c at,be,bg,hr,cy,cz,dk,ee,fi,fr,de,gr,hu,ie,it,lv,lt,lu,mt,nl,pl,pt,ro,sk,si,es,se,gb,gf,gp,mq,yt,re,mf,gi,ax,tr,is,me,rs,mk,al,ba,ad,mc,sm,va -p eu.extended", 42 | "release-efta": "./node_modules/.bin/gulp countrybuild -c is,li,no,ch -p efta", 43 | "release-eea": "./node_modules/.bin/gulp countrybuild -c at,be,bg,hr,cy,cz,dk,ee,fi,fr,de,gr,hu,is,ie,it,lv,li,lt,lu,mt,nl,no,pl,pt,ro,sk,si,es,se,ch,gb -p eea", 44 | "release-nordiccouncil": "./node_modules/.bin/gulp countrybuild -c dk,fi,is,no,es -p nordiccouncil", 45 | "release-bsec12": "./node_modules/.bin/gulp countrybuild -c al,am,az,bg,ge,gr,md,ro,ru,tr,ua,rs -p bsec12", 46 | "release-visegradgroup": "./node_modules/.bin/gulp countrybuild -c hu,pl,cz,sk -p visegradgroup", 47 | "release-cefta": "./node_modules/.bin/gulp countrybuild -c rs,me,md,al,mk,ba -p cefta", 48 | "release-balticassembly": "./node_modules/.bin/gulp countrybuild -c ee,lv,lt -p balticassembly", 49 | "release-guam": "./node_modules/.bin/gulp countrybuild -c ge,az,md,ua -p guam", 50 | "release-benelux": "./node_modules/.bin/gulp countrybuild -c bg,nl,lu -p benelux", 51 | "release-eucouncil": "./node_modules/.bin/gulp countrybuild -c ch,is,li,no,se,fi,dk,hu,pl,cz,sk,bg,nl,lu,ee,lv,lt,cy,ie,gb,al,am,az,bg,ge,gr,md,ro,ru,tr,ua,rs,mk,ba,me,hr,mc,ad,sm,it,es,fr,pt,de,mt,si,at -p eucouncil", 52 | "test": "./node_modules/.bin/gulp build -t test" 53 | }, 54 | "keywords": [ 55 | "phone", 56 | "telephone", 57 | "phone number", 58 | "mobile phone", 59 | "i18n", 60 | "validator", 61 | "formatter" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /demo/getexample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example phone number generator 6 | 7 | 8 | 9 | 10 |

Example phone number generator

11 | 12 |
13 |

14 | 19 |

20 | 21 | 22 |
23 | Output 24 |
25 |

 26 |   
27 | 28 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/filter/metadata.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.i18n.MetadataFilter'); 4 | 5 | goog.require('i18n.phonenumbers.metadata'); 6 | 7 | 8 | 9 | /** 10 | * MetadataFilter constructor 11 | * 12 | * @param {!Array.} countries 13 | * @constructor 14 | */ 15 | var MetadataFilter = function(countries) { 16 | // Obtain original country to region map and all country codes 17 | var originalMap = i18n.phonenumbers.metadata.countryCodeToRegionCodeMap, 18 | allCountries = []; 19 | for (var countryCallingCode in originalMap) { 20 | if (originalMap.hasOwnProperty(countryCallingCode)) { 21 | Array.prototype.push.apply(allCountries, originalMap[~~countryCallingCode]); 22 | } 23 | } 24 | // Unique the country codes 25 | allCountries = allCountries.filter(function uniq(value, index, that) { 26 | return that.indexOf(value) === index; 27 | }); 28 | // Obtain original metadata for countries 29 | /** 30 | * @type {!Object.} 31 | */ 32 | var originalMetadata = i18n.phonenumbers.metadata.countryToMetadata; 33 | // Verify the input country codes 34 | countries = (countries.constructor === Array ? countries : []); 35 | if (countries.length === 0) { 36 | throw new TypeError('An array with almost one country code is required'); 37 | } 38 | for (var i = 0; i < countries.length; i++) { 39 | var countryCode = countries[i]; 40 | if (typeof countryCode !== 'string') { 41 | throw new TypeError('Parameter must be an array of strings'); 42 | } 43 | if (allCountries.indexOf(countryCode.toUpperCase()) === -1) { 44 | throw new Error('Country "' + countryCode.toUpperCase() + '" is invalid'); 45 | } 46 | } 47 | /** 48 | * Filtered country code to region code map 49 | * 50 | * @type {!Object.>} 51 | */ 52 | var newCountryCodeToRegionCodeMap = {}; 53 | /** 54 | * Filtered mapping from a region code to the PhoneMetadata for that region 55 | * 56 | * @type {!Object.} 57 | */ 58 | var newMetadata = {}; 59 | 60 | // Helpers 61 | /** 62 | * Filter country code to region code map 63 | * 64 | * @private 65 | */ 66 | var getCountryCodeToRegionCodeMap = function() { 67 | for (var i = 0; i < countries.length; i++) { 68 | /** 69 | * @type {!string} 70 | */ 71 | var countryCode = countries[i].toUpperCase(); 72 | for (var countryCallingCode in originalMap) { 73 | if (originalMap.hasOwnProperty(countryCallingCode)) { 74 | countryCallingCode = ~~countryCallingCode; 75 | var regionCodes = originalMap[countryCallingCode]; 76 | if (regionCodes.indexOf(countryCode) !== -1) { 77 | if (typeof newCountryCodeToRegionCodeMap[countryCallingCode] === 'undefined') { 78 | newCountryCodeToRegionCodeMap[countryCallingCode] = [countryCode]; 79 | } else { 80 | newCountryCodeToRegionCodeMap[countryCallingCode].push(countryCode); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | }; 87 | /** 88 | * Filter metadata 89 | * 90 | * @private 91 | */ 92 | var getMetadata = function() { 93 | for (var countryCallingCode in newCountryCodeToRegionCodeMap) { 94 | if (newCountryCodeToRegionCodeMap.hasOwnProperty(countryCallingCode)) { 95 | var regionCodes = newCountryCodeToRegionCodeMap[~~countryCallingCode]; 96 | for (var j = 0; j < regionCodes.length; j++) { 97 | var regionCode = regionCodes[j]; 98 | var key = regionCode; 99 | // Handle "001" special case 100 | if (regionCode === '001') { 101 | key = countryCallingCode; 102 | } 103 | if (typeof originalMetadata[key] !== 'undefined') { 104 | newMetadata[key] = originalMetadata[key]; 105 | } 106 | } 107 | } 108 | } 109 | }; 110 | 111 | // Execution 112 | getCountryCodeToRegionCodeMap(); 113 | getMetadata(); 114 | 115 | // Privileged 116 | return { 117 | /** 118 | * @nocollapse 119 | */ 120 | map: newCountryCodeToRegionCodeMap, 121 | /** 122 | * @nocollapse 123 | */ 124 | data: newMetadata 125 | }; 126 | }; 127 | 128 | goog.exportSymbol('leodido.i18n.MetadataFilter', MetadataFilter); 129 | -------------------------------------------------------------------------------- /demo/genmeta.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Metadata generator 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 |
22 |
23 |
24 | For which countries do you want to generate metadata? 25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | i18n phone numbers 2 | ================== 3 | 4 | **Parse, format, and validate international phone numbers through Google's libphonenumber**. 5 | 6 | [![Bower](https://img.shields.io/bower/v/i18n-phonenumbers.svg?style=flat-square)](http://github.com/leodido/i18n.phonenumbers.js/releases/latest) [![License](https://img.shields.io/badge/license-apache--2.0-yellowgreen.svg?style=flat-square)](http://opensource.org/licenses/Apache-2.0) 7 | 8 | This repository provides an already compiled JavaScript library aimed to parse, to format and to validate international telephone numbers. It wraps Google's [libphonenumber](https://github.com/googlei18n/libphonenumber) library. 9 | 10 | There are two main files: 11 | 12 | 1. library with **full metadata** (i.e. **i18n.phonenumbers.min.js**) 13 | 2. library with **lite metadata**, that lacks example phone numbers (i.e. **lite.i18n...**) 14 | 15 | However, **other versions** (smaller) **of the library can be built restricting countries metadata**. E.g., 16 | 17 | * library containing only europe (extended) metadata 18 | * library containing only eurozone metadata 19 | * etc. etc. 20 | 21 | See [package.json](./package.json#L38-L51) for other available shortcut build scripts. 22 | 23 | Generally **you can build any version of the library** (see [below](#build) for details). 24 | 25 | This feature is very useful when your application needs **only the phone numbers of a specific country set** and you want to **save space**. 26 | 27 | Do you want to format and validate only italian phone numbers? 28 | 29 | Clone the repository, install its dependencies, and then run: 30 | 31 | ``` 32 | # gulp countrybuild --country=it 33 | ``` 34 | 35 | You'll get a file 10 times smaller than the full one. 36 | 37 | Demos 38 | ----- 39 | 40 | A demo demonstrating **i18n phone number parsing** is available [here](http://bit.ly/parse-phonenumbers). 41 | 42 | You don't know Belize's phone numbers? No hassle. At this [link](http://bit.ly/phonenumbers-generator) you can **generate example phone numbers** for each country in the world! 43 | 44 | 45 | Differences from other forks/wrappers 46 | ------------------------------------- 47 | 48 | 1. Built-in integration with Google Closure (compiler and library) 49 | 2. Automated fetch (via bower) of the Google's libphonenumber library 50 | 3. Fully automated build system 51 | 4. Support for various build types (e.g., with full metadata, with country's specific metadata) 52 | 5. Easy to maintain, simple to upgrade 53 | 54 | Install 55 | ------- 56 | 57 | Install it via `bower`. 58 | 59 | ``` 60 | # bower install i18n-phonenumbers 61 | ``` 62 | 63 | Otherwise you can simply grab `*.i18n.phonenumbers.min.js` file/s or use [rawgit](https://rawgit.com). 64 | 65 | ```html 66 | 67 | ``` 68 | 69 | Overview 70 | -------- 71 | 72 | Exported on `leodido.i18n.PhoneNumbers` object: 73 | 74 | * `getNumberType(phoneNumber, regionCode)` 75 | 76 | Retrieves the type of the given number (with region code). Distinguishes between more than 10 types of phone numbers (fixed line, mobile, VOIP, etc.). 77 | 78 | * `getRegionCodeForNumber(phoneNumber, regionCode)` 79 | 80 | Retrieves the region where a phone number is from 81 | 82 | * `formatNumber(phoneNumber, regionCode, format)` 83 | 84 | Formats a phone number for a region to a specific format (i.e., E164, international, national, or RFC3966). 85 | 86 | * `formatOriginal(phoneNumber, regionCode)` 87 | 88 | Formats a phone number using the original phone number format that the number is parsed from. 89 | 90 | * `formatOutOfCountryCalling(regionCallingFrom, phoneNumber, regionCode)` 91 | 92 | Formats a phone number for out-of-country dialing purposes. 93 | 94 | * `formatMobileDialing(regionCallingFrom, phoneNumber, regionCode)` 95 | 96 | Returns a phone number formatted in such a way that it can be dialed from a mobile phone in a specific region. 97 | 98 | * `formatNationalWithCarrierCode(carrierCode, phoneNumber, regionCode)` 99 | 100 | Formats a phone number in national format for dialing using the carrier. 101 | 102 | * `formatNationalWithPreferredCarrierCode(fallbackCarrierCode, phoneNumber, regionCode)` 103 | 104 | Formats a phone number in national format for dialing using the preferred domestic carrier code (or a fallback carrier code if the preferred is missing). 105 | 106 | * `getExampleNumber(regionCode, type, format)` 107 | 108 | Retrieves an example phone number of the given type (fixed line, mobile, etc.) for the specified region and formats it into the provided format (or fallbacks to E164 format). 109 | 110 | * `isValidNumber(phoneNumber, regionCode)` 111 | 112 | Full validates a phone number for a region using length and prefix information. 113 | 114 | * `isValidNumberForRegion(phoneNumber, regionCode)` 115 | 116 | Full verifies whether a phone number is valid for a certain region or not. 117 | 118 | * `isPossibleNumber(phoneNumber, regionCode)` 119 | 120 | Quickly guesses whether a phone number is possible by using only the length information (faster then full validation). 121 | 122 | * `isPossibleNumberWithReason(phoneNumber, regionCode)` 123 | 124 | As above but gives us additional info about the provided phone number. 125 | 126 | The exported object `leodido.i18n.AsYouTypeFormatter` is a proxy to Google's `i18n.phonenumbers.AsYouTypeFormatter`. It can be used to format phone numbers on-the-fly when users enter each digit, with this functions: 127 | 128 | * `inputDigit(nextChar)` 129 | 130 | * `inputDigitAndRememberPosition(nextChar)` 131 | 132 | * `getRememberedPosition()` 133 | 134 | 135 | #### Notes 136 | 137 | - Unless otherwise specified the `regionCode` always defaults to 'US'. 138 | 139 | 140 | #### Examples 141 | 142 | In the **demo** directory you can find examples covering phone number validation, formatting (as you type or one shot) and generation. 143 | 144 | Update 145 | ------ 146 | 147 | Have googlers committed new metadata or something else? 148 | 149 | No problem, ping me ([@leodido](https://twitter.com/leodido)) and I will update it to the last release of Google's libphonenumber. 150 | 151 | Or simply **DIY**. 152 | 153 | 1. Clone this repo 154 | 155 | ``` 156 | # git clone git@github.com:leodido/ni18n.phonenumbers.js.git 157 | # cd i18n.phonenumbers.js/ 158 | ``` 159 | 160 | 2. Install it locally (you will need `npm`, of course) ... 161 | 162 | ``` 163 | # npm install 164 | ``` 165 | 166 | 3. Build it against the last grabbed release of libphonenumber (see below) and **make me a PR**. I'll be happy to merge it. 167 | 168 | Makefile contains helpers to upgrade the library. 169 | 170 | Build 171 | ----- 172 | 173 | Build is handled through [Gulp](https://github.com/gulpjs/gulp/) and performed mainly via [Google Closure Compiler](https://github.com/google/closure-compiler). 174 | 175 | Need help? Run `gulp help` ! 176 | 177 | ``` 178 | # Usage 179 | # gulp [task] 180 | # 181 | # Available tasks 182 | # build Build the library from source files 183 | # --target=full|lite|bycountry|test Files to use 184 | # bump Bump version up for a new release 185 | # --level=major|minor|patch|prerelease Version level to increment 186 | # clean Clean build 187 | # --target=full|lite|bycountry|test Files to use 188 | # countrybuild Build a library that supports only specified countries 189 | # --country=it,es,fr,de,.. Comma separated list of ISO 3166-1 alpha-2 country codes 190 | # --prefix=... If specifiec the output file name will be .i18n.phonenumbers.min. 191 | # help Display this help text 192 | # lint Lint JS source files 193 | # --target=full|lite|bycountry|test Files to use 194 | # version Print the library version 195 | ``` 196 | 197 | To build a new version (both full and lite) of the JavaScript library: 198 | 199 | ``` 200 | # npm run release 201 | ``` 202 | 203 | Or 204 | 205 | ``` 206 | # make release 207 | ``` 208 | 209 | Known issues 210 | ------------ 211 | 212 | * Target `test` needs to be fixed 213 | 214 | * Missing country codes: 215 | 216 | * **XK** (Kosovo) -> should be added to targets: `release-eu-extended`, `release-cefta` 217 | 218 | * **IC** (Canary Islands) -> should be added to targets: `release-eu-extended` 219 | 220 | * **EA** (Ceuta and Melilla) -> should be added to targets: `release-eu-extended` 221 | 222 | --- 223 | 224 | [![Analytics](https://ga-beacon.appspot.com/UA-49657176-1/i18n.phonenumbers.js)](https://github.com/igrigorik/ga-beacon) 225 | -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Phone number parser demo 4 | 5 | 6 | 7 | 8 | 9 |

Phone number parser demo

10 | 11 |
12 |

13 | 16 |

17 |

18 | 21 |

22 |

23 | 26 |

27 | 28 | 29 | 30 |
31 | Output 32 |
33 |

 34 | 
 35 |   
36 | 37 | 215 | 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'), 4 | debug = require('gulp-debug'), 5 | filter = require('gulp-filter'), 6 | replace = require('gulp-replace'), 7 | ccompiler = require('gulp-closure-compiler'), 8 | gjslint = require('gulp-gjslint'), 9 | bump = require('gulp-bump'), 10 | del = require('del'), 11 | calcdeps = require('calcdeps'), 12 | sequence = require('run-sequence'), 13 | gutil = require('gulp-util'), 14 | template = require('gulp-template'), 15 | rename = require('gulp-rename'), 16 | minimist = require('minimist'), 17 | path = require('path'), 18 | knownArgs = { 19 | 'string': ['target', 'level', 'country', 'prefix'], 20 | 'default': { 21 | target: 'full', 22 | level: 'patch' 23 | }, 24 | 'alias': { t: 'target', l: 'level', c: 'country', p: 'prefix' } 25 | }, 26 | metadataGenerated = false; 27 | 28 | 29 | /** 30 | * @typedef {Object} GulpArguments 31 | * @property {!string} level 32 | * @property {!string} target 33 | * @property {!string} country 34 | */ 35 | var GulpArguments; 36 | 37 | 38 | /** @type {GulpArguments} */ 39 | var args = minimist(process.argv.slice(2), knownArgs), 40 | allowedLevels = ['major', 'minor', 'patch', 'prerelease'], 41 | allowedTargets = ['full', 'lite', 'bycountry', 'test'], 42 | targetsHelp = { options: {} }, 43 | countryHelp = { options: {} }, 44 | levelsHelp = { options: {} }; 45 | var getLevel = function() { 46 | if (allowedLevels.indexOf(args.level) === -1) { 47 | args.level = knownArgs.default.level; 48 | } 49 | return args.level; 50 | }; 51 | var getTarget = function() { 52 | if (allowedTargets.indexOf(args.target) === -1) { 53 | args.target = knownArgs.default.target; 54 | } 55 | return args.target; 56 | }; 57 | var getCountries = function() { 58 | if (typeof args.country === 'string') { 59 | return args.country.split(',').map(Function.prototype.call, String.prototype.trim); 60 | } 61 | return []; 62 | }; 63 | var getPrefix = function() { 64 | if (typeof args.prefix === 'string') { 65 | return args.prefix + '.'; 66 | } 67 | return getCountries().join('.') + '.'; 68 | }; 69 | levelsHelp.options['level=' + allowedLevels.join('|')] = 'Version level to increment'; 70 | targetsHelp.options['target=' + allowedTargets.join('|')] = 'Files to use'; 71 | countryHelp.options['country=it,es,fr,de,..'] = 'Comma separated list of ISO 3166-1 alpha-2 country codes'; 72 | countryHelp.options['prefix=...'] = 'It specifies the output file name will be .i18n.phonenumbers.min.js'; 73 | 74 | 75 | /** 76 | * @typedef {*} PackageJson 77 | * @property {!string} name 78 | * @property {!string} version 79 | * @property {!string} main 80 | */ 81 | var PackageJson; 82 | 83 | 84 | /** @type {PackageJson} */ 85 | var bundle = require('./package.json'); 86 | 87 | // Redefine gulp to support help 88 | gulp = require('gulp-help')(gulp, { 89 | description: 'Display this help text' 90 | }); 91 | 92 | 93 | /** 94 | * @type {{ 95 | * compiler: string, 96 | * library: string, 97 | * output: {full: (!string|*), lite: string, test: string}, 98 | * sources: string[], 99 | * dist: !string, 100 | * bycountry: Array, 101 | * tests: Array, 102 | * metadata: {full: string, lite: string, test: string, bycountry: string}, 103 | * deps: Array 104 | * }} 105 | */ 106 | var settings = { 107 | compiler: 'bower_components/closure-compiler/compiler.jar', 108 | library: 'bower_components/closure-library/', 109 | output: { 110 | full: bundle.main, 111 | lite: 'lite.' + bundle.main, 112 | bycountry: 'country.metadata.tmpl', 113 | test: 'test.' + bundle.main 114 | }, 115 | sources: [ 116 | 'bower_components/libphonenumber/javascript/i18n/phonenumbers/phonemetadata.pb.js', 117 | 'bower_components/libphonenumber/javascript/i18n/phonenumbers/phonenumber.pb.js', 118 | 'bower_components/libphonenumber/javascript/i18n/phonenumbers/phonenumberutil.js', 119 | 'bower_components/libphonenumber/javascript/i18n/phonenumbers/asyoutypeformatter.js', 120 | 'bower_components/libphonenumber/javascript/i18n/phonenumbers/regioncodefortesting.js', 121 | 'src/error.js', 122 | 'src/validation.result.js', 123 | 'src/type.js', 124 | 'src/format.js', 125 | 'src/phonenumbers.js', 126 | 'src/asyoutypeformatter.js' 127 | ], 128 | dist: './dist', 129 | bycountry: [ 130 | 'src/filter/metadata.js' 131 | ], 132 | tests: [], 133 | metadata: { 134 | full: 'bower_components/libphonenumber/javascript/i18n/phonenumbers/metadata.js', 135 | lite: 'bower_components/libphonenumber/javascript/i18n/phonenumbers/metadatalite.js', 136 | bycountry: 'bower_components/libphonenumber/javascript/i18n/phonenumbers/metadata.js', 137 | test: 'bower_components/libphonenumber/javascript/i18n/phonenumbers/metadatafortesting.js' 138 | }, 139 | deps: [] 140 | }; 141 | // NOTE: order matter 142 | 143 | 144 | /** 145 | * @type {string[]} 146 | * // NOTE: test files need source files 147 | */ 148 | settings.tests = settings.sources.concat([ 149 | 'bower_components/libphonenumber/javascript/i18n/phonenumbers/regioncodefortesting.js', 150 | 'bower_components/libphonenumber/javascript/i18n/phonenumbers/phonenumberutil_test.js', // FIXME 151 | 'bower_components/libphonenumber/javascript/i18n/phonenumbers/asyoutypeformatter_test.js' 152 | ]); 153 | 154 | gulp.task('lint', 'Lint JS source files', [], function() { 155 | var src = { 156 | full: settings.sources, 157 | lite: settings.sources, 158 | bycountry: settings.bycountry, 159 | test: settings.tests 160 | }, 161 | pattern = { 162 | full: ['*asyoutypeformatter*', '*phonenumberutil*'], 163 | lite: ['*asyoutypeformatter*', '*phonenumberutil*'], 164 | bycountry: [], 165 | test: ['*asyoutypeformatter*', '*phonenumberutil*', '*testing*'] 166 | }, 167 | lintOptions = { 168 | flags: [ 169 | '--flagfile=gjslint.conf' 170 | ] 171 | }; 172 | // NOTE: metadata files not linted 173 | return gulp.src(src[getTarget()]) 174 | .pipe(debug({ title: 'Lint'})) 175 | .pipe(filter(pattern[getTarget()])) 176 | .pipe(gjslint(lintOptions)) 177 | .pipe(gjslint.reporter('console'), { fail: true }); 178 | }, targetsHelp); 179 | 180 | // Sync task 181 | gulp.task('deps', false, ['lint'], function(done) { 182 | var src = { 183 | full: settings.sources, 184 | lite: settings.sources, 185 | bycountry: settings.bycountry, 186 | test: settings.tests 187 | }, 188 | options = { deps: [], exclude: [] }, 189 | source = src[getTarget()], 190 | metadata = [metadataGenerated || settings.metadata[getTarget()]]; 191 | 192 | options.input = metadata.concat(source); 193 | options.path = [settings.library]; 194 | options.output_mode = 'list'; 195 | 196 | calcdeps.calcdeps(options, function(err, results) { 197 | if (err) { 198 | gutil.log(gutil.colors.red(err)); 199 | process.exit(1); 200 | } else { 201 | settings.deps = results; 202 | for (var i = 0; i < settings.deps.length; i++) { 203 | gutil.log('Dep #' + (i + 1), gutil.colors.green(settings.deps[i])); 204 | } 205 | done(); 206 | } 207 | }); 208 | }); 209 | 210 | gulp.task('compile', false, ['deps'], function() { 211 | var target = getTarget(), 212 | wrapper = '(function(){%output%}).call(window);', //'(function(){%output%})();', 213 | level = 'VERBOSE', 214 | errors = ['accessControls', 'ambiguousFunctionDecl', 'checkDebuggerStatement', 'checkRegExp', 215 | 'checkTypes', 'checkVars', 'const', 'constantProperty', 'deprecated', 'duplicate', 'duplicateMessage', 216 | 'es5Strict', 'externsValidation', 'fileoverviewTags', 'globalThis', 'internetExplorerChecks', 'invalidCasts', 217 | 'misplacedTypeAnnotation', 'missingProperties', 'nonStandardJsDocs', 'strictModuleDepCheck', 'suspiciousCode', 218 | 'typeInvalidation', 'undefinedNames', 'undefinedVars', 'unknownDefines', 'uselessCode', 'visibility']; 219 | 220 | if (getTarget() === allowedTargets[2]) { 221 | wrapper = require('fs').readFileSync('src/template/country.metadata.tmpl', 'utf-8'); 222 | } 223 | 224 | var outputFile = settings.output[target]; 225 | if (metadataGenerated) { 226 | outputFile = getPrefix() + outputFile; 227 | } 228 | outputFile = path.join(settings.dist, outputFile); 229 | 230 | var cflags = { 231 | compilation_level: 'ADVANCED_OPTIMIZATIONS', 232 | language_in: 'ECMASCRIPT5_STRICT', 233 | generate_exports: true, 234 | output_wrapper: wrapper, // NOTE: IIFE 235 | warning_level: level, 236 | jscomp_error: errors 237 | }; 238 | return gulp.src(settings.deps) 239 | .pipe(debug({title: 'Input'})) 240 | .pipe(ccompiler({ 241 | compilerPath: settings.compiler, 242 | fileName: outputFile, 243 | compilerFlags: cflags 244 | })) 245 | .pipe(gulp.dest('./')); 246 | }); 247 | 248 | // Strip multiline comments 249 | gulp.task('rm-ml-comments', false, [], function() { 250 | var sourceFile = settings.output[getTarget()]; 251 | if (metadataGenerated) { 252 | sourceFile = getPrefix() + sourceFile; 253 | } 254 | sourceFile = path.join(settings.dist, sourceFile); 255 | 256 | return gulp.src([sourceFile]) 257 | .pipe(debug({ title: 'Removing multiline comments from' })) 258 | .pipe(replace(/(?:\/\*(?:[\s\S]*?)\*\/)/g, '')) 259 | .pipe(gulp.dest(settings.dist)); 260 | }); 261 | 262 | gulp.task('build', 'Build the library from source files', [], function(cb) { 263 | sequence('clean', 'compile', 'rm-ml-comments', cb); 264 | }, targetsHelp); 265 | 266 | gulp.task('clean', 'Clean build', [], function(cb) { 267 | var target = getTarget(); 268 | var files = ['npm-debug.log']; 269 | files.push(path.join(settings.dist, settings.output[target])); 270 | for (var key in settings.output) { 271 | if (settings.output.hasOwnProperty(key)) { 272 | var fname = path.join(settings.dist, settings.output[key]); 273 | if (key !== target) { 274 | files.push('!' + fname); 275 | } 276 | } 277 | } 278 | del(files, cb); 279 | }, targetsHelp); 280 | 281 | gulp.task('version', 'Print the library version', [], function() { 282 | return gutil.log('Library', gutil.colors.magenta(bundle.name) + ',', gutil.colors.magenta(bundle.version)); 283 | }); 284 | 285 | gulp.task('bump', 'Bump version up for a new release', [], function() { 286 | return gulp.src(['./bower.json', './package.json']) 287 | .pipe(bump({ type: getLevel() })) 288 | .pipe(gulp.dest('./')); 289 | }, levelsHelp); 290 | 291 | gulp.task('gen-metadata', false, [], function() { 292 | var countryCodes = getCountries(); 293 | var metadataFileName = getPrefix() + 'metadata.js'; 294 | var outputDirectory = './src'; 295 | var stream = gulp.src(path.join(settings.dist, settings.output.bycountry)) 296 | .pipe(template({ 297 | namespace: 'i18n.phonenumbers.metadata', 298 | countries: countryCodes, 299 | mapJsdoc: '/**\n* A mapping from a country calling code to the region codes which denote the\n* region represented by that country calling code. In the case of multiple\n* countries sharing a calling code, such as the NANPA regions, the one\n* indicated with "isMainCountryForCode" in the metadata should be first.\n* @type {!Object.>}\n*/', 300 | dataJsdoc: '/**\n* A mapping from a region code to the PhoneMetadata for that region.\n* @type {!Object.}\n*/' 301 | })) 302 | .pipe(rename(metadataFileName)) 303 | .pipe(gulp.dest(outputDirectory)); 304 | metadataGenerated = path.join(outputDirectory, metadataFileName); 305 | return stream; 306 | }, countryHelp); 307 | 308 | gulp.task('reset-target', false, [], function(cb) { 309 | args.target = allowedTargets[0]; // enforce target 'full' 310 | cb(); 311 | }); 312 | 313 | gulp.task('del-metadata', false, [], function(cb) { 314 | var metadataFile = path.join('./src', getPrefix() + 'metadata.js'); 315 | del(metadataFile, cb); 316 | }); 317 | 318 | gulp.task('countrybuild', 'Build a library that supports only specified countries', [], function(cb) { 319 | var countryCodes = getCountries(); 320 | if (countryCodes.length > 0) { 321 | args.target = allowedTargets[2]; // enforce target 'bycountry' 322 | // 1 * compile metadata template 323 | // 2 * generate metadata from compiled template 324 | // 3 * remove compiled template 325 | // 4 * build a full version of the library with only the generated metadata 326 | sequence('build', 'gen-metadata', 'clean', 'reset-target', 'compile', 'rm-ml-comments', 'del-metadata', cb); 327 | } else { 328 | gutil.log(gutil.colors.magenta('ERROR:'), 'almost one valid country code needed.'); 329 | } 330 | }, countryHelp); 331 | -------------------------------------------------------------------------------- /src/phonenumbers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.i18n.PhoneNumbers'); 4 | 5 | goog.require('i18n.phonenumbers.PhoneNumberUtil'); 6 | goog.require('leodido.i18n.PhoneNumbers.ERROR'); 7 | goog.require('i18n.phonenumbers.PhoneNumberFormat'); 8 | goog.require('leodido.i18n.PhoneNumbers.FORMAT'); 9 | goog.require('i18n.phonenumbers.PhoneNumberType'); 10 | goog.require('leodido.i18n.PhoneNumbers.TYPE'); 11 | goog.require('i18n.phonenumbers.PhoneNumberUtil.ValidationResult'); 12 | goog.require('leodido.i18n.PhoneNumbers.RESULT'); 13 | 14 | var phoneNumberUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance(); 15 | 16 | 17 | 18 | /** 19 | * @constructor 20 | */ 21 | var PhoneNumbers = function() {}; 22 | 23 | 24 | /** 25 | * Get the type of the given number. 26 | * 27 | * @param {string} phoneNumber 28 | * @param {?string} regionCode 29 | * @return {?string} 30 | * @throws {i18n.phonenumbers.PhoneNumberType} if the string is not considered to be a 31 | * viable phone number or if no default region was supplied. 32 | */ 33 | function getNumberType(phoneNumber, regionCode) { 34 | regionCode = regionCode || 'us'; 35 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 36 | return phoneNumberUtil.getNumberType(number); 37 | } 38 | 39 | 40 | /** 41 | * Return the region where a phone number is from. 42 | * 43 | * @param {string} phoneNumber 44 | * @param {?string} regionCode 45 | * @return {?string} 46 | * @throws {i18n.phonenumbers.Error} if the string is not considered to be a 47 | * viable phone number or if no default region was supplied. 48 | */ 49 | function getRegionCodeForNumber(phoneNumber, regionCode) { 50 | regionCode = regionCode || 'us'; 51 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 52 | return phoneNumberUtil.getRegionCodeForNumber(number); 53 | } 54 | 55 | 56 | /** 57 | * Format a phone number 58 | * 59 | * @param {string} phoneNumber 60 | * @param {?string} regionCode 61 | * @param {?i18n.phonenumbers.PhoneNumberFormat} format 62 | * @return {string} 63 | * @throws {i18n.phonenumbers.Error} if the string is not considered to be a 64 | * viable phone number or if no default region was supplied. 65 | */ 66 | function formatNumber(phoneNumber, regionCode, format) { 67 | regionCode = regionCode || 'us'; 68 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 69 | format = (typeof format == 'undefined') ? i18n.phonenumbers.PhoneNumberFormat.E164 : format; 70 | return phoneNumberUtil.format(number, format); 71 | } 72 | 73 | 74 | /** 75 | * Formats a phone number using the original phone number format that the number is parsed from. 76 | * 77 | * @param {string} phoneNumber 78 | * @param {?string} regionCode 79 | * @return {string} 80 | * @throws {i18n.phonenumbers.Error} if the string is not considered to be a 81 | * viable phone number or if no default region was supplied. 82 | */ 83 | function formatOriginal(phoneNumber, regionCode) { 84 | regionCode = regionCode || 'us'; 85 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 86 | return phoneNumberUtil.formatInOriginalFormat(number, regionCode); 87 | } 88 | 89 | 90 | /** 91 | * Formats a phone number for out-of-country dialing purposes. 92 | * 93 | * @param {string} regionCallingFrom 94 | * @param {string} phoneNumber 95 | * @param {?string} regionCode 96 | * @return {?string} 97 | * @throws {i18n.phonenumbers.Error} if the string is not considered to be a 98 | * viable phone number or if no default region was supplied. 99 | */ 100 | function formatOutOfCountryCalling(regionCallingFrom, phoneNumber, regionCode) { 101 | regionCode = regionCode || 'us'; 102 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 103 | return phoneNumberUtil.formatOutOfCountryCallingNumber(number, regionCallingFrom); 104 | } 105 | 106 | 107 | /** 108 | * Return a phone number formatted in such a way that it can be dialed from a mobile phone in a specific region. 109 | * If the number cannot be reached from the region 110 | * (e.g. some countries block toll-free numbers from being called outside of the country), 111 | * the method launch an exception. 112 | * 113 | * @param {string} regionCallingFrom 114 | * @param {string} phoneNumber 115 | * @param {?string} regionCode 116 | * @return {?string} 117 | * @throws {i18n.phonenumbers.Error} if the string is not considered to be a 118 | * viable phone number or if no default region was supplied. 119 | * @throws {Error} if the number can not be reached from the region 120 | */ 121 | function formatMobileDialing(regionCallingFrom, phoneNumber, regionCode) { 122 | regionCode = regionCode || 'us'; 123 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 124 | var output = phoneNumberUtil.formatNumberForMobileDialing(number, regionCallingFrom, true); 125 | if (output === '') { 126 | throw new Error( 127 | 'Current phone number (i.e., "' + phoneNumber + 128 | '") can not be dialed from a mobile phone within the "' + 129 | regionCallingFrom.toUpperCase() + '" region' 130 | ); 131 | } 132 | return output; 133 | } 134 | 135 | 136 | /** 137 | * Formats a phone number in national format for dialing using the carrier. 138 | * The {@code carrierCode} will always be used regardless of whether the phone number already has a preferred domestic 139 | * carrier code stored. 140 | * If {@code carrierCode} contains an empty string, returns the number in national format without any carrier code. 141 | * 142 | * @param {string} carrierCode the carrier selection code to be used. 143 | * @param {string} phoneNumber the phone number to be formatted. 144 | * @param {?string} regionCode 145 | * @return {string} the formatted phone number in national format for dialing 146 | using the carrier as specified in the {@code carrierCode}. 147 | */ 148 | function formatNationalWithCarrierCode(carrierCode, phoneNumber, regionCode) { 149 | regionCode = regionCode || 'us'; 150 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 151 | return phoneNumberUtil.formatNationalNumberWithCarrierCode(number, carrierCode); 152 | } 153 | 154 | 155 | /** 156 | * Formats a phone number in national format for dialing using the carrier as specified in the 157 | * preferred_domestic_carrier_code field. If that is missing, use the {@code fallbackCarrierCode} 158 | * passed in instead. 159 | * If there is no {@code preferred_domestic_carrier_code}, and the {@code fallbackCarrierCode} contains an empty string, 160 | * return the number in national format without any carrier code. 161 | * 162 | * Use {@link #formatNationalWithCarrierCode} instead if the carrier code passed in should take precedence 163 | * over the number's {@code preferred_domestic_carrier_code} when formatting. 164 | * 165 | * @param {string} fallbackCarrierCode the carrier selection code to be used, 166 | * if none is found in the phone number itself. 167 | * @param {string} phoneNumber the phone number to be formatted. 168 | * @param {?string} regionCode 169 | * @return {string} the formatted phone number in national format for dialing 170 | * using the number's preferred_domestic_carrier_code, or the 171 | * {@code fallbackCarrierCode} passed in if none is found. 172 | */ 173 | function formatNationalWithPreferredCarrierCode(fallbackCarrierCode, phoneNumber, regionCode) { 174 | regionCode = regionCode || 'us'; 175 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 176 | return phoneNumberUtil.formatNationalNumberWithPreferredCarrierCode(number, fallbackCarrierCode); 177 | } 178 | 179 | 180 | /** 181 | * Get an example phone number for the given region code 182 | * 183 | * Default format, used when format not specified, is E164 format. 184 | * 185 | * @param {string} regionCode 186 | * @param {i18n.phonenumbers.PhoneNumberType} type 187 | * @param {?i18n.phonenumbers.PhoneNumberFormat} format 188 | * @return {?string} 189 | */ 190 | function getExampleNumber(regionCode, type, format) { 191 | var number = phoneNumberUtil.getExampleNumberForType(regionCode, type); 192 | if (!number) { 193 | return null; 194 | } 195 | format = (typeof format == 'undefined') ? i18n.phonenumbers.PhoneNumberFormat.E164 : format; 196 | return phoneNumberUtil.format(number, format); 197 | } 198 | 199 | 200 | /** 201 | * Check (lenient) whether a phone number is a possible number. 202 | * 203 | * @param {string} phoneNumber 204 | * @param {?string} regionCode 205 | * @return {boolean} 206 | * @throws {i18n.phonenumbers.Error} if the string is not considered to be a 207 | * viable phone number or if no default region was supplied. 208 | */ 209 | function isPossibleNumber(phoneNumber, regionCode) { 210 | regionCode = regionCode || 'us'; 211 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 212 | return phoneNumberUtil.isPossibleNumber(number); 213 | } 214 | 215 | 216 | /** 217 | * Check whether a phone number is a valid number (i.e., matches a valid pattern). 218 | * 219 | * @param {string} phoneNumber 220 | * @param {?string} regionCode 221 | * @return {boolean} 222 | * @throws {i18n.phonenumbers.Error} if the string is not considered to be a 223 | * viable phone number or if no default region was supplied. 224 | */ 225 | function isValidNumber(phoneNumber, regionCode) { 226 | regionCode = regionCode || 'us'; 227 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 228 | return phoneNumberUtil.isValidNumber(number); 229 | } 230 | 231 | 232 | /** 233 | * Verify whether a phone number is valid for a certain region. 234 | * Note this doesn't verify the number is actually in use, which is impossible to tell by just 235 | * looking at a number itself. 236 | * If the country calling code is not the same as the country calling code for the region, 237 | * this immediately exits with false. 238 | * After this, the specific number pattern rules for the region are examined. 239 | * This is useful for determining for example whether a particular number is 240 | * valid for Canada, rather than just a valid NANPA number. 241 | * Warning: In most cases, you want to use {@link #isValidNumber} instead. 242 | * For example, this method will mark numbers from British Crown dependencies such as the Isle of Man as invalid 243 | * for the region "GB" (United Kingdom), since it has its own region code, "IM", which may be undesirable. 244 | * 245 | * @param {string} phoneNumber the phone number that we want to validate. 246 | * @param {string} regionCode the region that we want to validate the phone number for. 247 | * @return {boolean} a boolean that indicates whether the number is of a valid pattern. 248 | */ 249 | function isValidNumberForRegion(phoneNumber, regionCode) { 250 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 251 | return phoneNumberUtil.isValidNumberForRegion(number, regionCode); 252 | } 253 | 254 | 255 | /** 256 | * Check (lenient) whether a phone number is a possible number. 257 | * If the phone number is not possible it gives us the reason. 258 | * 259 | * @param {string} phoneNumber 260 | * @param {?string} regionCode 261 | * @return {i18n.phonenumbers.PhoneNumberUtil.ValidationResult} 262 | * @throws {i18n.phonenumbers.Error} if the string is not considered to be a 263 | * viable phone number or if no default region was supplied. 264 | */ 265 | function isPossibleNumberWithReason(phoneNumber, regionCode) { 266 | regionCode = regionCode || 'us'; 267 | var number = phoneNumberUtil.parseAndKeepRawInput(phoneNumber, regionCode); 268 | return phoneNumberUtil.isPossibleNumberWithReason(number); 269 | } 270 | 271 | 272 | /** 273 | * Convenience method to get a list of what regions the library has metadata for. 274 | * @return {!Array.} region codes supported by the library. 275 | */ 276 | function getSupportedRegions() { 277 | return phoneNumberUtil.getSupportedRegions(); 278 | } 279 | 280 | 281 | goog.exportSymbol('leodido.i18n.PhoneNumbers', PhoneNumbers); 282 | goog.exportSymbol('leodido.i18n.PhoneNumbers.getNumberType', getNumberType); 283 | goog.exportSymbol('leodido.i18n.PhoneNumbers.getRegionCodeForNumber', getRegionCodeForNumber); 284 | goog.exportSymbol('leodido.i18n.PhoneNumbers.formatNumber', formatNumber); 285 | goog.exportSymbol('leodido.i18n.PhoneNumbers.formatOriginal', formatOriginal); 286 | goog.exportSymbol('leodido.i18n.PhoneNumbers.formatOutOfCountryCalling', formatOutOfCountryCalling); 287 | goog.exportSymbol('leodido.i18n.PhoneNumbers.formatMobileDialing', formatMobileDialing); 288 | goog.exportSymbol('leodido.i18n.PhoneNumbers.formatNationalWithCarrierCode', formatNationalWithCarrierCode); 289 | goog.exportSymbol( 290 | 'leodido.i18n.PhoneNumbers.formatNationalWithPreferredCarrierCode', 291 | formatNationalWithPreferredCarrierCode 292 | ); 293 | goog.exportSymbol('leodido.i18n.PhoneNumbers.getExampleNumber', getExampleNumber); 294 | goog.exportSymbol('leodido.i18n.PhoneNumbers.isValidNumber', isValidNumber); 295 | goog.exportSymbol('leodido.i18n.PhoneNumbers.isValidNumberForRegion', isValidNumberForRegion); 296 | goog.exportSymbol('leodido.i18n.PhoneNumbers.isPossibleNumber', isPossibleNumber); 297 | goog.exportSymbol('leodido.i18n.PhoneNumbers.isPossibleNumberWithReason', isPossibleNumberWithReason); 298 | goog.exportSymbol('leodido.i18n.PhoneNumbers.getSupportedRegions', getSupportedRegions); 299 | goog.exportSymbol('leodido.i18n.PhoneNumbers.RESULT', results); 300 | goog.exportSymbol('leodido.i18n.PhoneNumbers.TYPE', types); 301 | goog.exportSymbol('leodido.i18n.PhoneNumbers.ERROR', errors); 302 | goog.exportSymbol('leodido.i18n.PhoneNumbers.FORMAT', formats); 303 | 304 | // TODO: do a function that guess countries/regioncodes? 305 | -------------------------------------------------------------------------------- /dist/eu.extended.i18n.phonenumbers.min.js: -------------------------------------------------------------------------------- 1 | (function(){'use strict';var aa=this;function k(a){return"string"==typeof a}function l(a,b){var c=a.split("."),d=aa;c[0]in d||!d.execScript||d.execScript("var "+c[0]);for(var f;c.length&&(f=c.shift());)c.length||void 0===b?d[f]?d=d[f]:d=d[f]={}:d[f]=b}function n(a,b){function c(){}c.prototype=b.prototype;a.ja=b.prototype;a.prototype=new c;a.prototype.constructor=a;a.oa=function(a,c,e){for(var g=Array(arguments.length-2),h=2;hc?Math.max(0,a.length+c):c;if(k(a))return k(b)&&1==b.length?a.indexOf(b,c):-1;for(;cb?1:aa.length?!1:M(Ja,a)}function Ra(a){return M(Ha,a)?N(a,Aa):N(a,K)}function Sa(a){var b=Ra(a.toString());E(a);a.a(b)}function Ta(){return da(Object.keys(ba),function(a){return isNaN(a)})} 116 | function N(a,b){for(var c=new D,d,f=a.length,e=0;e=e&&e<=f;++e)if(d=parseInt(c.substring(0,e),10),d in p)return b.a(c.substring(e)),d;return 0} 126 | function lb(a,b,c,d,f){if(0==a.length)return 0;a=new D(a);var e;null!=b&&(e=x(b,11));null==e&&(e="NonMatch");var g=a.toString();if(0==g.length)e=20;else if(L.test(g))g=g.replace(L,""),E(a),a.a(Ra(g)),e=1;else{g=new RegExp(e);Sa(a);e=a.toString();if(0==e.search(g)){var g=e.match(g)[0].length,h=e.substring(g).match(Da);h&&null!=h[1]&&0=a.b.length)throw"Phone number too short after IDD";c= 127 | kb(a,c);if(0!=c)return w(f,1,c),c;throw"Invalid country calling code";}if(null!=b&&(e=y(b,10),g=""+e,h=a.toString(),0==h.lastIndexOf(g,0))){var m=new D(h.substring(g.length)),h=x(b,1),g=new RegExp(y(h,2));mb(m,b,null);b=m.toString();h=y(h,3);if(!M(g,a.toString())&&M(g,b)||3==hb(h,a.toString()))return c.a(b),d&&w(f,6,10),w(f,1,e),e}w(f,1,0);return 0} 128 | function mb(a,b,c){var d=a.toString(),f=d.length,e=x(b,15);if(0!=f&&null!=e&&0!=e.length&&(e=new RegExp("^(?:"+e+")"),f=e.exec(d))){var g=RegExp,h;h=x(b,1);h=y(h,2);g=new g(h);h=M(g,d);var m=f.length-1;b=x(b,16);if(null==b||0==b.length||null==f[m]||0==f[m].length){if(!h||M(g,d.substring(f[0].length)))null!=c&&0g.b.length)throw"The string supplied is too short to be a phone number";null!=b&&(a=new D,c=new D(g.toString()),mb(c,b,a),ib(b,c.toString())||(g=c,d&&w(e,7,a.toString())));d=g.toString();a=d.length;if(2>a)throw"The string supplied is too short to be a phone number";if(17=g){f=d;break}e=e.substring(0,g);e=N(e,K);if(0==e.length){f=d;break}h=h.clone();A(h,4);e=[h];h=y(c,1);d=R(c);h in p?(f=T(f,h,U(h)),g="",g=Za(e,d),null==g?g=d:(e=g.clone(),g=y(g,4),0