diff --git a/Gruntfile.js b/Gruntfile.js
index 090e044ce..f7616189c 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -132,7 +132,8 @@ module.exports = function( grunt ) {
paths: {
cldr: "../external/cldrjs/dist/cldr",
"make-plural": "../external/make-plural/make-plural",
- messageformat: "../external/messageformat/messageformat",
+ "messageformat-parser": "../node_modules/messageformat-parser/parser",
+ "reserved-words": "../node_modules/reserved-words/lib/reserved-words",
"zoned-date-time": "../node_modules/zoned-date-time/src/zoned-date-time"
},
shim: {
@@ -151,8 +152,7 @@ module.exports = function( grunt ) {
// Only for root id's (the ones in src, not in src's subpaths). Note there's no
// conditional code checking for this type.
onBuildWrite: function( id, path, contents ) {
- var messageformat,
- name = camelCase( id.replace( /util\/|common\//, "" ) );
+ var name = camelCase( id.replace( /util\/|common\//, "" ) );
// MakePlural
if ( ( /make-plural/ ).test( id ) ) {
@@ -200,68 +200,39 @@ module.exports = function( grunt ) {
"/* jshint ignore:end */"
].join( "\n" ) );
- // messageformat
- } else if ( ( /messageformat/ ).test( id ) ) {
- return contents
+ // messageformat-parser
+ } else if ( ( /messageformat-parser/.test( id ) ) ) {
- // Remove browserify wrappers.
- .replace( /^\(function\(f\)\{if\(typeof exports==="object"&&type.*/, "" )
- .replace( "},{}],2:[function(require,module,exports){", "" )
- .replace( /\},\{"\.\/messageformat-parser":1,"make-plural\/plural.*/, "" )
- .replace( /\},\{\}\]\},\{\},\[2\]\)\(2\)[\s\S]*?$/, "" )
-
- // Set `MessageFormat.plurals` and remove `make-plural/plurals`
- // completely. This is populated by Globalize on demand.
- .replace( /var _cp = \[[\s\S]*?$/, "" )
- .replace(
- "MessageFormat.plurals = require('make-plural/plurals')",
- "MessageFormat.plurals = {}"
- )
-
- // Set `MessageFormat._parse`
- .replace(
- "MessageFormat._parse = require('./messageformat-parser').parse;",
- ""
- )
- .replace( /module\.exports = \(function\(\) \{([\s\S]*?)\n\}\)\(\);/, [
- "MessageFormat._parse = (function() {",
- "$1",
- "}()).parse;"
+ return contents
+ .replace( /^/, [
+ "var Parser;",
+ "/* jshint ignore:start */\n",
+ "Parser = (function() {"
].join( "\n" ) )
+ .replace( "module.exports = ", "return " )
+ .replace( /$/, [
+ "}());",
+ "/* jshint ignore:end */"
+ ].join( "\n" ) );
- // Remove unused code.
- .replace( /if \(!pluralFunc\) \{\n[\s\S]*?\n \}/, "" )
- .replace( /if \(!locale\) \{\n[\s\S]*? \}\n/, "this.lc = [locale];" )
- .replace( /(MessageFormat\.formatters) = \{[\s\S]*?\n\};/, "$1 = {};" )
- .replace( /MessageFormat\.prototype\.setIntlSupport[\s\S]*?\n\};/, "" )
+ // reserved-words
+ } else if ( ( /reserved-words/.test( id ) ) ) {
- // Wrap everything into a var assignment.
- .replace( "module.exports = MessageFormat;", "" )
+ return contents
.replace( /^/, [
- "var MessageFormat;",
- "/* jshint ignore:start */",
- "MessageFormat = (function() {"
+ "var reserved;",
+ "/* jshint ignore:start */\n",
+ "reserved = (function() {",
+ "var exports = {};"
].join( "\n" ) )
+ .replace( "var assert = require\('assert'\);", "" )
+ .replace( /^\s*assert\(.*;\s*$/gm, "" )
.replace( /$/, [
- "return MessageFormat;",
+ "return exports;",
"}());",
"/* jshint ignore:end */"
].join( "\n" ) );
- // message-runtime
- } else if ( ( /message-runtime/ ).test( id ) ) {
- messageformat = require( "./external/messageformat/messageformat" );
- delete messageformat.prototype.runtime.fmt;
- delete messageformat.prototype.runtime.pluralFuncs;
- contents = contents.replace( "Globalize._messageFormat = {};", [
- "/* jshint ignore:start */",
- "Globalize._messageFormat = (function() {",
- messageformat.prototype.runtime.toString(),
- "return {number: number, plural: plural, select: select};",
- "}());",
- "/* jshint ignore:end */"
- ].join( "\n" ) );
-
// ZonedDateTime
} else if ( ( /zoned-date-time/ ).test( id ) ) {
contents = contents.replace(
diff --git a/bower.json b/bower.json
index 524c7b592..ffe347670 100644
--- a/bower.json
+++ b/bower.json
@@ -14,7 +14,6 @@
"cldr-data": ">=25",
"es5-shim": "3.4.0",
"make-plural": "eemeli/make-plural.js#3.0.0",
- "messageformat": "SlexAxton/messageformat.js#v0.3.0-1",
"qunit": "1.18.0",
"requirejs": "2.1.20",
"requirejs-plugins": "1.0.2",
diff --git a/examples/amd-bower/main.js b/examples/amd-bower/main.js
index 1d44f831b..9ab900e32 100644
--- a/examples/amd-bower/main.js
+++ b/examples/amd-bower/main.js
@@ -41,6 +41,7 @@ require([
"json!cldr-data/supplemental/likelySubtags.json",
"json!cldr-data/supplemental/metaZones.json",
"json!cldr-data/supplemental/plurals.json",
+ "json!cldr-data/supplemental/ordinals.json",
"json!cldr-data/supplemental/timeData.json",
"json!cldr-data/supplemental/weekData.json",
"json!messages/en.json",
@@ -54,9 +55,8 @@ require([
"globalize/plural",
"globalize/relative-time",
"globalize/unit"
-], function( Globalize, enGregorian, enCurrencies, enDateFields, enNumbers,
- enTimeZoneNames, enUnits, currencyData, likelySubtags, metaZones,
- pluralsData, timeData, weekData, messages, ianaTzData ) {
+], function( Globalize, enGregorian, enCurrencies, enDateFields, enNumbers, enUnits, currencyData,
+ likelySubtags, pluralsData, ordinalsData, timeData, weekData, messages ) {
var en, like, number;
@@ -72,6 +72,7 @@ require([
likelySubtags,
metaZones,
pluralsData,
+ ordinalsData,
timeData,
weekData
);
diff --git a/examples/node-npm/main.js b/examples/node-npm/main.js
index 1ce052430..b0faf80ee 100644
--- a/examples/node-npm/main.js
+++ b/examples/node-npm/main.js
@@ -13,6 +13,7 @@ Globalize.load(
require( "cldr-data/supplemental/likelySubtags" ),
require( "cldr-data/supplemental/metaZones" ),
require( "cldr-data/supplemental/plurals" ),
+ require( "cldr-data/supplemental/ordinals" ),
require( "cldr-data/supplemental/timeData" ),
require( "cldr-data/supplemental/weekData" )
);
diff --git a/examples/plain-javascript/index.html b/examples/plain-javascript/index.html
index 900303448..c4c04a4b5 100644
--- a/examples/plain-javascript/index.html
+++ b/examples/plain-javascript/index.html
@@ -267,6 +267,14 @@
Demo output
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
}
+ },
+ "plurals-type-ordinal": {
+ "en": {
+ "pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …",
+ "pluralRule-count-two": "n % 10 = 2 and n % 100 != 12 @integer 2, 22, 32, 42, 52, 62, 72, 82, 102, 1002, …",
+ "pluralRule-count-few": "n % 10 = 3 and n % 100 != 13 @integer 3, 23, 33, 43, 53, 63, 73, 83, 103, 1003, …",
+ "pluralRule-count-other": " @integer 0, 4~18, 100, 1000, 10000, 100000, 1000000, …"
+ }
}
}
});
diff --git a/package.json b/package.json
index dc2a4afeb..9dfce4d39 100644
--- a/package.json
+++ b/package.json
@@ -96,7 +96,9 @@
"matchdep": "0.3.0",
"mocha": "^3.3.0",
"semver": "^5.3.0",
- "zoned-date-time": "1.0.0"
+ "zoned-date-time": "1.0.0",
+ "messageformat-parser": "^1.0.0",
+ "reserved-words": "^0.1.1"
},
"commitplease": {
"nohook": true
diff --git a/src/common/validate/parameter-type/plural-type.js b/src/common/validate/parameter-type/plural-type.js
index 8991f1907..b0d66e40b 100644
--- a/src/common/validate/parameter-type/plural-type.js
+++ b/src/common/validate/parameter-type/plural-type.js
@@ -6,8 +6,8 @@ return function( value, name ) {
validateParameterType(
value,
name,
- value === undefined || value === "cardinal" || value === "ordinal",
- "String \"cardinal\" or \"ordinal\""
+ value === undefined || value === "cardinal" || value === "ordinal" || value === "both",
+ "String \"cardinal\" or \"ordinal\" or \"both\""
);
};
diff --git a/src/core.js b/src/core.js
index 28a7b34ba..255e40e56 100644
--- a/src/core.js
+++ b/src/core.js
@@ -17,12 +17,13 @@ define([
"./util/object/extend",
"./util/regexp/escape",
"./util/string/pad",
+ "./util/formatterfn/options",
"cldr/event"
], function( Cldr, createError, formatMessage, runtimeBind, validate, validateCldr,
validateDefaultLocale, validateParameterPresence, validateParameterRange, validateParameterType,
validateParameterTypeLocale, validateParameterTypePlainObject, alwaysArray, alwaysCldr,
- isPlainObject, objectExtend, regexpEscape, stringPad ) {
+ isPlainObject, objectExtend, regexpEscape, stringPad, formatterfnOptions ) {
function validateLikelySubtags( cldr ) {
cldr.once( "get", validateCldr );
@@ -86,6 +87,12 @@ Globalize.locale = function( locale ) {
return this.cldr;
};
+Globalize._messageFmts = {};
+
+Globalize.addMessageFormatterFunction = function( name, fn, options ) {
+ Globalize._messageFmts[name] = formatterfnOptions( fn, options );
+};
+
/**
* Optimization to avoid duplicating some internal functions across modules.
*/
diff --git a/src/currency.js b/src/currency.js
index 977758038..5abde745f 100644
--- a/src/currency.js
+++ b/src/currency.js
@@ -91,6 +91,14 @@ Globalize.prototype.currencyFormatter = function( currency, options ) {
return returnFn;
};
+Globalize.addMessageFormatterFunction( "currency", function( currency, style ) {
+ var options = {};
+ if ( style ) {
+ options.style = style;
+ }
+ return this.currencyFormatter( currency, options );
+});
+
/**
* .currencyParser( currency [, options] )
*
diff --git a/src/date.js b/src/date.js
index 16ec37a9e..659d0cbbb 100644
--- a/src/date.js
+++ b/src/date.js
@@ -55,12 +55,14 @@ function validateOptionsPreset( options ) {
validateOptionsPresetEach( "datetime", options );
}
+var presets = [ "short", "medium", "long", "full" ];
+
function validateOptionsPresetEach( type, options ) {
var value = options[ type ];
validate(
"E_INVALID_OPTIONS",
"Invalid `{{type}: \"{value}\"}`.",
- value === undefined || [ "short", "medium", "long", "full" ].indexOf( value ) !== -1,
+ value === undefined || presets.indexOf( value ) !== -1,
{ type: type, value: value }
);
}
@@ -210,6 +212,24 @@ Globalize.prototype.dateToPartsFormatter = function( options ) {
return returnFn;
};
+[ "date", "time", "datetime" ].map(function( type ) {
+ Globalize.addMessageFormatterFunction( type, function( p ) {
+ var options = {};
+ if ( p ) {
+ var trimmed = p.trim();
+ if ( presets.indexOf( trimmed ) !== -1 ) {
+ options[type] = trimmed;
+ } else if ( trimmed.indexOf( "skeleton" ) === 0 && trimmed.indexOf( "," ) !== -1 ) {
+ var splitArgs = p.split( ",", 2 );
+ options.skeleton = splitArgs[1].trim();
+ } else {
+ options.raw = p;
+ }
+ }
+ return this.dateFormatter( options );
+ }, { split: false, trim: false });
+});
+
/**
* .dateParser( options )
*
diff --git a/src/message-runtime.js b/src/message-runtime.js
index 6880351de..eae8bc9de 100644
--- a/src/message-runtime.js
+++ b/src/message-runtime.js
@@ -2,11 +2,14 @@ define([
"./common/runtime-key",
"./common/validate/parameter-type/message-variables",
"./core-runtime",
- "./message/formatter-fn"
-], function( runtimeKey, validateParameterTypeMessageVariables, Globalize, messageFormatterFn ) {
+ "./message/formatter-fn",
+ "./message/formatter-runtime"
+], function( runtimeKey, validateParameterTypeMessageVariables, Globalize, messageFormatterFn,
+ messageFormatterRuntime
+) {
Globalize._messageFormatterFn = messageFormatterFn;
-Globalize._messageFormat = {};
+Globalize._messageFormat = new messageFormatterRuntime(); // TODO setStrictNumber
Globalize._validateParameterTypeMessageVariables = validateParameterTypeMessageVariables;
Globalize.messageFormatter =
diff --git a/src/message.js b/src/message.js
index b5c4e4675..0b0b86c04 100644
--- a/src/message.js
+++ b/src/message.js
@@ -1,6 +1,5 @@
define([
"cldr",
- "messageformat",
"./common/create-error",
"./common/create-error/plural-module-presence",
"./common/runtime-bind",
@@ -12,15 +11,18 @@ define([
"./common/validate/parameter-type",
"./common/validate/parameter-type/plain-object",
"./core",
+ "./message/compiler",
+ "./message/formatter-runtime",
"./message/formatter-fn",
"./message/formatter-runtime-bind",
"./util/always-array",
"cldr/event"
-], function( Cldr, MessageFormat, createError, createErrorPluralModulePresence, runtimeBind,
+], function( Cldr, createError, createErrorPluralModulePresence, runtimeBind,
validateDefaultLocale, validateMessageBundle, validateMessagePresence, validateMessageType,
validateParameterPresence, validateParameterType, validateParameterTypePlainObject, Globalize,
- messageFormatterFn, messageFormatterRuntimeBind, alwaysArray ) {
+ messageCompiler, messageFormatterRuntime, messageFormatterFn, messageFormatterRuntimeBind,
+ alwaysArray ) {
var slice = [].slice;
@@ -82,17 +84,56 @@ Globalize.prototype.messageFormatter = function( path ) {
}
validateMessageType( path, message );
- // Is plural module present? Yes, use its generator. Nope, use an error generator.
- pluralGenerator = this.plural !== undefined ?
- this.pluralGenerator() :
- createErrorPluralModulePresence;
+ var compiler = new messageCompiler( this, Globalize._messageFmts );
+ var formatterSrc = compiler
+ .compile( message, cldr.locale );
+
+ var pluralType = false;
+ if ( compiler.pluralTypes.cardinal && compiler.pluralTypes.ordinal ) {
+ pluralType = "both";
+ } else if ( compiler.pluralTypes.cardinal ) {
+ pluralType = "cardinal";
+ } else if ( compiler.pluralTypes.ordinal ) {
+ pluralType = "ordinal";
+ }
+
+ if ( pluralType !== false ) {
- formatter = new MessageFormat( cldr.locale, pluralGenerator ).compile( message );
+ // Is plural module present? Yes, use its generator. Nope, use an error generator.
+ pluralGenerator = this.plural !== undefined ?
+ this.pluralGenerator( { type: pluralType } ) :
+ createErrorPluralModulePresence;
+ }
- returnFn = messageFormatterFn( formatter );
+ var runtime = new messageFormatterRuntime( compiler.strictNumberSign );
+
+ /* jshint evil:true */
+ formatter = new Function(
+ "number, plural, select", messageCompiler.funcname( cldr.locale ),
+ " var fmt = [].slice.call( arguments, 4 );\n" +
+ " return " + formatterSrc + "\n"
+ );
+
+ returnFn = messageFormatterFn.apply( this, [
+ formatter, "call",
+ runtime.number, runtime.plural, runtime.select, pluralGenerator
+ ].concat( compiler.formatters ) );
+
+ var runtimeArgs = [
+ messageFormatterRuntimeBind(
+ formatter, formatterSrc, compiler.runtime, pluralType, cldr.locale, compiler.formatters
+ ),
+ "call"
+ ];
+
+ if ( pluralGenerator ) {
+ runtimeArgs.push( pluralGenerator );
+ }
+ runtimeArgs = runtimeArgs.concat(
+ compiler.formatters
+ );
- runtimeBind( args, cldr, returnFn,
- [ messageFormatterRuntimeBind( cldr, formatter ), pluralGenerator ] );
+ runtimeBind( args, cldr, returnFn, runtimeArgs );
return returnFn;
};
diff --git a/src/message/compiler.js b/src/message/compiler.js
new file mode 100644
index 000000000..d53527aa9
--- /dev/null
+++ b/src/message/compiler.js
@@ -0,0 +1,211 @@
+define([
+ "../core",
+ "messageformat-parser",
+ "reserved-words"
+], function( Globalize, Parser, reserved ) {
+
+/** Creates a new message compiler.
+ *
+ * @class
+ * @param {object} context - Context (this) for formatter functions
+ * @param {array} fmtFuncs - Formatter functions
+ * @property {object} runtime - Names of the core runtime functions used by the compiled functions
+ * @property {array} formatters - The formatter functions used by the compiled functions
+ * @property {object} pluralTypes - The plural types used by the compiled functions
+ */
+function Compiler( context, fmtFuncs ) {
+ this._globalize = Globalize;
+ this.fmt = fmtFuncs;
+ this.runtime = {};
+ this.pluralTypes = {
+ ordinal: false,
+ cardinal: false
+ };
+ this.formatters = [];
+ this.context = context;
+}
+
+/** Enable or disable the addition of Unicode control characters to all input
+ * to preserve the integrity of the output when mixing LTR and RTL text.
+ *
+ * @see http://cldr.unicode.org/development/development-process/design-proposals/bidi-handling-of-structured-text
+ *
+ * @memberof Compiler
+ * @param {boolean} [enable=true]
+ * @returns {Compiler} The Compiler instance, to allow for chaining
+ */
+Compiler.prototype.setBiDiSupport = function( enable ) {
+ this.bidiSupport = !!enable || ( typeof enable === "undefined" );
+ return this;
+};
+
+/** According to the ICU MessageFormat spec, a `#` character directly inside a
+ * `plural` or `selectordinal` statement should be replaced by the number
+ * matching the surrounding statement. By default, messageformat.js will
+ * replace `#` signs with the value of the nearest surrounding `plural` or
+ * `selectordinal` statement.
+ *
+ * Set this to true to follow the stricter ICU MessageFormat spec, and to
+ * throw a runtime error if `#` is used with non-numeric input.
+ *
+ * @memberof Compiler
+ * @param {boolean} [enable=true]
+ * @returns {Compiler} The Compiler instance, to allow for chaining
+ */
+Compiler.prototype.setStrictNumberSign = function( enable ) {
+ this.strictNumberSign = !!enable || ( typeof enable === "undefined" );
+
+ // TODO
+ // this.runtime.setStrictNumber(this.strictNumberSign);
+ return this;
+};
+
+/** Utility function for quoting an Object's key value if required
+ *
+ * Quotes the key if it contains invalid characters or is an
+ * ECMAScript 3rd Edition reserved word (for IE8).
+ */
+Compiler.propname = function( key, obj ) {
+ if ( /^[A-Z_$][0-9A-Z_$]*$/i.test( key ) &&
+ [ "break", "continue", "delete", "else", "for", "function", "if", "in", "new",
+ "return", "this", "typeof", "var", "void", "while", "with", "case", "catch",
+ "default", "do", "finally", "instanceof", "switch", "throw", "try" ].indexOf( key ) < 0 ) {
+ return obj ? obj + "." + key : key;
+ } else {
+ var jkey = JSON.stringify( key );
+ return obj ? obj + "[" + jkey + "]" : jkey;
+ }
+};
+
+/** Utility function for escaping a function name if required
+ */
+Compiler.funcname = function( key ) {
+ var fn = key.trim().replace( /\W+/g, "_" );
+ return reserved.check( fn, "es2015", true ) || /^\d/.test( fn ) ? "_" + fn : fn;
+};
+
+/** Utility formatter function for enforcing Bidi Structured Text by using UCC
+ *
+ * List inlined from data extracted from CLDR v27 & v28
+ * To verify/recreate, use the following:
+ *
+ * git clone https://github.com/unicode-cldr/cldr-misc-full.git
+ * cd cldr-misc-full/main/
+ * grep characterOrder -r . | tr '"/' '\t' | cut -f2,6 | grep -C4 right-to-left
+ */
+Compiler.bidiMarkText = function( text, locale ) {
+ function isLocaleRTL( locale ) {
+ var rtlLanguages = [ "ar", "ckb", "fa", "he", "ks($|[^bfh])", "lrc", "mzn",
+ "pa-Arab", "ps", "ug", "ur", "uz-Arab", "yi" ];
+ return new RegExp( "^" + rtlLanguages.join( "|^" ) ).test( locale );
+ }
+ var mark = JSON.stringify( isLocaleRTL( locale ) ? "\u200F" : "\u200E" );
+ return mark + " + " + text + " + " + mark;
+};
+
+/** @private */
+Compiler.prototype.cases = function( token, plural ) {
+ var needOther = true;
+ var r = token.cases.map( function( c ) {
+ if ( c.key === "other" ) {
+ needOther = false;
+ }
+ var s = c.tokens.map( function( tok ) { return this.token( tok, plural ); }, this );
+ return Compiler.propname( c.key ) + ": " + ( s.join( " + " ) || "\"\"" );
+ }, this );
+ if ( needOther ) {
+ throw new Error( "No 'other' form found in " + JSON.stringify( token ) );
+ }
+ return "{ " + r.join( ", " ) + " }";
+};
+
+/** @private */
+Compiler.prototype.token = function( token, plural ) {
+ if ( typeof token === "string" ) {
+ return JSON.stringify( token );
+ }
+
+ var fn, args = [ Compiler.propname( token.arg, "d" ) ];
+ switch ( token.type ) {
+ case "argument":
+ return this.bidiSupport ? Compiler.bidiMarkText( args[0], this.lc ) : args[0];
+
+ case "select":
+ fn = "select";
+ args.push( this.cases( token, this.strictNumberSign ? null : plural ) );
+ this.runtime.select = true;
+ break;
+
+ case "selectordinal":
+ fn = "plural";
+ args.push( 0, Compiler.funcname( this.lc ), this.cases( token, token ), 1 );
+ this.runtime.plural = true;
+ this.pluralTypes.ordinal = true;
+ break;
+
+ case "plural":
+ fn = "plural";
+ args.push(
+ token.offset || 0,
+ Compiler.funcname( this.lc ), this.cases( token, token )
+ );
+ this.runtime.plural = true;
+ this.pluralTypes.cardinal = true;
+ break;
+
+ case "function":
+ if ( !this.fmt[token.key] ) {
+ throw new Error( "Formatting function " +
+ JSON.stringify( token.key ) + " not found!" );
+ }
+ fn = "fmt[" + JSON.stringify( this.formatters.length ) + "]";
+ this.formatters.push( this.fmt[token.key].apply( this.context, token.params || [] ) );
+ break;
+
+ case "octothorpe":
+ if ( !plural ) {
+ return "\"#\"";
+ }
+ fn = "number";
+ args = [ Compiler.propname( plural.arg, "d" ), JSON.stringify( plural.arg ) ];
+ if ( plural.offset ) {
+ args.push( plural.offset );
+ }
+
+ this.runtime.number = true;
+ break;
+ }
+
+ if ( !fn ) {
+ throw new Error( "Parser error for token " + JSON.stringify( token ) );
+ }
+ return fn + "(" + args.join( ", " ) + ")";
+};
+
+/** Recursively compile a string or a tree of strings to JavaScript function sources
+ *
+ * If `src` is an object with a key that is also present in `plurals`, the key
+ * in question will be used as the locale identifier for its value. To disable
+ * the compile-time checks for plural & selectordinal keys while maintaining
+ * multi-locale support, use falsy values in `plurals`.
+ *
+ * @param {string|object} src - the source for which the JS code should be generated
+ * @param {string} lc - the default locale
+ * @param {object} plurals - a map of pluralization keys for all available locales
+ */
+Compiler.prototype.compile = function( src, lc ) {
+ this.lc = lc;
+
+ // TODO: pc is only needed for validation, disable for now.
+ var pc = { cardinal: [], ordinal: [] };
+
+ // Enable icu-compatible function parameter parsing so that commas
+ // can be used inside custom date formats.
+ pc.strictFunctionParams = true;
+ var r = Parser.parse( src, pc ).map( function( token ) { return this.token( token ); }, this );
+ return "function(d) { return " + ( r.join( " + " ) || "\"\"" ) + "; }";
+};
+
+return Compiler;
+
+});
diff --git a/src/message/formatter-fn.js b/src/message/formatter-fn.js
index 720f481cb..a0205a258 100644
--- a/src/message/formatter-fn.js
+++ b/src/message/formatter-fn.js
@@ -2,7 +2,10 @@ define([
"../common/validate/parameter-type/message-variables"
], function( validateParameterTypeMessageVariables ) {
-return function( formatter ) {
+return function( formatter, shouldCall ) {
+ if ( shouldCall === "call" ) {
+ formatter = formatter.apply( null, [].slice.call( arguments, 2 ) );
+ }
return function messageFormatter( variables ) {
if ( typeof variables === "number" || typeof variables === "string" ) {
variables = [].slice.call( arguments, 0 );
diff --git a/src/message/formatter-runtime-bind.js b/src/message/formatter-runtime-bind.js
index c2b285d65..ff57f02d0 100644
--- a/src/message/formatter-runtime-bind.js
+++ b/src/message/formatter-runtime-bind.js
@@ -1,43 +1,35 @@
define(function() {
-return function( cldr, messageformatter ) {
- var locale = cldr.locale,
- origToString = messageformatter.toString;
+return function( messageformatter, formatterSrc, runtime, pluralType, locale, formatters ) {
+ var hasFormatters = formatters.length > 0;
messageformatter.toString = function() {
- var argNames, argValues, output,
- args = {};
+ var locals = [];
+ var argCount = 0,
+ args = [];
- // Properly adjust SlexAxton/messageformat.js compiled variables with Globalize variables:
- output = origToString.call( messageformatter );
+ if ( runtime.number ) {
+ locals.push( "var number = messageFormat.number;" );
+ }
- if ( /number\(/.test( output ) ) {
- args.number = "messageFormat.number";
+ if ( runtime.plural ) {
+ locals.push( "var plural = messageFormat.plural;" );
}
- if ( /plural\(/.test( output ) ) {
- args.plural = "messageFormat.plural";
+ if ( runtime.select ) {
+ locals.push( "var select = messageFormat.select;" );
}
- if ( /select\(/.test( output ) ) {
- args.select = "messageFormat.select";
+ if ( pluralType !== false ) {
+ args.push( locale );
+ argCount++;
}
- output.replace( /pluralFuncs(\[([^\]]+)\]|\.([a-zA-Z]+))/, function( match ) {
- args.pluralFuncs = "{" +
- "\"" + locale + "\": Globalize(\"" + locale + "\").pluralGenerator()" +
- "}";
- return match;
- });
-
- argNames = Object.keys( args ).join( ", " );
- argValues = Object.keys( args ).map(function( key ) {
- return args[ key ];
- }).join( ", " );
-
- return "(function( " + argNames + " ) {\n" +
- " return " + output + "\n" +
- "})(" + argValues + ")";
+ return "(function( " + args.join( ", " ) + " ) {\n" +
+ ( locals.length ? ( locals.join( "\n" ) + "\n" ) : "" ) +
+ ( hasFormatters ? " var fmt = [].slice.call( arguments, " + argCount + " );\n" : "" ) +
+ " return " + formatterSrc + "\n" +
+ "})";
};
return messageformatter;
diff --git a/src/message/formatter-runtime.js b/src/message/formatter-runtime.js
new file mode 100644
index 000000000..c2c53d239
--- /dev/null
+++ b/src/message/formatter-runtime.js
@@ -0,0 +1,92 @@
+define([], function() {
+
+function Runtime( strictNumberSign ) {
+ this.setStrictNumber( strictNumberSign );
+}
+
+/** Utility function for `#` in plural rules
+ *
+ * Will throw an Error if `value` has a non-numeric value and `offset` is
+ * non-zero or {@link MessageFormat#setStrictNumberSign} is set.
+ *
+ * @function Runtime#number
+ * @param {number} value - The value to operate on
+ * @param {string} name - The name of the argument, used for error reporting
+ * @param {number} [offset=0] - An optional offset, set by the surrounding context
+ * @returns {number|string} The result of applying the offset to the input value
+ */
+function defaultNumber( value, name, offset ) {
+ if ( !offset ) {
+ return value;
+ }
+ if ( isNaN( value ) ) {
+ throw new Error( "Can't apply offset:" + offset + " to argument `" + name +
+ "` with non-numerical value " + JSON.stringify( value ) + "." );
+ }
+ return value - offset;
+}
+
+/** @private */
+function strictNumber( value, name, offset ) {
+ if ( isNaN( value ) ) {
+ throw new Error( "Argument `" + name + "` has non-numerical value " +
+ JSON.stringify( value ) + "." );
+ }
+ return value - ( offset || 0 );
+}
+
+/** Set how strictly the {@link number} method parses its input.
+ *
+ * According to the ICU MessageFormat spec, `#` can only be used to replace a
+ * number input of a `plural` statement. By default, messageformat.js does not
+ * throw a runtime error if you use non-numeric argument with a `plural` rule,
+ * unless rule also includes a non-zero `offset`.
+ *
+ * This is called by {@link MessageFormat#setStrictNumberSign} to follow the
+ * stricter ICU MessageFormat spec.
+ *
+ * @param {boolean} [enable=false]
+ */
+Runtime.prototype.setStrictNumber = function( enable ) {
+ this.number = enable ? strictNumber : defaultNumber;
+};
+
+/** Utility function for `{N, plural|selectordinal, ...}`
+ *
+ * @param {number} value - The key to use to find a pluralization rule
+ * @param {number} offset - An offset to apply to `value`
+ * @param {function} lcfunc - A locale function from `pluralFuncs`
+ * @param {Object.} data - The object from which results are looked up
+ * @param {?boolean} isOrdinal - If true, use ordinal rather than cardinal rules
+ * @returns {string} The result of the pluralization
+ */
+Runtime.prototype.plural = function( value, offset, lcfunc, data, isOrdinal ) {
+ if ( {}.hasOwnProperty.call( data, value ) ) {
+ return data[value];
+ }
+ if ( offset ) {
+ value -= offset;
+ }
+ var key = lcfunc( value, isOrdinal );
+ if ( key in data ) {
+ return data[key];
+ }
+ return data.other;
+};
+
+/** Utility function for `{N, select, ...}`
+ *
+ * @param {number} value - The key to use to find a selection
+ * @param {Object.} data - The object from which results are looked up
+ * @returns {string} The result of the select statement
+ */
+Runtime.prototype.select = function( value, data ) {
+ if ( {}.hasOwnProperty.call( data, value ) ) {
+ return data[value];
+ }
+ return data.other;
+};
+
+return Runtime;
+
+});
diff --git a/src/number.js b/src/number.js
index 4922e4b27..f4bef6870 100644
--- a/src/number.js
+++ b/src/number.js
@@ -100,6 +100,14 @@ Globalize.prototype.numberFormatter = function( options ) {
return returnFn;
};
+Globalize.addMessageFormatterFunction( "number", function( style ) {
+ var options = {};
+ if ( style ) {
+ options.style = style;
+ }
+ return this.numberFormatter( options );
+});
+
/**
* .numberParser( [options] )
*
diff --git a/src/plural.js b/src/plural.js
index b9781546c..9895f0b2b 100644
--- a/src/plural.js
+++ b/src/plural.js
@@ -48,7 +48,7 @@ Globalize.prototype.plural = function( value, options ) {
*/
Globalize.pluralGenerator =
Globalize.prototype.pluralGenerator = function( options ) {
- var args, cldr, isOrdinal, plural, returnFn, type;
+ var args, cldr, isOrdinal, isCardinal, plural, returnFn, type;
validateParameterTypePlainObject( options, "options" );
@@ -62,18 +62,29 @@ Globalize.prototype.pluralGenerator = function( options ) {
validateDefaultLocale( cldr );
- isOrdinal = type === "ordinal";
+ isOrdinal = type === "ordinal" || type === "both";
+ isCardinal = type === "cardinal" || type === "both";
cldr.on( "get", validateCldr );
- cldr.supplemental([ "plurals-type-" + type, "{language}" ]);
+ if ( isOrdinal ) {
+ cldr.supplemental([ "plurals-type-ordinal", "{language}" ]);
+ }
+ if ( isCardinal ) {
+ cldr.supplemental([ "plurals-type-cardinal", "{language}" ]);
+ }
cldr.off( "get", validateCldr );
MakePlural.rules = {};
- MakePlural.rules[ type ] = cldr.supplemental( "plurals-type-" + type );
+ if ( isOrdinal ) {
+ MakePlural.rules.ordinal = cldr.supplemental( "plurals-type-ordinal" );
+ }
+ if ( isCardinal ) {
+ MakePlural.rules.cardinal = cldr.supplemental( "plurals-type-cardinal" );
+ }
plural = new MakePlural( cldr.attributes.language, {
"ordinals": isOrdinal,
- "cardinals": !isOrdinal
+ "cardinals": isCardinal
});
returnFn = pluralGeneratorFn( plural );
diff --git a/src/plural/generator-fn.js b/src/plural/generator-fn.js
index 3c052396c..79cc58551 100644
--- a/src/plural/generator-fn.js
+++ b/src/plural/generator-fn.js
@@ -4,11 +4,11 @@ define([
], function( validateParameterPresence, validateParameterTypeNumber ) {
return function( plural ) {
- return function pluralGenerator( value ) {
+ return function pluralGenerator( value, ord ) {
validateParameterPresence( value, "value" );
validateParameterTypeNumber( value, "value" );
- return plural( value );
+ return plural( value, ord );
};
};
diff --git a/src/relative-time.js b/src/relative-time.js
index 4c36886af..557d2a2e9 100644
--- a/src/relative-time.js
+++ b/src/relative-time.js
@@ -74,6 +74,14 @@ Globalize.prototype.relativeTimeFormatter = function( unit, options ) {
return returnFn;
};
+Globalize.addMessageFormatterFunction( "relativetime", function( unit, form ) {
+ var options = {};
+ if ( form ) {
+ options.form = form;
+ }
+ return this.relativeTimeFormatter( unit, options );
+});
+
return Globalize;
});
diff --git a/src/unit.js b/src/unit.js
index 024b68e51..7349e22bd 100644
--- a/src/unit.js
+++ b/src/unit.js
@@ -69,6 +69,14 @@ Globalize.prototype.unitFormatter = function( unit, options ) {
return returnFn;
};
+Globalize.addMessageFormatterFunction( "unit", function( unit, form ) {
+ var options = {};
+ if ( form ) {
+ options.form = form;
+ }
+ return this.unitFormatter( unit, options );
+});
+
return Globalize;
});
diff --git a/src/util/formatterfn/options.js b/src/util/formatterfn/options.js
new file mode 100644
index 000000000..965648258
--- /dev/null
+++ b/src/util/formatterfn/options.js
@@ -0,0 +1,19 @@
+define(function() {
+
+return function( fn, options ) {
+ options = options || {};
+ return function( ) {
+ var args = [].slice.call( arguments, 0 );
+ if ( args.length === 1 && ( options.split === undefined || options.split === true ) ) {
+ args = args[0].split( "," );
+ }
+ if ( options.trim === undefined || options.trim === true ) {
+ args = args.map( function( v ) {
+ return v.trim();
+ });
+ }
+ return fn.apply( this, args );
+ };
+};
+
+});
diff --git a/test/compiler/cases/message.js b/test/compiler/cases/message.js
index 16e81e883..2fe2e8d93 100644
--- a/test/compiler/cases/message.js
+++ b/test/compiler/cases/message.js
@@ -4,13 +4,89 @@ module.exports = {
Globalize.load(
// core
- require( "../../../external/cldr-data/supplemental/likelySubtags.json" )
+ require( "../../../external/cldr-data/supplemental/likelySubtags.json" ),
+ // date
+ require( "../../../external/cldr-data/main/en/ca-gregorian.json" ),
+ require( "../../../external/cldr-data/main/en/timeZoneNames.json" ),
+ require( "../../../external/cldr-data/supplemental/metaZones.json" ),
+ require( "../../../external/cldr-data/supplemental/timeData.json" ),
+ require( "../../../external/cldr-data/supplemental/weekData.json" ),
+ // number
+ require( "../../../external/cldr-data/main/en/numbers.json" ),
+ require( "../../../external/cldr-data/supplemental/numberingSystems.json" ),
+ // currency
+ require( "../../../external/cldr-data/main/en/currencies.json" ),
+ require( "../../../external/cldr-data/supplemental/currencyData.json" ),
+ // plural
+ require( "../../../external/cldr-data/supplemental/plurals.json" ),
+ require( "../../../external/cldr-data/supplemental/ordinals.json" ),
+ // relative time
+ require( "../../../external/cldr-data/main/en/dateFields.json" ),
+ // unit
+ require( "../../../external/cldr-data/main/en/units.json" )
);
Globalize.loadMessages({
en: {
greetings: {
- hello: "Hello, {name}"
+ hello: "Hello",
+ helloArray: "Hello, {0}",
+ helloArray2: "Hello, {0} and {1}",
+ helloName: "Hello, {name}"
+ },
+ like: [
+ "{count, plural, offset:1",
+ " =0 {Be the first to like this}",
+ " =1 {You liked this}",
+ " one {You and {someone} liked this}",
+ " other {You and # others liked this}",
+ "}"
+ ],
+ party: [
+ "{hostGender, select,",
+ " female {{host} invites {guest} to her party}",
+ " male {{host} invites {guest} to his party}",
+ " other {{host} invites {guest} to their party}",
+ "}"
+ ],
+ task: [
+ "You have {0, plural,",
+ " one {one task}",
+ " other {# tasks}",
+ "} remaining"
+ ],
+ ordinal: [
+ "{cat, selectordinal, one{#st} two{#nd} few{#rd} other{#th} }",
+ "category"
+ ],
+ date: {
+ date: "date: {x, date, long}",
+ time: "time: {x, time, long}",
+ datetime: "datetime: {x, datetime, long}",
+ raw: "date raw: {x, date, y-M-d HH:mm:ss zzzz }",
+ rawComma: "date raw comma: {x, date, y-M-d, HH:mm:ss zzzz }",
+ skeleton: "date skeleton: {x, date, skeleton, GyMMMEdhms}",
+ skeletonInvalid: "date skeleton: {x, date, skeleton}"
+ },
+ relativetime: {
+ default: "relativetime: {x, relativetime, minute}",
+ short: "relativetime short: {x, relativetime, minute, short}",
+ narrow: "relativetime narrow: {x, relativetime, minute, narrow}"
+ },
+ number: {
+ decimal: "number decimal: {x, number}",
+ percent: "number percent: {x, number, percent}"
+ },
+ currency: {
+ symbol: "currency symbol: {x, currency, USD}",
+ accounting: "currency accounting: {x, currency, USD, accounting}",
+ code: "currency code: {x, currency, USD, code}",
+ name: "currency name: {x, currency, USD, name}"
+ },
+ unit: {
+ long: "unit long: {x, unit, second, long}",
+ short: "unit short: {x, unit, second, short}",
+ narrow: "unit narrow: {x, unit, second, narrow}"
}
}
});
@@ -18,11 +94,142 @@ module.exports = {
return Globalize;
},
cases: function( Globalize ) {
+ var date = new Date( 2010, 8, 15, 17, 35, 7, 369 );
+
Globalize.locale( "en" );
return [
- { formatter: Globalize( "en" ).messageFormatter( "greetings/hello" ), args: [ {
- name: "Beethoven"
- } ] }
+ {
+ formatter: Globalize( "en" ).messageFormatter( "greetings/hello" ),
+ args: []
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "greetings/helloArray" ),
+ args: [ "Beethoven" ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "greetings/helloArray2" ),
+ args: [ [ "Beethoven", "Mozart" ] ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "greetings/helloName" ),
+ args: [ { name: "Beethoven" } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "task" ),
+ args: [ 123 ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "party" ),
+ args: [ {
+ guest: "Mozart",
+ host: "Beethoven",
+ hostGender: "male"
+ } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "like" ),
+ args: [ { count: 0 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "like" ),
+ args: [ { count: 1 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "like" ),
+ args: [ { count: 2, someone: "Beethoven" } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "like" ),
+ args: [ { count: 3 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "ordinal" ),
+ args: [ { cat: 1 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "ordinal" ),
+ args: [ { cat: 2 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "ordinal" ),
+ args: [ { cat: 3 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "ordinal" ),
+ args: [ { cat: 4 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "date/date" ),
+ args: [ { x: date } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "date/time" ),
+ args: [ { x: date } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "date/datetime" ),
+ args: [ { x: date } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "relativetime/default" ),
+ args: [ { x: 2 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "relativetime/default" ),
+ args: [ { x: -2 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "relativetime/short" ),
+ args: [ { x: 2 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "relativetime/short" ),
+ args: [ { x: -2 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "relativetime/narrow" ),
+ args: [ { x: 2 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "relativetime/narrow" ),
+ args: [ { x: -2 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "number/decimal" ),
+ args: [ { x: 0.5 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "number/percent" ),
+ args: [ { x: 0.5 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "currency/symbol" ),
+ args: [ { x: 100 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "currency/accounting" ),
+ args: [ { x: 100 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "currency/code" ),
+ args: [ { x: 100 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "currency/name" ),
+ args: [ { x: 100 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "unit/long" ),
+ args: [ { x: 42 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "unit/short" ),
+ args: [ { x: 42 } ]
+ },
+ {
+ formatter: Globalize( "en" ).messageFormatter( "unit/narrow" ),
+ args: [ { x: 42 } ]
+ },
];
}
};
diff --git a/test/functional/message/format-message.js b/test/functional/message/format-message.js
index 5cd71f2be..3c1f2dd16 100644
--- a/test/functional/message/format-message.js
+++ b/test/functional/message/format-message.js
@@ -2,16 +2,18 @@ define([
"globalize",
"json!cldr-data/supplemental/likelySubtags.json",
"json!cldr-data/supplemental/plurals.json",
+ "json!cldr-data/supplemental/ordinals.json",
"../../util",
"globalize/message",
"globalize/plural"
-], function( Globalize, likelySubtags, plurals, util ) {
+], function( Globalize, likelySubtags, plurals, ordinals, util ) {
QUnit.module( ".formatMessage( path [, variables] )", {
setup: function() {
Globalize.load( likelySubtags );
Globalize.load( plurals );
+ Globalize.load( ordinals );
Globalize.loadMessages({
en: {
greetings: {
diff --git a/test/functional/message/message-formatter.js b/test/functional/message/message-formatter.js
index 08224fd90..b2757a12e 100644
--- a/test/functional/message/message-formatter.js
+++ b/test/functional/message/message-formatter.js
@@ -1,13 +1,43 @@
define([
"globalize",
"json!cldr-data/supplemental/likelySubtags.json",
+ "json!cldr-data/supplemental/numberingSystems.json",
+ "json!cldr-data/supplemental/currencyData.json",
"json!cldr-data/supplemental/plurals.json",
+ "json!cldr-data/supplemental/ordinals.json",
+ "json!cldr-data/supplemental/timeData.json",
+ "json!cldr-data/supplemental/weekData.json",
+ "json!cldr-data/main/en/numbers.json",
+ "json!cldr-data/main/en/units.json",
+ "json!cldr-data/main/en/currencies.json",
+ "json!cldr-data/main/en/ca-gregorian.json",
+ "json!cldr-data/main/en/timeZoneNames.json",
+ "json!cldr-data/main/en/dateFields.json",
+ "json!iana-tz-data.json",
"../../util",
"cldr/unresolved",
"globalize/message",
"globalize/plural"
-], function( Globalize, likelySubtags, plurals, util ) {
+], function( Globalize, likelySubtags, numberingSystems, currencyData, plurals,
+ ordinals, timeData, weekData, enNumbers, enUnitFields, enCurrencies, enCaGregorian,
+ enTimeZoneNames, enDateFields, ianaTimezoneData, util ) {
+
+function extraSetup() {
+ Globalize.load(
+ numberingSystems,
+ currencyData,
+ enNumbers,
+ enUnitFields,
+ enCurrencies,
+ timeData,
+ weekData,
+ enCaGregorian,
+ enTimeZoneNames,
+ enDateFields
+ );
+ Globalize.loadTimeZone( ianaTimezoneData );
+}
QUnit.assert.messageFormatter = function( locale, path, variables, expected ) {
if ( arguments.length === 3 ) {
@@ -28,6 +58,7 @@ QUnit.module( ".messageFormatter( path )", {
setup: function() {
Globalize.load( likelySubtags );
Globalize.load( plurals );
+ Globalize.load( ordinals );
Globalize.loadMessages({
root: {
amen: "Amen"
@@ -60,7 +91,40 @@ QUnit.module( ".messageFormatter( path )", {
" one {one task}",
" other {# tasks}",
"} remaining"
- ]
+ ],
+ ordinal: [
+ "{cat, selectordinal, one{#st} two{#nd} few{#rd} other{#th} }",
+ "category"
+ ],
+ date: {
+ date: "date: {x, date, long}",
+ time: "time: {x, time, long}",
+ datetime: "datetime: {x, datetime, long}",
+ raw: "date raw: {x, date, y-M-d HH:mm:ss zzzz }",
+ rawComma: "date raw comma: {x, date, y-M-d, HH:mm:ss zzzz }",
+ skeleton: "date skeleton: {x, date, skeleton, GyMMMEdhms}",
+ skeletonInvalid: "date skeleton: {x, date, skeleton}"
+ },
+ relativetime: {
+ default: "relativetime: {x, relativetime, minute}",
+ short: "relativetime short: {x, relativetime, minute, short}",
+ narrow: "relativetime narrow: {x, relativetime, minute, narrow}"
+ },
+ number: {
+ decimal: "number decimal: {x, number}",
+ percent: "number percent: {x, number, percent}"
+ },
+ currency: {
+ symbol: "currency symbol: {x, currency, USD}",
+ accounting: "currency accounting: {x, currency, USD, accounting}",
+ code: "currency code: {x, currency, USD, code}",
+ name: "currency name: {x, currency, USD, name}"
+ },
+ unit: {
+ long: "unit long: {x, unit, second, long}",
+ short: "unit short: {x, unit, second, short}",
+ narrow: "unit narrow: {x, unit, second, narrow}"
+ }
},
"en-GB": {},
fr: {},
@@ -173,6 +237,100 @@ QUnit.test( "should support ICU message format", function( assert ) {
}), "You and Beethoven liked this" );
assert.equal( like({ count: 3 }), "You and 2 others liked this" );
+
+ // Selectordinal
+ assert.messageFormatter( "en", "ordinal", {
+ cat: 1,
+ }, "1st category" );
+
+ assert.messageFormatter( "en", "ordinal", {
+ cat: 2,
+ }, "2nd category" );
+
+ assert.messageFormatter( "en", "ordinal", {
+ cat: 3,
+ }, "3rd category" );
+
+ assert.messageFormatter( "en", "ordinal", {
+ cat: 4,
+ }, "4th category" );
+});
+
+QUnit.test( "should support formatters in messages", function( assert ) {
+ extraSetup();
+
+ var date = new Date( 2010, 8, 15, 17, 35, 7, 369 );
+
+ assert.messageFormatter( "en", "date/date", {
+ x: date,
+ }, "date: September 15, 2010" );
+ assert.messageFormatter( "en", "date/time", {
+ x: date,
+ }, "time: 5:35:07 PM GMT+2" );
+ assert.messageFormatter( "en", "date/datetime", {
+ x: date,
+ }, "datetime: September 15, 2010 at 5:35:07 PM GMT+2" );
+ assert.messageFormatter( "en", "date/raw", {
+ x: date,
+ }, "date raw: 2010-9-15 17:35:07 GMT+02:00 " );
+ assert.messageFormatter( "en", "date/rawComma", {
+ x: date,
+ }, "date raw comma: 2010-9-15, 17:35:07 GMT+02:00 " );
+ assert.messageFormatter( "en", "date/skeleton", {
+ x: date,
+ }, "date skeleton: Wed, Sep 15, 2010 AD, 5:35:07 PM" );
+ assert.messageFormatter( "en", "date/skeletonInvalid", {
+ x: date,
+ }, "date skeleton: 7174l4ton" );
+
+ assert.messageFormatter( "en", "relativetime/default", {
+ x: 2,
+ }, "relativetime: in 2 minutes" );
+ assert.messageFormatter( "en", "relativetime/default", {
+ x: -2,
+ }, "relativetime: 2 minutes ago" );
+ assert.messageFormatter( "en", "relativetime/short", {
+ x: 2,
+ }, "relativetime short: in 2 min." );
+ assert.messageFormatter( "en", "relativetime/short", {
+ x: -2,
+ }, "relativetime short: 2 min. ago" );
+ assert.messageFormatter( "en", "relativetime/narrow", {
+ x: 2,
+ }, "relativetime narrow: in 2 min." );
+ assert.messageFormatter( "en", "relativetime/narrow", {
+ x: -2,
+ }, "relativetime narrow: 2 min. ago" );
+
+ assert.messageFormatter( "en", "number/decimal", {
+ x: 0.5,
+ }, "number decimal: 0.5" );
+ assert.messageFormatter( "en", "number/percent", {
+ x: 0.5,
+ }, "number percent: 50%" );
+
+ assert.messageFormatter( "en", "currency/symbol", {
+ x: 100,
+ }, "currency symbol: $100.00" );
+ assert.messageFormatter( "en", "currency/accounting", {
+ x: 100,
+ }, "currency accounting: $100.00" );
+ assert.messageFormatter( "en", "currency/code", {
+ x: 100,
+ }, "currency code: 100.00 USD" );
+ assert.messageFormatter( "en", "currency/name", {
+ x: 100,
+ }, "currency name: 100.00 US dollars" );
+
+ assert.messageFormatter( "en", "unit/long", {
+ x: 42,
+ }, "unit long: 42 seconds" );
+ assert.messageFormatter( "en", "unit/short", {
+ x: 42,
+ }, "unit short: 42 sec" );
+ assert.messageFormatter( "en", "unit/narrow", {
+ x: 42,
+ }, "unit narrow: 42s" );
});
// Reference #473