Skip to content

Commit

Permalink
Add selectordinal support and improve locale data
Browse files Browse the repository at this point in the history
This upgrades to intl-messageformat-parser@1.1.0 and adds support for
`selectordinal` arguments; e.g.:

```
This is my {year, selectordinal,
    one {#st}
    two {#nd}
    few {#rd}
    other{#th}
} birthday.
```

The locale data has also been vastly improved in the following ways:

* Added `pt-PT` plural rule function, which differs from `pt`'s.

* Properly de-duplicate data for all CLDR locales by correctly
  traversing a locale's hierarchy of ancestor locales.

* Added data for the following languages:
  aa, agq, bas, bh, ckb, dav, dje, dsb, dua, dv, dyo, ebu, ewo, guw,
  guz, hsb, ia, in, iu, iw, jbo, ji, jv, jw, kaj, kam, kcg, khq, ki,
  kln, kok, ksf, ku, lb, lu, luo, luy, mer, mfe, mgh, mo, mua, nah,
  nmg, no, nqo, nus, ny, pap, prg, qu, rn, rw, sbp, sh, sma, smi, smj,
  smn, sms, swc, syr, tk, tl, twq, vai, wa, wo, yav, yi, zgh

----

These changes also include improvements for how locales are resolved.
Here are some details of these changes:

* If no extra locale data is loaded, the locale will _always_ resolved
  to `en`.

* If locale data is missing for a leaf locale like `fr-FR`, but there
  _is_ data for the root, `fr` in this case, then its root will be
  used.

* If there's data for the specified locale, then that locale will be
  resolved; i.e.,

    var mf = new IntlMessageFormat('', 'en-US');
    assert(mf.resolvedOptions().locale === 'en-US'); // true

* The resolved locales are now normalized; e.g., `en-us` will resolve
  to: `en-US`.

Fixes formatjs#84
  • Loading branch information
