From 8570d35e5430172ec8a115ed3c818c234b8ce536 Mon Sep 17 00:00:00 2001 From: Adam Hooper Date: Fri, 21 Oct 2016 12:09:27 -0400 Subject: [PATCH] Use Intl.NumberFormat to format numbers --- README.md | 19 +++++++++++++++++++ index.js | 28 ++++++++++++++++++++++++---- package.json | 3 ++- test/index.js | 17 ++++++++++++++++- 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c2d6eac..eeacbe1 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,25 @@ polyglot.t("car", 2); => "2 cars" ``` +Interpolated `Number`s will be number-formatted according to the `locale`: + +```js +polyglot.t("num_cars", 2000); +=> "2,000 cars" +``` + +On a default Node install, this may only work in English. To format in +non-English locales (e.g., to output "2.000" in France or use other numerals), +compile Node with "full" ICU data or include the `full-icu` package in your +project: + +1. `npm install --save full-icu` +2. Run `node --full-data-dir=node_modules/full-icu` instead of just `node`, or + set the `NODE_ICU_DATA=node_modules/full-icu` environment variable. + +If you're running Polyglot within a browser, it can number-format in any +locale the web browser supports. + If you like, you can provide a default value in case the phrase is missing. Use the special option key "_" to specify a default. diff --git a/index.js b/index.js index 84e53a8..ea7e5c5 100644 --- a/index.js +++ b/index.js @@ -119,7 +119,7 @@ var tokenRegex = /%\{(.*?)\}/g; // // You should pass in a third argument, the locale, to specify the correct plural type. // It defaults to `'en'` with 2 plural forms. -function transformPhrase(phrase, substitutions, locale) { +function transformPhrase(phrase, substitutions, locale, numberFormat) { if (typeof phrase !== 'string') { throw new TypeError('Polyglot.transformPhrase expects argument #1 to be string'); } @@ -144,8 +144,14 @@ function transformPhrase(phrase, substitutions, locale) { // Interpolate: Creates a `RegExp` object for each interpolation placeholder. result = result.replace(tokenRegex, function (expression, argument) { if (!has(options, argument)) { return ''; } + + var replacement = options[argument]; + if (typeof replacement === 'number') { + replacement = numberFormat.format(replacement); + } + // Ensure replacement value is escaped to prevent special $-prefixed regex replace tokens. - return replace.call(options[argument], dollarRegex, dollarBillsYall); + return replace.call(replacement, dollarRegex, dollarBillsYall); }); return result; @@ -157,6 +163,12 @@ function Polyglot(options) { this.phrases = {}; this.extend(opts.phrases || {}); this.currentLocale = opts.locale || 'en'; + if (typeof Intl === 'object') { + this.numberFormat = new Intl.NumberFormat(this.currentLocale); + } else { + // Fallback for IE<11 + this.numberFormat = { format: function (n) { return String(n); } }; + } this.allowMissing = !!opts.allowMissing; this.warn = opts.warn || warn; } @@ -165,7 +177,15 @@ function Polyglot(options) { // // Get or set locale. Internally, Polyglot only uses locale for pluralization. Polyglot.prototype.locale = function (newLocale) { - if (newLocale) this.currentLocale = newLocale; + if (newLocale) { + this.currentLocale = newLocale; + if (typeof Intl === 'object') { + this.numberFormat = new Intl.NumberFormat(this.currentLocale); + } else { + // Fallback for IE<11 + this.numberFormat = { format: function (n) { return String(n); } }; + } + } return this.currentLocale; }; @@ -314,7 +334,7 @@ Polyglot.prototype.t = function (key, options) { result = key; } if (typeof phrase === 'string') { - result = transformPhrase(phrase, opts, this.currentLocale); + result = transformPhrase(phrase, opts, this.currentLocale, this.numberFormat); } return result; }; diff --git a/package.json b/package.json index 75a7b7f..12f504b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "pretest": "npm run --silent lint", "test": "npm run --silent tests-only", - "tests-only": "mocha test/*.js --reporter spec", + "tests-only": "NODE_ICU_DATA=node_modules/full-icu mocha test/*.js --reporter spec", "lint": "eslint *.js test/*.js", "docs": "docco -o docs/ index.js" }, @@ -36,6 +36,7 @@ "eslint": "^3.9.1", "eslint-config-airbnb-base": "^10.0.1", "eslint-plugin-import": "^2.2.0", + "full-icu": "^1.0.3", "mocha": "^3.1.2", "should": "^11.1.1", "uglify-js": "2.7.3" diff --git a/test/index.js b/test/index.js index 055ff1e..c51b12a 100644 --- a/test/index.js +++ b/test/index.js @@ -8,7 +8,8 @@ describe('t', function () { hello: 'Hello', hi_name_welcome_to_place: 'Hi, %{name}, welcome to %{place}!', name_your_name_is_name: '%{name}, your name is %{name}!', - empty_string: '' + empty_string: '', + number: '%{number}' }; var polyglot; @@ -89,6 +90,14 @@ describe('t', function () { expect(instance.t('nav.cta.join_now')).to.equal('Join now!'); expect(instance.t('header.sign_in')).to.equal('Sign In'); }); + + it('uses an Intl.NumberFormat', function () { + var en = new Polyglot({ phrases: phrases, locale: 'en' }); + var fr = new Polyglot({ phrases: phrases, locale: 'fr' }); + + expect(en.t('number', { number: 1234.56 })).to.equal('1,234.56'); + expect(fr.t('number', { number: 1234.56 })).to.equal('1 234,56'); + }); }); describe('pluralize', function () { @@ -165,6 +174,12 @@ describe('locale', function () { polyglot.locale('fr'); expect(polyglot.locale()).to.equal('fr'); }); + + it('updates number format when setting locale', function () { + polyglot.locale('fr'); + polyglot.extend({ x: '%{n}' }); + expect(polyglot.t('x', { n: 1234.56 })).to.equal('1 234,56'); + }); }); describe('extend', function () {