diff --git a/Gruntfile.js b/Gruntfile.js
index 23380afa4..9a54e3e3f 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -106,7 +106,8 @@ module.exports = function( grunt ) {
optimize: "none",
paths: {
cldr: "../external/cldrjs/dist/cldr",
- "make-plural": "../external/make-plural/make-plural"
+ "make-plural": "../external/make-plural/make-plural",
+ messageformat: "../external/messageformat/messageformat"
},
skipSemiColonInsertion: true,
skipModuleInsertion: true,
@@ -121,16 +122,8 @@ module.exports = function( grunt ) {
onBuildWrite: function( id, path, contents ) {
var name = camelCase( id.replace( /util\/|common\//, "" ) );
- // CLDRPluralRuleParser
- if ( (/CLDRPluralRuleParser/).test( id ) ) {
- return contents
-
- // Replace UMD wrapper into var assignment.
- .replace( /\(function\(root, factory\)[\s\S]*?}\(this, function\(\) {/, "var CLDRPluralRuleParser = (function() {" )
- .replace( /}\)\);\s+$/, "}());" );
-
// MakePlural
- } else if ( (/make-plural/).test( id ) ) {
+ if ( (/make-plural/).test( id ) ) {
return contents
// Replace its wrapper into var assignment.
@@ -156,6 +149,22 @@ module.exports = function( grunt ) {
// Remove MakePlural.load = function(.*) {...return MakePlural;.*};
.replace( /MakePlural.load = function\([\s\S]*?return MakePlural;\n};/, "" );
+
+ // messageformat
+ } else if ( (/messageformat/).test( id ) ) {
+ return contents
+
+ // Replace its wrapper into var assignment.
+ .replace( /\(function \( root \) {/, [
+ "var MessageFormat;",
+ "/* jshint ignore:start */",
+ "MessageFormat = (function() {"
+ ].join( "\n" ) )
+ .replace( /if \(typeof exports !== 'undefined'[\s\S]*/, [
+ "return MessageFormat;",
+ "}());",
+ "/* jshint ignore:end */"
+ ].join( "\n" ) );
}
// 1, and 2: Remove define() wrap.
@@ -358,7 +367,7 @@ module.exports = function( grunt ) {
"jscs:source",
// TODO fix issues, enable
- // "jscs:test",
+ //"jscs:test",
"test:unit",
"clean",
"requirejs",
diff --git a/README.md b/README.md
index 302409276..a46ae85d4 100644
--- a/README.md
+++ b/README.md
@@ -100,7 +100,7 @@ information on its usage.
| globalize.js | 1.3KB | [Core library](#core) |
| globalize/currency.js | +2.6KB | [Currency module](#currency_module) provides currency formatting and parsing |
| globalize/date.js | +4.8KB | [Date module](#date_module) provides date formatting and parsing |
-| globalize/message.js | +0.5KB | [Message module](#message_module) provides message translation |
+| globalize/message.js | +5.5KB | [Message module](#message_module) provides ICU message format support |
| globalize/number.js | +2.9KB | [Number module](#number_module) provides number formatting and parsing |
| globalize/plural.js | +1.7KB | [Plural module](#plural_module) provides pluralization support |
@@ -273,17 +273,23 @@ to you in different flavors):
### Message module
-- **`Globalize.loadTranslations( json )`**
+- **`Globalize.loadMessages( json )`**
- Load translation data.
+ Load messages data.
- [Read more...](doc/api/message/load-translation.md)
+ [Read more...](doc/api/message/load-messages.md)
-- **`.translate( path )`**
+- **`.messageFormatter( path ) ➡ function([ variables ])`**
- Translate item given its path.
+ Return a function that formats a message (using ICU message format pattern)
+ given its path and a set of variables into a user-readable string. It supports
+ pluralization and gender inflections.
- [Read more...](doc/api/message/translate.md)
+ [Read more...](doc/api/message/message-formatter.md)
+
+- **`.formatMessage( path [, variables ] )`**
+
+ Alias to `.messageFormatter( path )([ variables ])`.
### Number module
diff --git a/bower.json b/bower.json
index 6df61372d..102a6f61b 100644
--- a/bower.json
+++ b/bower.json
@@ -15,6 +15,7 @@
"devDependencies": {
"make-plural": "eemeli/make-plural.js#2.1.2",
"es5-shim": "3.4.0",
+ "messageformat": "SlexAxton/messageformat.js#debeaf4",
"qunit": "1.12.0",
"requirejs": "2.1.9",
"requirejs-plugins": "1.0.2",
diff --git a/doc/api/message/load-messages.md b/doc/api/message/load-messages.md
new file mode 100644
index 000000000..2be58174f
--- /dev/null
+++ b/doc/api/message/load-messages.md
@@ -0,0 +1,102 @@
+## .loadMessages( json )
+
+Load messages data.
+
+The first level of keys must be locales. For example:
+
+```
+{
+ en: {
+ hello: "Hello"
+ },
+ pt: {
+ hello: "Olá"
+ }
+}
+```
+
+ICU MessageFormat pattern is supported: variable replacement, gender and plural
+inflections. For more information see [`.messageFormatter( path ) ➡ function([
+variables ])`](./message-formatter.md).
+
+The provided messages are stored along side other cldr data, under the
+"globalize-messages" key. This allows Globalize to reuse the traversal methods
+provided by cldrjs. You can inspect this data using
+`cldrjs.get("globalize-messages")`.
+
+### Parameters
+
+**json**
+
+JSON object of messages data. Keys can use any character, except `/`, `{` and
+`}`. Values (i.e., the message content itself) can contain any character.
+
+### Example
+
+```javascript
+Globalize.loadMessages({
+ pt: {
+ greetings: {
+ hello: "Olá",
+ bye: "Tchau"
+ }
+ }
+});
+
+Globalize( "pt" ).formatMessage( "greetings/hello" );
+➡ Olá
+```
+
+#### Multiline strings
+
+Use Arrays as a convenience for multiline strings. The lines will be joined by a
+space.
+
+```javascript
+Globalize.loadMessages({
+ en: {
+ longText: [
+ "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eligendi non",
+ "quis exercitationem culpa nesciunt nihil aut nostrum explicabo",
+ "reprehenderit optio amet ab temporibus asperiores quasi cupiditate.",
+ "Voluptatum ducimus voluptates voluptas?"
+ ]
+ }
+});
+
+Globalize( "en" ).formatMessage( "longText" );
+➡ "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eligendi non quis exercitationem culpa nesciunt nihil aut nostrum explicabo reprehenderit optio amet ab temporibus asperiores quasi cupiditate. Voluptatum ducimus voluptates voluptas?"
+```
+
+#### Messages inheritance
+
+It's possible to inherit messages, for example:
+
+```javascript
+Globalize.loadMessages({
+ root: {
+ amen: "Amen"
+ },
+ pt: {
+ amen: "Amém"
+ }
+});
+
+Globalize( "pt-PT" ).formatMessage( "amen" ); // "Amém"
+Globalize( "de" ).formatMessage( "amen" ); // "Amen"
+Globalize( "en" ).formatMessage( "amen" ); // "Amen"
+Globalize( "en-GB" ).formatMessage( "amen" ); // "Amen"
+Globalize( "fr" ).formatMessage( "amen" ); // "Amen"
+```
+
+Note that `pt-PT`, `de`, `en`, `en-GB`, and `fr` have never been defined.
+`.formatMessage()` inherits `pt-PT` messages from `pt` (`pt-PT` ➡ `pt`), and
+it inherits the other messages from root, eg. `en-GB` ➡ `en` ➡ `root`. Yes,
+`root` is the last bundle of the parent lookup.
+
+Attention: On browsers, message inheritance only works if the optional
+dependency `cldr/unresolved` is loaded.
+
+```html
+
+```
diff --git a/doc/api/message/load-translation.md b/doc/api/message/load-translation.md
deleted file mode 100644
index ca2c84f7e..000000000
--- a/doc/api/message/load-translation.md
+++ /dev/null
@@ -1,68 +0,0 @@
-## .loadTranslations( json )
-
-Load translation data.
-
-The first dregree keys must be locales. For example:
-
-```
-{
- en: {
- hello: "Hello"
- },
- pt: {
- hello: "Olá"
- }
-}
-```
-
-### Parameters
-
-**json**
-
-JSON object of translation data.
-
-### Example
-
-```javascript
-Globalize.loadTranslations({
- pt: {
- greetings: {
- hello: "Olá",
- bye: "Tchau"
- }
- }
-});
-
-Globalize( "pt" ).translate( "greetings/hello" ); // Olá
-```
-
-It's possible to inherit translations, for example:
-
-```javascript
-Globalize.loadTranslations({
- root: {
- amen: "Amen"
- },
- pt: {
- amen: "Amém"
- }
-});
-
-Globalize( "pt-PT" ).translate( "amen" ); // "Amém"
-Globalize( "de" ).translate( "amen" ); // "Amen"
-Globalize( "en" ).translate( "amen" ); // "Amen"
-Globalize( "en-GB" ).translate( "amen" ); // "Amen"
-Globalize( "fr" ).translate( "amen" ); // "Amen"
-```
-
-Note that `pt-PT`, `de`, `en`, `en-GB`, and `fr` have never been defined.
-`.translate()` inherits `pt-PT` translation from `pt` (`pt-PT` ➡ `pt`), and it
-inherits the other translations from root, eg. `en-GB` ➡ `en` ➡ `root`. Yes,
-`root` is the last bundle of the parent lookup.
-
-Attention: On browsers, translation inheritance only works if the optional
-dependency `cldr/unresolved` is loaded.
-
-```html
-
-```
diff --git a/doc/api/message/message-formatter.md b/doc/api/message/message-formatter.md
new file mode 100644
index 000000000..420cd11fb
--- /dev/null
+++ b/doc/api/message/message-formatter.md
@@ -0,0 +1,216 @@
+## .messageFormatter( path ) ➡ function([ variables ])
+
+Return a function that formats a message (using ICU message format pattern)
+given its path and a set of variables into a user-readable string. It supports
+pluralization and gender inflections.
+
+Use [`Globalize.loadMessages( json )`](./load-messages.md) to load
+messages data.
+
+### Parameters
+
+**path**
+
+String or Array containing the path of the message content, eg.
+`"greetings/bye"`, or `[ "greetings", "bye" ]`.
+
+**variables** (optional)
+
+Variables can be Objects, where each property can be referenced by name inside a
+message; or Arrays, where each entry of the Array can be used inside a message,
+using numeric indices. When passing one or more arguments of other types,
+they're converted to an Array and used as such.
+
+### Example
+
+You can use the static method `Globalize.messageFormatter()`, which uses the default
+locale.
+
+```javascript
+var formatter;
+
+Globalize.loadMessages({
+ pt: {
+ greetings: {
+ bye: "Tchau"
+ }
+ }
+});
+
+Globalize.locale( "pt" );
+formatter = Globalize.messageFormatter( "greetings/bye" );
+
+formatter();
+➡ "Tchau"
+```
+
+You can use the instance method `.messageFormatter()`, which uses the instance locale.
+
+```javascript
+var pt = new Globalize( "pt" ),
+ formatter = pt.messageFormatter( "greetings/bye" );
+
+formatter();
+➡ "Tchau"
+```
+
+Simple Variable Replacement.
+
+```javascript
+var formatter;
+
+Globalize.loadMessages({
+ en: {
+ hello: "Hello, {0} {1} {2}",
+ hey: "Hey, {first} {middle} {last}"
+ }
+});
+
+formatter = Globalize( "en" ).messageFormatter( "hello" );
+
+// Numbered variables using Array.
+formatter([ "Wolfgang", "Amadeus", "Mozart" ]);
+➡ "Hello, Wolfgang Amadeus Mozart"
+
+// Numbered variables using function arguments.
+formatter( "Wolfgang", "Amadeus", "Mozart" );
+➡ "Hello, Wolfgang Amadeus Mozart"
+
+// Named variables using Object key-value pairs.
+formatter = Globalize( "en" ).messageFormatter( "hey" );
+formatter({
+ first: "Wolfgang",
+ middle: "Amadeus",
+ last: "Mozart"
+});
+➡ "Hey, Wolfgang Amadeus Mozart"
+```
+
+Gender inflections. Note `select` can be used to format any message variations
+that works like a switch.
+
+```javascript
+var formatter;
+
+// Note you can define multiple lines message using an Array of Strings.
+Globalize.loadMessages({
+ en: {
+ party: [
+ "{hostGender, select,",
+ " female {{host} invites {guest} to her party}",
+ " male {{host} invites {guest} to his party}",
+ " other {{host} invites {guest} to their party}",
+ "}"
+ ]
+ }
+});
+
+formatter = Globalize( "en" ).messageFormatter( "party" );
+
+formatter({
+ guest: "Mozart",
+ host: "Beethoven",
+ hostGender: "male"
+});
+➡ "Beethoven invites Mozart to his party"
+```
+
+Plural inflections. It uses the plural forms `zero`, `one`, `two`, `few`,
+`many`, or `other` (required). English only uses `one` and `other`. So,
+including `zero` will never get called, even when the number is 0. For more
+information see [`.pluralGenerator()`](../plural/plural-generator.md).
+
+```javascript
+var numberFormatter, taskFormatter,
+ en = new Globalize( "en" );
+
+// Note you can define multiple lines message using an Array of Strings.
+Globalize.loadMessages({
+ en: {
+ task: [
+ "You have {count, plural,",
+ " one {one task}",
+ " other {{formattedCount} tasks}",
+ "} remaining"
+ ]
+ }
+});
+
+numberFormatter = en.numberFormatter();
+taskFormatter = en.messageFormatter( "task" );
+
+taskFormatter({
+ count: 1000,
+ formattedCount: numberFormatter( 1000 )
+});
+➡ "You have 1,000 tasks remaining"
+```
+
+Literal numeric keys can be used in `plural` to match single, specific numbers.
+
+```javascript
+var taskFormatter,
+ en = new Globalize( "en" );
+
+// Note you can define multiple lines message using an Array of Strings.
+Globalize.loadMessages({
+ en: {
+ task: [
+ "You have {count, plural,",
+ " =0 {no tasks}",
+ " one {one task}",
+ " other {{formattedCount} tasks}",
+ "} remaining"
+ ]
+ }
+});
+
+taskFormatter = Globalize( "en" ).messageFormatter( "task" );
+
+taskFormatter({
+ count: 0,
+ formattedCount: en.numberFormatter( 0 )
+});
+➡ "You have no tasks remaining"
+```
+
+You may find useful having the plural forms calculated with an offset applied.
+Use `#` to output the resulting number. Note literal numeric keys do NOT use the
+offset value.
+
+```javascript
+var likeFormatter,
+ en = new Globalize( "en" );
+
+Globalize.loadMessages({
+ en: {
+ like: [
+ "{0, plural, offset:1",
+ " =0 {Be the first to like this}",
+ " =1 {You liked this}",
+ " one {You and someone else liked this}",
+ " other {You and # others liked this}",
+ "}"
+ ]
+ }
+});
+
+likeFormatter = Globalize( "en" ).messageFormatter( "like" );
+
+likeFormatter( 0 );
+➡ "Be the first to like this"
+
+likeFormatter( 1 );
+➡ "You liked this"
+
+likeFormatter( 2 );
+➡ "You and someone else liked this"
+
+likeFormatter( 3 );
+➡ "You and 2 others liked this"
+```
+
+Read on [SlexAxton/messageFormatter.js][] for more information on regard of ICU
+MessageFormat.
+
+[SlexAxton/messageFormatter.js]: https://github.com/SlexAxton/messageformat.js/#no-frills
diff --git a/doc/api/message/translate.md b/doc/api/message/translate.md
deleted file mode 100644
index 416d65a2b..000000000
--- a/doc/api/message/translate.md
+++ /dev/null
@@ -1,37 +0,0 @@
-## .translate( path )
-
-Translate item given its path.
-
-For translation inheritance, see the [Example section of
-.loadTranslations()](./load-translation.md#example).
-
-### Parameters
-**path**
-
-String or Array containing the translation item path, eg. `"greetings/bye"`, or
-`[ "greetings", "bye" ]`.
-
-### Example
-
-You can use the static method `Globalize.translate()`, which uses the default
-locale.
-
-```javascript
-Globalize.loadTranslations({
- greetings: {
- bye: "Tchau"
- }
-});
-
-Globalize.locale( "pt-BR" );
-Globalize.translate( "greetings/bye" );
-// ➡ "Tchau"
-```
-
-You can use the instance method `.translate()`, which uses the instance locale.
-
-```javascript
-var ptBr = new Globalize( "pt-BR" );
-ptBr.translate( "greetings/bye" );
-// ➡ "Tchau"
-```
diff --git a/examples/amd-bower/index.html b/examples/amd-bower/index.html
index 77a174e64..31f322edb 100644
--- a/examples/amd-bower/index.html
+++ b/examples/amd-bower/index.html
@@ -22,12 +22,14 @@
Demo output
Now:
A number:
A currency:
- Plurals:
+ Plural form of is
+ Messages:
+
+
+
+
+
+
@@ -177,9 +180,23 @@ Demo output
}
}
});
+ Globalize.loadMessages({
+ "en": {
+ "like": [
+ "{0, plural, offset:1",
+ " =0 {Be the first to like this}",
+ " =1 {You liked this}",
+ " one {You and someone else liked this}",
+ " other {You and # others liked this}",
+ "}"
+ ]
+ }
+ });
+
+ var en, like, number;
// Instantiate "en".
- var en = Globalize( "en" );
+ en = Globalize( "en" );
// Use Globalize to format dates.
document.getElementById( "date" ).innerHTML = en.formatDate( new Date(), {
@@ -187,15 +204,22 @@ Demo output
});
// Use Globalize to format numbers.
- document.getElementById( "number" ).innerHTML = en.formatNumber( 12345.6789 );
+ number = en.numberFormatter();
+ document.getElementById( "number" ).innerHTML = number( 12345.6789 );
// Use Globalize to format currencies.
document.getElementById( "currency" ).innerHTML = en.formatCurrency( 69900, "USD" );
// Use Globalize to get the plural form of a numeric value.
- document.getElementById( "plural-0" ).innerHTML = en.plural( 0 );
- document.getElementById( "plural-1" ).innerHTML = en.plural( 1 );
- document.getElementById( "plural-2" ).innerHTML = en.plural( 2 );
+ document.getElementById( "plural-number" ).innerHTML = number( 12345.6789 )
+ document.getElementById( "plural-form" ).innerHTML = en.plural( 12345.6789 );
+
+ // Use Globalize to format a message with plural inflection.
+ like = en.messageFormatter( "like" );
+ document.getElementById( "message-0" ).innerHTML = like( 0 );
+ document.getElementById( "message-1" ).innerHTML = like( 1 );
+ document.getElementById( "message-2" ).innerHTML = like( 2 );
+ document.getElementById( "message-3" ).innerHTML = like( 3 );
document.getElementById( "requirements" ).style.display = "none";
document.getElementById( "demo" ).style.display = "block";
diff --git a/src/build/intro-message.js b/src/build/intro-message.js
index fea296133..65f72261b 100644
--- a/src/build/intro-message.js
+++ b/src/build/intro-message.js
@@ -35,6 +35,8 @@
}(this, function( Cldr, Globalize ) {
var alwaysArray = Globalize._alwaysArray,
+ isPlainObject = Globalize._isPlainObject,
+ validate = Globalize._validate,
validateDefaultLocale = Globalize._validateDefaultLocale,
validateParameterPresence = Globalize._validateParameterPresence,
validateParameterType = Globalize._validateParameterType,
diff --git a/src/common/validate/message-presence.js b/src/common/validate/message-presence.js
new file mode 100644
index 000000000..5f7fd9395
--- /dev/null
+++ b/src/common/validate/message-presence.js
@@ -0,0 +1,11 @@
+define([
+ "../validate"
+], function( validate ) {
+
+return function( path, value ) {
+ path = path.join( "/" );
+ validate( "E_MISSING_MESSAGE", "Missing required message content `{path}`.",
+ value !== undefined, { path: path } );
+};
+
+});
diff --git a/src/common/validate/message-type.js b/src/common/validate/message-type.js
new file mode 100644
index 000000000..185d2b4cf
--- /dev/null
+++ b/src/common/validate/message-type.js
@@ -0,0 +1,18 @@
+define([
+ "../validate"
+], function( validate ) {
+
+return function( path, value ) {
+ path = path.join( "/" );
+ validate(
+ "E_INVALID_MESSAGE",
+ "Invalid message content `{path}`. {expected} expected.",
+ typeof value === "string",
+ {
+ expected: "a string",
+ path: path
+ }
+ );
+};
+
+});
diff --git a/src/common/validate/parameter-type/message-variables.js b/src/common/validate/parameter-type/message-variables.js
new file mode 100644
index 000000000..a0f0d5d09
--- /dev/null
+++ b/src/common/validate/parameter-type/message-variables.js
@@ -0,0 +1,15 @@
+define([
+ "../parameter-type",
+ "../../../util/is-plain-object"
+], function( validateParameterType, isPlainObject ) {
+
+return function( value, name ) {
+ validateParameterType(
+ value,
+ name,
+ value === undefined || isPlainObject( value ) || Array.isArray( value ),
+ "Array or Plain Object"
+ );
+};
+
+});
diff --git a/src/message.js b/src/message.js
index 8ea42a577..e9853e369 100644
--- a/src/message.js
+++ b/src/message.js
@@ -1,24 +1,45 @@
define([
"cldr",
+ "messageformat",
"./core",
+ "./common/create-error",
"./common/validate/default-locale",
+ "./common/validate/message-presence",
+ "./common/validate/message-type",
"./common/validate/parameter-presence",
"./common/validate/parameter-type",
+ "./common/validate/parameter-type/message-variables",
"./common/validate/parameter-type/plain-object",
+ "./common/validate/plural-module-presence",
"./util/always-array"
-], function( Cldr, Globalize, validateDefaultLocale, validateParameterPresence,
- validateParameterType, validateParameterTypePlainObject, alwaysArray ) {
+], function( Cldr, MessageFormat, Globalize, createError, validateDefaultLocale,
+ validateMessagePresence, validateMessageType, validateParameterPresence, validateParameterType,
+ validateParameterTypeMessageVariables, validateParameterTypePlainObject,
+ validatePluralModulePresence, alwaysArray ) {
+
+var slice = [].slice;
+
+function MessageFormatInit( globalize, cldr ) {
+ var plural;
+ return new MessageFormat( cldr.locale, function( value ) {
+ if ( !plural ) {
+ validatePluralModulePresence();
+ plural = globalize.pluralGenerator();
+ }
+ return plural( value );
+ });
+}
/**
- * .loadTranslations( json )
+ * .loadMessages( json )
*
* @json [JSON]
*
* Load translation data.
*/
-Globalize.loadTranslations = function( json ) {
+Globalize.loadMessages = function( json ) {
var customData = {
- "globalize-translations": json
+ "globalize-messages": json
};
validateParameterPresence( json, "json" );
@@ -28,15 +49,15 @@ Globalize.loadTranslations = function( json ) {
};
/**
- * .translate( path )
+ * .messageFormatter( path )
*
* @path [String or Array]
*
- * Translate item given its path.
+ * Format a message given its path.
*/
-Globalize.translate =
-Globalize.prototype.translate = function( path ) {
- var cldr;
+Globalize.messageFormatter =
+Globalize.prototype.messageFormatter = function( path ) {
+ var cldr, formatter, message;
validateParameterPresence( path, "path" );
validateParameterType( path, "path", typeof path === "string" || Array.isArray( path ),
@@ -47,7 +68,38 @@ Globalize.prototype.translate = function( path ) {
validateDefaultLocale( cldr );
- return cldr.get( [ "globalize-translations/{languageId}" ].concat( path ) );
+ message = cldr.get( [ "globalize-messages/{languageId}" ].concat( path ) );
+ validateMessagePresence( path, message );
+
+ // If message is an Array, concatenate it.
+ if ( Array.isArray( message ) ) {
+ message = message.join( " " );
+ }
+ validateMessageType( path, message );
+
+ formatter = MessageFormatInit( this, cldr ).compile( message );
+
+ return function( variables ) {
+ if ( typeof variables === "number" || typeof variables === "string" ) {
+ variables = slice.call( arguments, 0 );
+ }
+ validateParameterTypeMessageVariables( variables, "variables" );
+ return formatter( variables );
+ };
+};
+
+/**
+ * .formatMessage( path [, variables] )
+ *
+ * @path [String or Array]
+ *
+ * @variables [Number, String, Array or Object]
+ *
+ * Format a message given its path.
+ */
+Globalize.formatMessage =
+Globalize.prototype.formatMessage = function( path ) {
+ return this.messageFormatter( path ).apply( {}, slice.call( arguments, 1 ) );
};
return Globalize;
diff --git a/test/functional.js b/test/functional.js
index 84f060f13..893ed9d94 100644
--- a/test/functional.js
+++ b/test/functional.js
@@ -25,7 +25,8 @@ require([
"./functional/date/parse-date",
// message
- "./functional/message/translate",
+ "./functional/message/message-formatter",
+ "./functional/message/format-message",
// number
"./functional/number/number-formatter",
diff --git a/test/functional/message/format-message.js b/test/functional/message/format-message.js
new file mode 100644
index 000000000..065fd3b40
--- /dev/null
+++ b/test/functional/message/format-message.js
@@ -0,0 +1,51 @@
+define([
+ "globalize",
+ "json!cldr-data/supplemental/likelySubtags.json",
+ "json!cldr-data/supplemental/plurals.json",
+ "../../util",
+
+ "cldr/event",
+ "globalize/message",
+ "globalize/plural"
+], function( Globalize, likelySubtags, plurals, util ) {
+
+QUnit.module( ".formatMessage( path [, variables] )", {
+ setup: function() {
+ Globalize.load( likelySubtags );
+ Globalize.load( plurals );
+ Globalize.loadMessages({
+ en: {
+ greetings: {
+ hello: "Hello, {name}"
+ }
+ }
+ });
+ },
+ teardown: util.resetCldrContent
+});
+
+QUnit.test( "should validate parameters", function( assert ) {
+ util.assertParameterPresence( assert, "path", function() {
+ Globalize( "en" ).formatMessage();
+ });
+
+ util.assertPathParameter( assert, "path", function( invalidValue ) {
+ return function() {
+ Globalize( "en" ).formatMessage( invalidValue );
+ };
+ });
+
+ util.assertMessageVariablesType( assert, "variables", function( invalidValue ) {
+ return function() {
+ Globalize( "en" ).formatMessage( "greetings/hello", invalidValue );
+ };
+ });
+});
+
+QUnit.test( "should format a message", function( assert ) {
+ assert.equal( Globalize( "en" ).formatMessage( "greetings/hello", {
+ name: "Beethoven"
+ }), "Hello, Beethoven" );
+});
+
+});
diff --git a/test/functional/message/message-formatter.js b/test/functional/message/message-formatter.js
new file mode 100644
index 000000000..92e166841
--- /dev/null
+++ b/test/functional/message/message-formatter.js
@@ -0,0 +1,156 @@
+define([
+ "globalize",
+ "json!cldr-data/supplemental/likelySubtags.json",
+ "json!cldr-data/supplemental/plurals.json",
+ "../../util",
+
+ "cldr/event",
+ "cldr/unresolved",
+ "globalize/message",
+ "globalize/plural"
+], function( Globalize, likelySubtags, plurals, util ) {
+
+QUnit.assert.messageFormatter = function( locale, path, variables, expected ) {
+ if ( arguments.length === 3 ) {
+ expected = variables;
+ variables = undefined;
+ }
+ this.equal( Globalize( locale ).messageFormatter( path )( variables ), expected );
+};
+
+QUnit.module( ".messageFormatter( path )", {
+ setup: function() {
+ Globalize.load( likelySubtags );
+ Globalize.load( plurals );
+ Globalize.loadMessages({
+ root: {
+ amen: "Amen"
+ },
+ pt: {
+ amen: "Amém"
+ },
+ zh: {
+ amen: "阿门"
+ },
+ en: {
+ greetings: {
+ 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"
+ ]
+ }
+ });
+ },
+ teardown: util.resetCldrContent
+});
+
+QUnit.test( "should validate parameters", function( assert ) {
+ util.assertParameterPresence( assert, "path", function() {
+ Globalize( "en" ).messageFormatter();
+ });
+
+ util.assertPathParameter( assert, "path", function( invalidValue ) {
+ return function() {
+ Globalize( "en" ).messageFormatter( invalidValue );
+ };
+ });
+});
+
+QUnit.test( "should validate messages", function( assert ) {
+ util.assertMessagePresence( assert, "non-existent/path", function() {
+ Globalize( "en" ).messageFormatter( "non-existent/path" );
+ });
+
+ util.assertMessageType( assert, "invalid-message", function( invalidValue ) {
+ Globalize.loadMessages({
+ en: {
+ "invalid-message": invalidValue
+ }
+ });
+ return function() {
+ Globalize( "en" ).messageFormatter( "invalid-message" );
+ };
+ });
+});
+
+QUnit.test( "should return the loaded translation", function( assert ) {
+ assert.messageFormatter( "pt", "amen", "Amém" );
+ assert.messageFormatter( "zh", "amen", "阿门" );
+});
+
+QUnit.test( "should traverse the translation data", function( assert ) {
+ assert.messageFormatter( "en", "greetings/hello", "Hello" );
+ assert.messageFormatter( "en", [ "greetings", "hello" ], "Hello" );
+});
+
+QUnit.test( "should return inherited translation if cldr/unresolved is loaded", function( assert ) {
+ assert.messageFormatter( "en", "amen", "Amen" );
+ assert.messageFormatter( "de", "amen", "Amen" );
+ assert.messageFormatter( "en-GB", "amen", "Amen" );
+ assert.messageFormatter( "fr", "amen", "Amen" );
+ assert.messageFormatter( "pt-PT", "amen", "Amém" );
+});
+
+QUnit.test( "should support ICU message format", function( assert ) {
+ var like;
+
+ // Var replacement
+ assert.messageFormatter( "en", "greetings/helloArray", [ "Beethoven" ], "Hello, Beethoven" );
+ assert.messageFormatter( "en", "greetings/helloArray", "Beethoven", "Hello, Beethoven" );
+ assert.messageFormatter( "en", "greetings/helloArray2", [ "Beethoven", "Mozart" ],
+ "Hello, Beethoven and Mozart" );
+ assert.equal(
+ Globalize( "en" ).messageFormatter( "greetings/helloArray2" )( "Beethoven", "Mozart" ),
+ "Hello, Beethoven and Mozart"
+ );
+ assert.messageFormatter( "en", "greetings/helloName", {
+ name: "Beethoven"
+ }, "Hello, Beethoven" );
+
+ // Plural
+ assert.messageFormatter( "en", "task", 123, "You have 123 tasks remaining" );
+
+ // Select
+ assert.messageFormatter( "en", "party", {
+ guest: "Mozart",
+ host: "Beethoven",
+ hostGender: "male"
+ }, "Beethoven invites Mozart to his party" );
+
+ // Plural offset
+ like = new Globalize( "en" ).messageFormatter( "like" );
+ assert.equal( like({ count: 0 }), "Be the first to like this" );
+
+ assert.equal( like({ count: 1 }), "You liked this" );
+
+ assert.equal( like({
+ count: 2,
+ someone: "Beethoven"
+ }), "You and Beethoven liked this" );
+
+ assert.equal( like({ count: 3 }), "You and 2 others liked this" );
+});
+
+});
diff --git a/test/functional/message/translate.js b/test/functional/message/translate.js
deleted file mode 100644
index bd4427bfd..000000000
--- a/test/functional/message/translate.js
+++ /dev/null
@@ -1,63 +0,0 @@
-define([
- "globalize",
- "json!cldr-data/supplemental/likelySubtags.json",
- "../../util",
-
- "cldr/unresolved",
- "globalize/message"
-], function( Globalize, likelySubtags, util ) {
-
-QUnit.module( ".translate( path )", {
- setup: function() {
- Globalize.load( likelySubtags );
- Globalize.loadTranslations({
- root: {
- amen: "Amen"
- },
- pt: {
- amen: "Amém"
- },
- zh: {
- amen: "阿门"
- },
- en: {
- greetings: {
- hello: "Hello"
- }
- }
- });
- },
- teardown: util.resetCldrContent
-});
-
-QUnit.test( "should validate parameters", function( assert ) {
- util.assertParameterPresence( assert, "path", function() {
- Globalize.translate();
- });
-
- util.assertPathParameter( assert, "path", function( invalidValue ) {
- return function() {
- Globalize.translate( invalidValue );
- };
- });
-});
-
-QUnit.test( "should return the loaded translation", function( assert ) {
- assert.equal( Globalize( "pt" ).translate( "amen" ), "Amém" );
- assert.equal( Globalize( "zh" ).translate( "amen" ), "阿门" );
-});
-
-QUnit.test( "should traverse the translation data", function( assert ) {
- assert.equal( Globalize( "en" ).translate( "greetings/hello" ), "Hello" );
- assert.equal( Globalize( "en" ).translate([ "greetings", "hello" ]), "Hello" );
-});
-
-QUnit.test( "should return inherited translation if cldr/unresolved is loaded", function( assert ) {
- assert.equal( Globalize( "en" ).translate( "amen" ), "Amen" );
- assert.equal( Globalize( "de" ).translate( "amen" ), "Amen" );
- assert.equal( Globalize( "en-GB" ).translate( "amen" ), "Amen" );
- assert.equal( Globalize( "fr" ).translate( "amen" ), "Amen" );
- assert.equal( Globalize( "pt-PT" ).translate( "amen" ), "Amém" );
-});
-
-});
diff --git a/test/unit.js b/test/unit.js
index 86322f715..8d0ed3a9c 100644
--- a/test/unit.js
+++ b/test/unit.js
@@ -33,9 +33,6 @@ require([
"./unit/date/parse",
- // message
- "./unit/message/translate",
-
// number
"./unit/number/pattern-properties",
"./unit/number/format/integer-fraction-digits",
diff --git a/test/unit/message/translate.js b/test/unit/message/translate.js
deleted file mode 100644
index 6b532f240..000000000
--- a/test/unit/message/translate.js
+++ /dev/null
@@ -1,47 +0,0 @@
-define([
- "src/core",
- "json!cldr-data/supplemental/likelySubtags.json",
-
- "cldr/unresolved",
- "src/message"
-], function( Globalize, likelySubtags ) {
-
-Globalize.load( likelySubtags );
-Globalize.loadTranslations({
- root: {
- amen: "Amen"
- },
- pt: {
- amen: "Amém"
- },
- zh: {
- amen: "阿门"
- },
- en: {
- greetings: {
- hello: "Hello"
- }
- }
-});
-
-QUnit.module( "Translate" );
-
-QUnit.test( "should return the loaded translation", function( assert ) {
- assert.equal( Globalize( "pt" ).translate( "amen" ), "Amém" );
- assert.equal( Globalize( "zh" ).translate( "amen" ), "阿门" );
-});
-
-QUnit.test( "should traverse the translation data", function( assert ) {
- assert.equal( Globalize( "en" ).translate( "greetings/hello" ), "Hello" );
- assert.equal( Globalize( "en" ).translate([ "greetings", "hello" ]), "Hello" );
-});
-
-QUnit.test( "should return inherited translation if cldr/unresolved is loaded", function( assert ) {
- assert.equal( Globalize( "en" ).translate( "amen" ), "Amen" );
- assert.equal( Globalize( "de" ).translate( "amen" ), "Amen" );
- assert.equal( Globalize( "en-GB" ).translate( "amen" ), "Amen" );
- assert.equal( Globalize( "fr" ).translate( "amen" ), "Amen" );
- assert.equal( Globalize( "pt-PT" ).translate( "amen" ), "Amém" );
-});
-
-});
diff --git a/test/util.js b/test/util.js
index 54f2f684a..6dbf5a85e 100644
--- a/test/util.js
+++ b/test/util.js
@@ -100,6 +100,27 @@ return {
assertParameterType( assert, [ "cldr", "null", "string" ], name, fn );
},
+ assertMessagePresence: function( assert, path, fn ) {
+ assert.throws( fn, function E_MISSING_PARAMETER( error ) {
+ return error.code === "E_MISSING_MESSAGE" &&
+ error.path === path;
+ }, "Expected \"E_MISSING_MESSAGE: Missing required message content `" + path + "`\" to be thrown" );
+ },
+
+ assertMessageType: function( assert, path, fn ) {
+ Object.keys( allTypes ).filter( not([ "array", "string" ]) ).forEach(function( type ) {
+ assert.throws( fn( allTypes[ type ] ), function E_INVALID_MESSAGE( error ) {
+ return error.code === "E_INVALID_MESSAGE" &&
+ error.path === path &&
+ "expected" in error;
+ }, "Expected \"E_INVALID_MESSAGE: Invalid message content `" + path + "`\" to be thrown. (" + type + ")" );
+ });
+ },
+
+ assertMessageVariablesType: function( assert, name, fn ) {
+ assertParameterType( assert, [ "array", "cldr", "number", "plainObject", "string" ], name, fn );
+ },
+
assertNumberParameter: function( assert, name, fn ) {
assertParameterType( assert, "number", name, fn );
},