ericf committed Mar 6, 2015
1 parent 32967d4 commit 678b8fb
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 161 deletions.
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ module.exports = function (grunt) {

extract_cldr_data: {
options: {
plurals: true
pluralRules: true
},

src_en: {
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"./lib/locales.js": false
},
"dependencies": {
"intl-messageformat-parser": "1.0.2"
"intl-messageformat-parser": "1.1.0"
},
"devDependencies": {
"benchmark": "^1.0.0",
Expand All @@ -49,7 +49,7 @@
"grunt-contrib-copy": "^0.8.0",
"grunt-contrib-jshint": "^0.11.0",
"grunt-contrib-uglify": "^0.8.0",
"grunt-extract-cldr-data": "^1.1.0",
"grunt-extract-cldr-data": "^2.1.0",
"grunt-json-remove-fields": "git://github.com/ericf/grunt-json-remove-fields#improve-files-and-output",
"grunt-saucelabs": "^8.4.1",
"intl": "^0.1.4",
Expand All @@ -58,7 +58,7 @@
"xunit-file": "0.0.6"
},
"scripts": {
"test": "istanbul cover -x lib/locales.js -- ./node_modules/mocha/bin/_mocha tests/runner.js --reporter spec",
"test": "istanbul cover -x lib/locales.js -- _mocha tests/runner.js --reporter spec",
"travis": "istanbul cover -x lib/locales.js -- _mocha tests/runner.js --reporter spec && tests/saucelabs.sh",
"prepublish": "grunt"
},
Expand Down
23 changes: 13 additions & 10 deletions src/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ Compiler.prototype.compileArgument = function (element) {

case 'pluralFormat':
options = this.compileOptions(element);
return new PluralFormat(element.id, format.offset, options, pluralFn);
return new PluralFormat(
element.id, format.ordinal, format.offset, options, pluralFn
);

case 'selectFormat':
options = this.compileOptions(element);
Expand All @@ -127,8 +129,8 @@ Compiler.prototype.compileOptions = function (element) {
optionsHash = {};

// Save the current plural element, if any, then set it to a new value when
// compiling the options sub-patterns. This conform's the spec's algorithm
// for handling `"#"` synax in message text.
// compiling the options sub-patterns. This conforms the spec's algorithm
// for handling `"#"` syntax in message text.
this.pluralStack.push(this.currentPlural);
this.currentPlural = format.type === 'pluralFormat' ? element : null;

Expand All @@ -141,7 +143,7 @@ Compiler.prototype.compileOptions = function (element) {
optionsHash[option.selector] = this.compileMessage(option.value);
}

// Pop the plural stack to put back the original currnet plural value.
// Pop the plural stack to put back the original current plural value.
this.currentPlural = this.pluralStack.pop();

return optionsHash;
Expand All @@ -161,18 +163,19 @@ StringFormat.prototype.format = function (value) {
return typeof value === 'string' ? value : String(value);
};

function PluralFormat(id, offset, options, pluralFn) {
this.id = id;
this.offset = offset;
this.options = options;
this.pluralFn = pluralFn;
function PluralFormat(id, useOrdinal, offset, options, pluralFn) {
this.id = id;
this.useOrdinal = useOrdinal;
this.offset = offset;
this.options = options;
this.pluralFn = pluralFn;
}

PluralFormat.prototype.getOption = function (value) {
var options = this.options;

var option = options['=' + value] ||
options[this.pluralFn(value - this.offset)];
options[this.pluralFn(value - this.offset, this.useOrdinal)];

return option || options.other;
};
Expand Down
77 changes: 44 additions & 33 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@ function MessageFormat(message, locales, formats) {
// Defined first because it's used to build the format pattern.
defineProperty(this, '_locale', {value: this._resolveLocale(locales)});

var pluralFn = MessageFormat.__localeData__[this._locale].pluralRuleFunction;

// Compile the `ast` to a pattern that is highly optimized for repeated
// `format()` invocations. **Note:** This passes the `locales` set provided
// to the constructor instead of just the resolved locale.
var pattern = this._compilePattern(ast, locales, formats, pluralFn);
var pluralFn = this._findPluralRuleFunction(this._locale);
var pattern = this._compilePattern(ast, locales, formats, pluralFn);

// "Bind" `format()` method to `this` so it can be passed by reference like
// the other `Intl` APIs.
Expand Down Expand Up @@ -129,17 +128,7 @@ defineProperty(MessageFormat, '__addLocaleData', {value: function (data) {
);
}

if (!data.pluralRuleFunction) {
throw new Error(
'Locale data provided to IntlMessageFormat is missing a ' +
'`pluralRuleFunction` property'
);
}

// Message format locale data only requires the first part of the tag.
var locale = data.locale.toLowerCase().split('-')[0];

MessageFormat.__localeData__[locale] = data;
MessageFormat.__localeData__[data.locale.toLowerCase()] = data;
}});

// Defines `__parse()` static method as an exposed private.
Expand All @@ -165,6 +154,26 @@ MessageFormat.prototype._compilePattern = function (ast, locales, formats, plura
return compiler.compile(ast);
};

MessageFormat.prototype._findPluralRuleFunction = function (locale) {
var localeData = MessageFormat.__localeData__;
var data = localeData[locale.toLowerCase()];

// The locale data is de-duplicated, so we have to traverse the locale's
// hierarchy until we find a `pluralRuleFunction` to return.
while (data) {
if (data.pluralRuleFunction) {
return data.pluralRuleFunction;
}

data = data.parentLocale && localeData[data.parentLocale.toLowerCase()];
}

throw new Error(
'Locale data added to IntlMessageFormat is missing a ' +
'`pluralRuleFunction` for :' + locale
);
};

MessageFormat.prototype._format = function (pattern, values) {
var result = '',
i, len, part, id, value;
Expand Down Expand Up @@ -218,37 +227,39 @@ MessageFormat.prototype._mergeFormats = function (defaults, formats) {
};

MessageFormat.prototype._resolveLocale = function (locales) {
if (!locales) {
locales = MessageFormat.defaultLocale;
}

if (typeof locales === 'string') {
locales = [locales];
}

// Create a copy of the array so we can push on the default locale.
locales = (locales || []).concat(MessageFormat.defaultLocale);

var localeData = MessageFormat.__localeData__;
var i, len, locale;
var i, len, localeParts, data;

// Using the set of locales + the default locale, we look for the first one
// which that has been registered. When data does not exist for a locale, we
// traverse its ancestors to find something that's been registered within
// its hierarchy of locales. Since we lack the proper `parentLocale` data
// here, we must take a naive approach to traversal.
for (i = 0, len = locales.length; i < len; i += 1) {
// We just need the root part of the langage tag.
locale = locales[i].split('-')[0].toLowerCase();

// Validate that the langage tag is structurally valid.
if (!/[a-z]{2,3}/.test(locale)) {
throw new Error(
'Language tag provided to IntlMessageFormat is not ' +
'structrually valid: ' + locale
);
}
localeParts = locales[i].toLowerCase().split('-');

while (localeParts.length) {
data = localeData[localeParts.join('-')];
if (data) {
// Return the normalized locale string; e.g., we return "en-US",
// instead of "en-us".
return data.locale;
}

// Return the first locale for which we have CLDR data registered.
if (hop.call(localeData, locale)) {
return locale;
localeParts.pop();
}
}

var defaultLocale = locales.pop();
throw new Error(
'No locale data has been added to IntlMessageFormat for: ' +
locales.join(', ')
locales.join(', ') + ', or the default locale: ' + defaultLocale
);
};
2 changes: 1 addition & 1 deletion src/en.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// GENERATED FILE
export default {"locale":"en","pluralRuleFunction":function (n) {var i=Math.floor(Math.abs(n)),v=n.toString().replace(/^[^.]*\.?/,"").length;n=Math.floor(n);if(i===1&&v===0)return"one";return"other";}};
export default {"locale":"en","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].substr(-1),n100=t0&&s[0].substr(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other"}};
Loading

0 comments on commit 678b8fb

Please sign in to comment.