Skip to content

Commit 8570d35

Browse files
adamhooperljharb
authored andcommitted
Use Intl.NumberFormat to format numbers
1 parent 2d06f0f commit 8570d35

File tree

4 files changed

+61
-6
lines changed

4 files changed

+61
-6
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,25 @@ polyglot.t("car", 2);
243243
=> "2 cars"
244244
```
245245

246+
Interpolated `Number`s will be number-formatted according to the `locale`:
247+
248+
```js
249+
polyglot.t("num_cars", 2000);
250+
=> "2,000 cars"
251+
```
252+
253+
On a default Node install, this may only work in English. To format in
254+
non-English locales (e.g., to output "2.000" in France or use other numerals),
255+
compile Node with "full" ICU data or include the `full-icu` package in your
256+
project:
257+
258+
1. `npm install --save full-icu`
259+
2. Run `node --full-data-dir=node_modules/full-icu` instead of just `node`, or
260+
set the `NODE_ICU_DATA=node_modules/full-icu` environment variable.
261+
262+
If you're running Polyglot within a browser, it can number-format in any
263+
locale the web browser supports.
264+
246265
If you like, you can provide a default value in case the phrase is missing.
247266
Use the special option key "_" to specify a default.
248267

index.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ var tokenRegex = /%\{(.*?)\}/g;
119119
//
120120
// You should pass in a third argument, the locale, to specify the correct plural type.
121121
// It defaults to `'en'` with 2 plural forms.
122-
function transformPhrase(phrase, substitutions, locale) {
122+
function transformPhrase(phrase, substitutions, locale, numberFormat) {
123123
if (typeof phrase !== 'string') {
124124
throw new TypeError('Polyglot.transformPhrase expects argument #1 to be string');
125125
}
@@ -144,8 +144,14 @@ function transformPhrase(phrase, substitutions, locale) {
144144
// Interpolate: Creates a `RegExp` object for each interpolation placeholder.
145145
result = result.replace(tokenRegex, function (expression, argument) {
146146
if (!has(options, argument)) { return ''; }
147+
148+
var replacement = options[argument];
149+
if (typeof replacement === 'number') {
150+
replacement = numberFormat.format(replacement);
151+
}
152+
147153
// Ensure replacement value is escaped to prevent special $-prefixed regex replace tokens.
148-
return replace.call(options[argument], dollarRegex, dollarBillsYall);
154+
return replace.call(replacement, dollarRegex, dollarBillsYall);
149155
});
150156

151157
return result;
@@ -157,6 +163,12 @@ function Polyglot(options) {
157163
this.phrases = {};
158164
this.extend(opts.phrases || {});
159165
this.currentLocale = opts.locale || 'en';
166+
if (typeof Intl === 'object') {
167+
this.numberFormat = new Intl.NumberFormat(this.currentLocale);
168+
} else {
169+
// Fallback for IE<11
170+
this.numberFormat = { format: function (n) { return String(n); } };
171+
}
160172
this.allowMissing = !!opts.allowMissing;
161173
this.warn = opts.warn || warn;
162174
}
@@ -165,7 +177,15 @@ function Polyglot(options) {
165177
//
166178
// Get or set locale. Internally, Polyglot only uses locale for pluralization.
167179
Polyglot.prototype.locale = function (newLocale) {
168-
if (newLocale) this.currentLocale = newLocale;
180+
if (newLocale) {
181+
this.currentLocale = newLocale;
182+
if (typeof Intl === 'object') {
183+
this.numberFormat = new Intl.NumberFormat(this.currentLocale);
184+
} else {
185+
// Fallback for IE<11
186+
this.numberFormat = { format: function (n) { return String(n); } };
187+
}
188+
}
169189
return this.currentLocale;
170190
};
171191

@@ -314,7 +334,7 @@ Polyglot.prototype.t = function (key, options) {
314334
result = key;
315335
}
316336
if (typeof phrase === 'string') {
317-
result = transformPhrase(phrase, opts, this.currentLocale);
337+
result = transformPhrase(phrase, opts, this.currentLocale, this.numberFormat);
318338
}
319339
return result;
320340
};

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"scripts": {
77
"pretest": "npm run --silent lint",
88
"test": "npm run --silent tests-only",
9-
"tests-only": "mocha test/*.js --reporter spec",
9+
"tests-only": "NODE_ICU_DATA=node_modules/full-icu mocha test/*.js --reporter spec",
1010
"lint": "eslint *.js test/*.js",
1111
"docs": "docco -o docs/ index.js"
1212
},
@@ -36,6 +36,7 @@
3636
"eslint": "^3.9.1",
3737
"eslint-config-airbnb-base": "^10.0.1",
3838
"eslint-plugin-import": "^2.2.0",
39+
"full-icu": "^1.0.3",
3940
"mocha": "^3.1.2",
4041
"should": "^11.1.1",
4142
"uglify-js": "2.7.3"

test/index.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ describe('t', function () {
88
hello: 'Hello',
99
hi_name_welcome_to_place: 'Hi, %{name}, welcome to %{place}!',
1010
name_your_name_is_name: '%{name}, your name is %{name}!',
11-
empty_string: ''
11+
empty_string: '',
12+
number: '%{number}'
1213
};
1314

1415
var polyglot;
@@ -89,6 +90,14 @@ describe('t', function () {
8990
expect(instance.t('nav.cta.join_now')).to.equal('Join now!');
9091
expect(instance.t('header.sign_in')).to.equal('Sign In');
9192
});
93+
94+
it('uses an Intl.NumberFormat', function () {
95+
var en = new Polyglot({ phrases: phrases, locale: 'en' });
96+
var fr = new Polyglot({ phrases: phrases, locale: 'fr' });
97+
98+
expect(en.t('number', { number: 1234.56 })).to.equal('1,234.56');
99+
expect(fr.t('number', { number: 1234.56 })).to.equal('1 234,56');
100+
});
92101
});
93102

94103
describe('pluralize', function () {
@@ -165,6 +174,12 @@ describe('locale', function () {
165174
polyglot.locale('fr');
166175
expect(polyglot.locale()).to.equal('fr');
167176
});
177+
178+
it('updates number format when setting locale', function () {
179+
polyglot.locale('fr');
180+
polyglot.extend({ x: '%{n}' });
181+
expect(polyglot.t('x', { n: 1234.56 })).to.equal('1 234,56');
182+
});
168183
});
169184

170185
describe('extend', function () {

0 commit comments

Comments
 (0)