diff --git a/core/localizer.js b/core/localizer.js
new file mode 100644
index 0000000000..6b7875ad34
--- /dev/null
+++ b/core/localizer.js
@@ -0,0 +1,1098 @@
+/*
Localize a key and return a message function.
+ +If the message is a precompiled function then this is returned + directly. Otherwise the message string is compiled to a function with + + messageformat.js. The resulting function takes an object mapping + from variables in the message to their values.
+ +Be aware that if the messages have not loaded yet this method
+ will not return the localized string. See localize
for
+ method that works whether the messages have loaded or not.
Async version of {@link localize}.
+ +Waits for the localizer to get messages before localizing the key. + Use either the callback or the promise.
+ +
+defaultLocalizer.localize("hello_name", "Hello, {name}!").then(function (hi) {
+ console.log(hi({name: "World"})); // => "Hello, World!""
+ console.log(hi()); // => Error: MessageFormat: No data passed to function.
+});
+
+
+ If the message for the key is "simple", i.e. it does not contain any
+ variables, then the function will implement a custom toString
+ function that also returns the message. This means that you can use
+ the function like a string. Example:
+defaultLocalizer.localize("hello", "Hello").then(function (hi) {
+ // Concatenating an object to a string calls its toString
+ myObject.hello = "" + hi;
+ var y = "The greeting '" + hi + "' is used in this locale";
+ // textContent only accepts strings and so calls toString
+ button.textContent = hi;
+ // and use as a function also works.
+ var z = hi();
+});
+
+
+ @function
+ @param {String} key The key to the string in the {@link messages} object.
+ @param {String} defaultMessage The value to use if key does not exist.
+ @param {String} [defaultOnFail=true] Whether to use the default messages if the messages fail to load.
+ @param {Function} [callback] Passed the message function.
+ @returns {Promise} A promise that is resolved with the message function.
+ */
+ localize: {
+ value: function(key, defaultMessage, defaultOnFail, callback) {
+ var listener, deferred, promise, self = this;
+ defaultOnFail = (typeof defaultOnFail === "undefined") ? true : defaultOnFail;
+
+ if (!this.messagesPromise) {
+ promise = Promise.resolve(this.localizeSync(key, defaultMessage));
+ promise.then(callback);
+ return promise;
+ }
+
+ var l = function() {
+ var messageFn = self.localizeSync(key, defaultMessage);
+ if (typeof callback === "function") {
+ callback(messageFn);
+ }
+ return messageFn;
+ };
+
+ if (defaultOnFail) {
+ // Try and localize the message, no matter what the outcome
+ return this.messagesPromise.then(l, l);
+ } else {
+ return this.messagesPromise.then(l);
+ }
+ }
+ }
+
+});
+
+/**
+ @class module:montage/core/localizer.DefaultLocalizer
+ @extends module:montage/core/localizer.Localizer
+*/
+var DefaultLocalizer = Montage.create(Localizer, /** @lends module:montage/core/localizer.DefaultLocalizer# */ {
+ init: {
+ value: function() {
+ var defaultLocale = this.callDelegateMethod("getDefaultLocale");
+
+ if (!defaultLocale && typeof window !== "undefined") {
+ if (window.localStorage) {
+ defaultLocale = window.localStorage.getItem(LOCALE_STORAGE_KEY);
+ }
+ defaultLocale = defaultLocale || window.navigator.userLanguage || window.navigator.language;
+ }
+
+ defaultLocale = defaultLocale || "en";
+ this.locale = defaultLocale;
+
+ this.loadMessages().done();
+
+ return this;
+ }
+ },
+
+ _delegate: {
+ enumerable: false,
+ value: null
+ },
+ /**
+ Delegate to get the default locale.
+
+ Should implement a getDefaultLocale
method that returns
+ a language-tag string that can be passed to {@link locale}
+ @type Object
+ @default null
+ */
+ delegate: {
+ get: function() {
+ return this._delegate;
+ },
+ set: function(value) {
+ if (this._delegate !== value) {
+ this._delegate = value;
+ this.init();
+ }
+ }
+ },
+
+ locale: {
+ get: function() {
+ return this._locale;
+ },
+ set: function(value) {
+ try {
+ Object.getPropertyDescriptor(Localizer, "locale").set.call(this, value);
+ } catch (e) {
+ value = "en";
+ Object.getPropertyDescriptor(Localizer, "locale").set.call(this, value);
+ }
+
+ // If possible, save locale
+ if (typeof window !== "undefined" && window.localStorage) {
+ window.localStorage.setItem(LOCALE_STORAGE_KEY, value);
+ }
+ }
+ },
+
+ /**
+ Reset the saved locale back to default by using the steps above.
+ @function
+ @returns {String} the reset locale
+ */
+ reset: {
+ value: function() {
+ if (typeof window !== "undefined" && window.localStorage) {
+ window.localStorage.removeItem(LOCALE_STORAGE_KEY);
+ }
+ this.init();
+ return this._locale;
+ }
+ }
+});
+
+/**
+ The default localizer.
+
+ The locale of the defaultLocalizer is determined by following these steps:
+ +defaultLocalizer.locale can be set and if localStorage exists then the value will be saved in + "montage_locale" (LOCALE_STORAGE_KEY).
+ + @type {module:montage/core/localizer.DefaultLocalizer} + @static +*/ +var defaultLocalizer = exports.defaultLocalizer = DefaultLocalizer.create().init(); + +/** + The localize function from {@link defaultLocalizer} provided for convenience. + + @function + @see module:montage/core/localizer.Localizer#localize +*/ +exports.localize = defaultLocalizer.localize.bind(defaultLocalizer); + +/** + Stores data needed for {@link Message}. + + When any of the properties in this object are set using setProperty (i.e. + through a binding) a change event will be dispatched. + + Really this is a proxy, and can be replaced by one once they become + readily available. + + @class module:montage/core/localizer.MessageData + @extends module:montage/core/core.Montage + @private +*/ +var MessageData = Montage.create(Montage, /** @lends module:montage/core/localizer.MessageVariables# */{ + /** + Initialize the object. + + @function + @param {Object} data Object with own properties to set on this object. + */ + init: { + enumerable: false, + value: function(data) { + for (var p in data) { + // adding this binding will call setProperty below which will + // add the PropertyChangeListener + Object.defineBinding(this, p, { + boundObject: data, + boundObjectPropertyPath: p, + oneway: false + }); + } + + return this; + } + }, + + /** + Watch any properties set through bindings for changes. + + @function + @private + */ + setProperty: { + enumerable: false, + value: function(path, value) { + // The same listener will only be registered once. It's faster to + // just register again than it would be to check for the existance + // of an existing listener. + this.addPropertyChangeListener(path, this); + + Object.setProperty.call(this, path, value); + } + }, + + handleChange: { + enumerable: false, + value: function(event) { + this.dispatchEventNamed("change", true, false); + } + } +}); +/** + Tracks a message function and its data for changes in order to generate a + localized message. + + @class module:montage/core/localizer.MessageLocalizer + @extends module:montage/core/core.Montage +*/ +var Message = exports.Message = Montage.create(Montage, /** @lends module:montage/core/localizer.MessageLocalizer# */ { + + didCreate: { + value: function() { + this._data = MessageData.create(); + this._data.addEventListener("change", this, false); + } + }, + + /** + Initialize the object. + + @function + @param {string|Function} keyOrFunction A messageformat string or a + function that takes an object argument mapping variables to values and + returns a string. Usually the output of Localizer#localize. + @param {Object} data Value for this data property. + @returns {Message} this. + */ + init: { + value: function(key, defaultMessage, data) { + if (key) this.key = key; + if (defaultMessage) this.defaultMessage = defaultMessage; + if (data) this.data = data; + + return this; + } + }, + + _localizer: { + value: defaultLocalizer + }, + localizer: { + get: function() { + return this._localizer; + }, + set: function(value) { + if (this._localizer == value) { + return; + } + this._localizer = value; + this._localize(); + } + }, + + _key: { + value: null + }, + /** + * A key for the default localizer to get the message function from. + * @type {string} + * @default null + */ + key: { + get: function() { + return this._key; + }, + set: function(value) { + if (this._key === value) { + return; + } + + this._key = value; + this._localize(); + } + }, + + _defaultMessage: { + value: null + }, + defaultMessage: { + get: function() { + return this._defaultMessage; + }, + set: function(value) { + if (this._defaultMessage === value) { + return; + } + this._defaultMessage = value; + this._localize(); + } + }, + + _isLocalizeQueued: { + value: false + }, + _localize: { + value: function() { + // Optimization: only run this function in the next tick. So that + // if the key, defaultKey and data are set individually we don't + // try and localize each time. + if (this._isLocalizeQueued) return; + this._isLocalizeQueued = true; + + var self = this; + // Set up a new promise now, so anyone accessing it in this tick + // won't get the old one. + var temp = Promise.defer(); + this._messageFunction = temp.promise; + + // Don't use fcall, so that if the `data` object is completely + // changed we have the latest version. + this.localized = this._messageFunction.then(function (fn) { + return fn(self._data); + }); + + Promise.nextTick(function() { + self._isLocalizeQueued = false; + + if (!self._key && !self._defaultMessage) { + // TODO: Revisit when components inside repetitions aren't + // uselessly instatiated. + // While it might seem like we should reject here, when + // repetitions get set up both the key and default message + // are null/undefined. By rejecting the developer would + // get an error whenever they use localization with a + // repetition. + // Instead we show a less severe "warning", so the issue + // is still surfaced + console.warn("Both key and default message are falsey for", + self, "If this is in a repetition this warning can be ignored"); + temp.resolve(EMPTY_STRING_FUNCTION); + return; + } + // Replace the _messageFunction promise with the real one. + temp.resolve(self._localizer.localizeSync( + self._key, + self._defaultMessage + )); + }); + } + }, + + _messageFunction: { + value: Promise.resolve(EMPTY_STRING_FUNCTION) + }, + + /** + The data needed for the message. Properties on this object can be + bound to. + + This object will be wrapped in a MessageData object to watch all + properties for changes so that the localized message can be updated. + + @type {MessageData} + @default null + */ + _data: { + value: null + }, + data: { + get: function() { + return this._data; + }, + set: function(value) { + if (this._data === value) { + return; + } + if (this._data) { + this._data.removeEventListener("change", this); + } + this._data = MessageData.create().init(value); + this._data.addEventListener("change", this, false); + this.handleChange(); + } + }, + + // TODO: Remove when possible to bind to promises + __localizedResolved: { + value: "" + }, + + _localizedDeferred: { + value: Promise.defer() + }, + /** + The message localized with all variables replaced. + @type {string} + @default "" + */ + localized: { + get: function() { + return this._localizedDeferred.promise; + }, + set: function(value) { + if (value === this._localized) { + return; + } + var self = this; + + // We create our own deferred so that if localized gets set in + // succession without being resolved, we can replace the old + // promises with the new one transparently. + var deferred = Promise.defer(); + this._localizedDeferred.resolve(deferred.promise); + value.then(deferred.resolve, deferred.reject); + + // TODO: Remove when possible to bind to promises + deferred.promise.then(function (message) { + return self.__localizedResolved = message; + }).done(); + + this._localizedDeferred = deferred; + } + }, + + setProperty: { + enumerable: false, + value: function(path, value) { + // If a binding to a data property has been set directly on this + // object, instead of on the data object, install a listener for + // the data object + if (path.indexOf("data.") === 0) { + this._data.addPropertyChangeListener(path.substring(5), this._data); + } + Object.setProperty.call(this, path, value); + } + }, + + /** + * Whenever there is a change set the localized property. + * @type {Function} + * @private + */ + handleChange: { + value: function(event) { + this.localized = this._messageFunction.fcall(this._data); + } + }, + + serializeSelf: { + value: function(serializer) { + var result = { + _bindingDescriptors: this._bindingDescriptors + }; + + // don't serialize the message function + result.key = this._key; + result.defaultMessage = this._defaultMessage; + + // only serialize localizer if it isn't the default one + if (this._localizer !== defaultLocalizer) { + result.localizer = this._localizer; + } + + return result; + } + }, + + serializeForLocalizations: { + value: function(serializer) { + var result = {}; + + var bindings = this._bindingDescriptors; + + if (bindings && bindings.key) { + result[KEY_KEY] = bindings.key; + } else { + result[KEY_KEY] = this._key; + } + + if (bindings && bindings.defaultMessage) { + result[DEFAULT_MESSAGE_KEY] = bindings.defaultMessage; + } else { + result[DEFAULT_MESSAGE_KEY] = this._defaultMessage; + } + + var dataBindings = this._data._bindingDescriptors; + + // NOTE: Can't use `Montage.getSerializablePropertyNames(this._data)` + // because the properties we want to serialize are not defined + // using `Montage.defineProperty`, and so don't have + // `serializable: true` as part of the property descriptor. + for (var p in this._data) { + if (this._data.hasOwnProperty(p) && + (!dataBindings || !dataBindings[p]) + ) { + if (!result.data) result.data = {}; + result.data[p] = this._data[p]; + } + } + + // Loop through bindings seperately in case the bound properties + // haven't been set on the data object yet. + for (var b in dataBindings) { + if (!result.data) result.data = {}; + result.data[b] = dataBindings[b]; + } + + return result; + } + } + +}); + +var createMessageBinding = function(object, prop, key, defaultMessage, data, deserializer) { + var message = Message.create(); + + for (var d in data) { + if (typeof data[d] === "string") { + message.data[d] = data[d]; + } else { + deserializeBindingToBindingDescriptor(data[d], deserializer); + Object.defineBinding(message.data, d, data[d]); + } + } + + if (typeof key === "object") { + deserializeBindingToBindingDescriptor(key, deserializer); + Object.defineBinding(message, "key", key); + } else { + message.key = key; + } + + if (typeof defaultMessage === "object") { + deserializeBindingToBindingDescriptor(defaultMessage, deserializer); + Object.defineBinding(message, "defaultMessage", defaultMessage); + } else { + message.defaultMessage = defaultMessage; + } + + Object.defineBinding(object, prop, { + boundObject: message, + // TODO: Remove when possible to bind to promises and replace with + // binding to "localized" + boundObjectPropertyPath: "__localizedResolved", + oneway: true, + serializable: false + }); +}; + +Serializer.defineSerializationUnit("localizations", function(object) { + var bindingDescriptors = object._bindingDescriptors; + + if (bindingDescriptors) { + var result; + for (var prop in bindingDescriptors) { + var desc = bindingDescriptors[prop]; + if (Message.isPrototypeOf(desc.boundObject)) { + if (!result) { + result = {}; + } + var message = desc.boundObject; + result[prop] = message.serializeForLocalizations(); + } + } + return result; + } +}); + +Deserializer.defineDeserializationUnit("localizations", function(object, properties, deserializer) { + for (var prop in properties) { + var desc = properties[prop], + key, + defaultMessage; + + if (!(KEY_KEY in desc)) { + console.error("localized property '" + prop + "' must contain a key property (" + KEY_KEY + "), in ", properties[prop]); + continue; + } + if(logger.isDebug && !(DEFAULT_MESSAGE_KEY in desc)) { + logger.debug(this, "Warning: localized property '" + prop + "' does not contain a default message property (" + DEFAULT_MESSAGE_KEY + "), in ", object); + } + + key = desc[KEY_KEY]; + defaultMessage = desc[DEFAULT_MESSAGE_KEY]; + + createMessageBinding(object, prop, key, defaultMessage, desc.data, deserializer); + } +}); diff --git a/core/messageformat-locale.js b/core/messageformat-locale.js new file mode 100644 index 0000000000..0dfeacaef9 --- /dev/null +++ b/core/messageformat-locale.js @@ -0,0 +1,491 @@ +exports.am = function(n) { + if (n === 0 || n == 1) { + return 'one'; + } + return 'other'; +}; +exports.ar = function(n) { + if (n === 0) { + return 'zero'; + } + if (n == 1) { + return 'one'; + } + if (n == 2) { + return 'two'; + } + if ((n % 100) >= 3 && (n % 100) <= 10 && n == Math.floor(n)) { + return 'few'; + } + if ((n % 100) >= 11 && (n % 100) <= 99 && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; +exports.bg = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.bn = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.br = function (n) { + if (n === 0) { + return 'zero'; + } + if (n == 1) { + return 'one'; + } + if (n == 2) { + return 'two'; + } + if (n == 3) { + return 'few'; + } + if (n == 6) { + return 'many'; + } + return 'other'; +}; +exports.ca = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.cs = function (n) { + if (n == 1) { + return 'one'; + } + if (n == 2 || n == 3 || n == 4) { + return 'few'; + } + return 'other'; +}; +exports.cy = function (n) { + if (n === 0) { + return 'zero'; + } + if (n == 1) { + return 'one'; + } + if (n == 2) { + return 'two'; + } + if (n == 3) { + return 'few'; + } + if (n == 6) { + return 'many'; + } + return 'other'; +}; +exports.da = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.de = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.el = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.en = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.es = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.et = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.eu = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.fa = function ( n ) { + return "other"; +}; +exports.fi = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.fil = function(n) { + if (n === 0 || n == 1) { + return 'one'; + } + return 'other'; +}; +exports.fr = function (n) { + if (n >= 0 && n < 2) { + return 'one'; + } + return 'other'; +}; +exports.ga = function (n) { + if (n == 1) { + return 'one'; + } + if (n == 2) { + return 'two'; + } + return 'other'; +}; +exports.gl = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.gsw = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.gu = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.he = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.hi = function(n) { + if (n === 0 || n == 1) { + return 'one'; + } + return 'other'; +}; +exports.hr = function (n) { + if ((n % 10) == 1 && (n % 100) != 11) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 4 && + ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { + return 'few'; + } + if ((n % 10) === 0 || ((n % 10) >= 5 && (n % 10) <= 9) || + ((n % 100) >= 11 && (n % 100) <= 14) && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; +exports.hu = function(n) { + return 'other'; +}; +exports.id = function(n) { + return 'other'; +}; +exports["in"] = function(n) { + return 'other'; +}; +exports.is = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.it = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.iw = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.ja = function ( n ) { + return "other"; +}; +exports.kn = function ( n ) { + return "other"; +}; +exports.ko = function ( n ) { + return "other"; +}; +exports.lag = function (n) { + if (n === 0) { + return 'zero'; + } + if (n > 0 && n < 2) { + return 'one'; + } + return 'other'; +}; +exports.ln = function(n) { + if (n === 0 || n == 1) { + return 'one'; + } + return 'other'; +}; +exports.lt = function (n) { + if ((n % 10) == 1 && ((n % 100) < 11 || (n % 100) > 19)) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 9 && + ((n % 100) < 11 || (n % 100) > 19) && n == Math.floor(n)) { + return 'few'; + } + return 'other'; +}; +exports.lv = function (n) { + if (n === 0) { + return 'zero'; + } + if ((n % 10) == 1 && (n % 100) != 11) { + return 'one'; + } + return 'other'; +}; +exports.mk = function (n) { + if ((n % 10) == 1 && n != 11) { + return 'one'; + } + return 'other'; +}; +exports.ml = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.mo = function (n) { + if (n == 1) { + return 'one'; + } + if (n === 0 || n != 1 && (n % 100) >= 1 && + (n % 100) <= 19 && n == Math.floor(n)) { + return 'few'; + } + return 'other'; +}; +exports.mr = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.ms = function ( n ) { + return "other"; +}; +exports.mt = function (n) { + if (n == 1) { + return 'one'; + } + if (n === 0 || ((n % 100) >= 2 && (n % 100) <= 4 && n == Math.floor(n))) { + return 'few'; + } + if ((n % 100) >= 11 && (n % 100) <= 19 && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; +exports.nl = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.no = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.or = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.pl = function (n) { + if (n == 1) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 4 && + ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { + return 'few'; + } + if ((n % 10) === 0 || n != 1 && (n % 10) == 1 || + ((n % 10) >= 5 && (n % 10) <= 9 || (n % 100) >= 12 && (n % 100) <= 14) && + n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; +exports.pt = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.ro = function (n) { + if (n == 1) { + return 'one'; + } + if (n === 0 || n != 1 && (n % 100) >= 1 && + (n % 100) <= 19 && n == Math.floor(n)) { + return 'few'; + } + return 'other'; +}; +exports.ru = function (n) { + if ((n % 10) == 1 && (n % 100) != 11) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 4 && + ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { + return 'few'; + } + if ((n % 10) === 0 || ((n % 10) >= 5 && (n % 10) <= 9) || + ((n % 100) >= 11 && (n % 100) <= 14) && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; +exports.shi = function(n) { + if (n >= 0 && n <= 1) { + return 'one'; + } + if (n >= 2 && n <= 10 && n == Math.floor(n)) { + return 'few'; + } + return 'other'; +}; +exports.sk = function (n) { + if (n == 1) { + return 'one'; + } + if (n == 2 || n == 3 || n == 4) { + return 'few'; + } + return 'other'; +}; +exports.sl = function (n) { + if ((n % 100) == 1) { + return 'one'; + } + if ((n % 100) == 2) { + return 'two'; + } + if ((n % 100) == 3 || (n % 100) == 4) { + return 'few'; + } + return 'other'; +}; +exports.sq = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.sr = function (n) { + if ((n % 10) == 1 && (n % 100) != 11) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 4 && + ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { + return 'few'; + } + if ((n % 10) === 0 || ((n % 10) >= 5 && (n % 10) <= 9) || + ((n % 100) >= 11 && (n % 100) <= 14) && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; +exports.sv = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.sw = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.ta = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.te = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.th = function ( n ) { + return "other"; +}; +exports.tl = function(n) { + if (n === 0 || n == 1) { + return 'one'; + } + return 'other'; +}; +exports.tr = function(n) { + return 'other'; +}; +exports.uk = function (n) { + if ((n % 10) == 1 && (n % 100) != 11) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 4 && + ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { + return 'few'; + } + if ((n % 10) === 0 || ((n % 10) >= 5 && (n % 10) <= 9) || + ((n % 100) >= 11 && (n % 100) <= 14) && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; +exports.ur = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; +exports.vi = function ( n ) { + return "other"; +}; +exports.zh = function ( n ) { + return "other"; +}; diff --git a/core/messageformat.js b/core/messageformat.js new file mode 100644 index 0000000000..f22f9b7417 --- /dev/null +++ b/core/messageformat.js @@ -0,0 +1,1911 @@ +/** + * messageformat.js + * + * ICU PluralFormat + SelectFormat for JavaScript + * + * @author Alex Sexton - @SlexAxton + * @version 0.1.5 + * @license WTFPL + * @contributor_license Dojo CLA +*/ +(function ( root ) { + + // Create the contructor function + function MessageFormat ( locale, pluralFunc ) { + var fallbackLocale; + + if ( locale && pluralFunc ) { + MessageFormat.locale[ locale ] = pluralFunc; + } + + // Defaults + fallbackLocale = locale = locale || "en"; + pluralFunc = pluralFunc || MessageFormat.locale[ fallbackLocale = MessageFormat.Utils.getFallbackLocale( locale ) ]; + + if ( ! pluralFunc ) { + throw new Error( "Plural Function not found for locale: " + locale ); + } + + // Own Properties + this.pluralFunc = pluralFunc; + this.locale = locale; + this.fallbackLocale = fallbackLocale; + } + + // Set up the locales object. Add in english by default + MessageFormat.locale = { + "en" : function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; + } + }; + + // Build out our basic SafeString type + // more or less stolen from Handlebars by @wycats + MessageFormat.SafeString = function( string ) { + this.string = string; + }; + + MessageFormat.SafeString.prototype.toString = function () { + return this.string.toString(); + }; + + MessageFormat.Utils = { + numSub : function ( string, key, depth ) { + // make sure that it's not an escaped octothorpe + return string.replace( /^#|[^\\]#/g, function (m) { + var prefix = m && m.length === 2 ? m.charAt(0) : ''; + return prefix + '" + (function(){ var x = ' + + key+';\nif( isNaN(x) ){\nthrow new Error("MessageFormat: `"+lastkey_'+depth+'+"` isnt a number.");\n}\nreturn x;\n})() + "'; + }); + }, + escapeExpression : function (string) { + var escape = { + "\n": "\\n", + "\"": '\\"' + }, + badChars = /[\n"]/g, + possible = /[\n"]/, + escapeChar = function(chr) { + return escape[chr] || "&"; + }; + + // Don't escape SafeStrings, since they're already safe + if ( string instanceof MessageFormat.SafeString ) { + return string.toString(); + } + else if ( string === null || string === false ) { + return ""; + } + + if ( ! possible.test( string ) ) { + return string; + } + return string.replace( badChars, escapeChar ); + }, + getFallbackLocale: function( locale ) { + var tagSeparator = locale.indexOf("-") >= 0 ? "-" : "_"; + + // Lets just be friends, fallback through the language tags + while ( ! MessageFormat.locale.hasOwnProperty( locale ) ) { + locale = locale.substring(0, locale.lastIndexOf( tagSeparator )); + if (locale.length === 0) { + return null; + } + } + + return locale; + } + }; + + // This is generated and pulled in for browsers. + var mparser = (function(){ + /* Generated by PEG.js 0.6.2 (http://pegjs.majda.cz/). */ + + var result = { + /* + * Parses the input with a generated parser. If the parsing is successfull, + * returns a value explicitly or implicitly specified by the grammar from + * which the parser was generated (see |PEG.buildParser|). If the parsing is + * unsuccessful, throws |PEG.parser.SyntaxError| describing the error. + */ + parse: function(input, startRule) { + var parseFunctions = { + "_": parse__, + "char": parse_char, + "chars": parse_chars, + "digits": parse_digits, + "elementFormat": parse_elementFormat, + "hexDigit": parse_hexDigit, + "id": parse_id, + "messageFormatElement": parse_messageFormatElement, + "messageFormatPattern": parse_messageFormatPattern, + "messageFormatPatternRight": parse_messageFormatPatternRight, + "offsetPattern": parse_offsetPattern, + "pluralFormatPattern": parse_pluralFormatPattern, + "pluralForms": parse_pluralForms, + "pluralStyle": parse_pluralStyle, + "selectFormatPattern": parse_selectFormatPattern, + "selectStyle": parse_selectStyle, + "start": parse_start, + "string": parse_string, + "stringKey": parse_stringKey, + "whitespace": parse_whitespace + }; + + if (startRule !== undefined) { + if (parseFunctions[startRule] === undefined) { + throw new Error("Invalid rule name: " + quote(startRule) + "."); + } + } else { + startRule = "start"; + } + + var pos = 0; + var reportMatchFailures = true; + var rightmostMatchFailuresPos = 0; + var rightmostMatchFailuresExpected = []; + var cache = {}; + + function padLeft(input, padding, length) { + var result = input; + + var padLength = length - input.length; + for (var i = 0; i < padLength; i++) { + result = padding + result; + } + + return result; + } + + function escape(ch) { + var charCode = ch.charCodeAt(0); + + if (charCode <= 0xFF) { + var escapeChar = 'x'; + var length = 2; + } else { + var escapeChar = 'u'; + var length = 4; + } + + return '\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length); + } + + function quote(s) { + /* + * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a + * string literal except for the closing quote character, backslash, + * carriage return, line separator, paragraph separator, and line feed. + * Any character may appear in the form of an escape sequence. + */ + return '"' + s + .replace(/\\/g, '\\\\') // backslash + .replace(/"/g, '\\"') // closing quote character + .replace(/\r/g, '\\r') // carriage return + .replace(/\n/g, '\\n') // line feed + .replace(/[\x80-\uFFFF]/g, escape) // non-ASCII characters + + '"'; + } + + function matchFailed(failure) { + if (pos < rightmostMatchFailuresPos) { + return; + } + + if (pos > rightmostMatchFailuresPos) { + rightmostMatchFailuresPos = pos; + rightmostMatchFailuresExpected = []; + } + + rightmostMatchFailuresExpected.push(failure); + } + + function parse_start() { + var cacheKey = 'start@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var result1 = parse_messageFormatPattern(); + var result2 = result1 !== null + ? (function(messageFormatPattern) { return { type: "program", program: messageFormatPattern }; })(result1) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_messageFormatPattern() { + var cacheKey = 'messageFormatPattern@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var savedPos1 = pos; + var result3 = parse_string(); + if (result3 !== null) { + var result4 = []; + var result5 = parse_messageFormatPatternRight(); + while (result5 !== null) { + result4.push(result5); + var result5 = parse_messageFormatPatternRight(); + } + if (result4 !== null) { + var result1 = [result3, result4]; + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + var result2 = result1 !== null + ? (function(s1, inner) { + var st = []; + if ( s1 && s1.val ) { + st.push( s1 ); + } + for( var i in inner ){ + if ( inner.hasOwnProperty( i ) ) { + st.push( inner[ i ] ); + } + } + return { type: 'messageFormatPattern', statements: st }; + })(result1[0], result1[1]) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_messageFormatPatternRight() { + var cacheKey = 'messageFormatPatternRight@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var savedPos1 = pos; + if (input.substr(pos, 1) === "{") { + var result3 = "{"; + pos += 1; + } else { + var result3 = null; + if (reportMatchFailures) { + matchFailed("\"{\""); + } + } + if (result3 !== null) { + var result4 = parse__(); + if (result4 !== null) { + var result5 = parse_messageFormatElement(); + if (result5 !== null) { + var result6 = parse__(); + if (result6 !== null) { + if (input.substr(pos, 1) === "}") { + var result7 = "}"; + pos += 1; + } else { + var result7 = null; + if (reportMatchFailures) { + matchFailed("\"}\""); + } + } + if (result7 !== null) { + var result8 = parse_string(); + if (result8 !== null) { + var result1 = [result3, result4, result5, result6, result7, result8]; + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + var result2 = result1 !== null + ? (function(mfe, s1) { + var res = []; + if ( mfe ) { + res.push(mfe); + } + if ( s1 && s1.val ) { + res.push( s1 ); + } + return { type: "messageFormatPatternRight", statements : res }; + })(result1[2], result1[5]) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_messageFormatElement() { + var cacheKey = 'messageFormatElement@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var savedPos1 = pos; + var result3 = parse_id(); + if (result3 !== null) { + var savedPos2 = pos; + if (input.substr(pos, 1) === ",") { + var result6 = ","; + pos += 1; + } else { + var result6 = null; + if (reportMatchFailures) { + matchFailed("\",\""); + } + } + if (result6 !== null) { + var result7 = parse_elementFormat(); + if (result7 !== null) { + var result5 = [result6, result7]; + } else { + var result5 = null; + pos = savedPos2; + } + } else { + var result5 = null; + pos = savedPos2; + } + var result4 = result5 !== null ? result5 : ''; + if (result4 !== null) { + var result1 = [result3, result4]; + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + var result2 = result1 !== null + ? (function(argIdx, efmt) { + var res = { + type: "messageFormatElement", + argumentIndex: argIdx + }; + if ( efmt && efmt.length ) { + res.elementFormat = efmt[1]; + } + else { + res.output = true; + } + return res; + })(result1[0], result1[1]) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_elementFormat() { + var cacheKey = 'elementFormat@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos2 = pos; + var savedPos3 = pos; + var result14 = parse__(); + if (result14 !== null) { + if (input.substr(pos, 6) === "plural") { + var result15 = "plural"; + pos += 6; + } else { + var result15 = null; + if (reportMatchFailures) { + matchFailed("\"plural\""); + } + } + if (result15 !== null) { + var result16 = parse__(); + if (result16 !== null) { + if (input.substr(pos, 1) === ",") { + var result17 = ","; + pos += 1; + } else { + var result17 = null; + if (reportMatchFailures) { + matchFailed("\",\""); + } + } + if (result17 !== null) { + var result18 = parse__(); + if (result18 !== null) { + var result19 = parse_pluralStyle(); + if (result19 !== null) { + var result20 = parse__(); + if (result20 !== null) { + var result12 = [result14, result15, result16, result17, result18, result19, result20]; + } else { + var result12 = null; + pos = savedPos3; + } + } else { + var result12 = null; + pos = savedPos3; + } + } else { + var result12 = null; + pos = savedPos3; + } + } else { + var result12 = null; + pos = savedPos3; + } + } else { + var result12 = null; + pos = savedPos3; + } + } else { + var result12 = null; + pos = savedPos3; + } + } else { + var result12 = null; + pos = savedPos3; + } + var result13 = result12 !== null + ? (function(t, s) { + return { + type : "elementFormat", + key : t, + val : s.val + }; + })(result12[1], result12[5]) + : null; + if (result13 !== null) { + var result11 = result13; + } else { + var result11 = null; + pos = savedPos2; + } + if (result11 !== null) { + var result0 = result11; + } else { + var savedPos0 = pos; + var savedPos1 = pos; + var result4 = parse__(); + if (result4 !== null) { + if (input.substr(pos, 6) === "select") { + var result5 = "select"; + pos += 6; + } else { + var result5 = null; + if (reportMatchFailures) { + matchFailed("\"select\""); + } + } + if (result5 !== null) { + var result6 = parse__(); + if (result6 !== null) { + if (input.substr(pos, 1) === ",") { + var result7 = ","; + pos += 1; + } else { + var result7 = null; + if (reportMatchFailures) { + matchFailed("\",\""); + } + } + if (result7 !== null) { + var result8 = parse__(); + if (result8 !== null) { + var result9 = parse_selectStyle(); + if (result9 !== null) { + var result10 = parse__(); + if (result10 !== null) { + var result2 = [result4, result5, result6, result7, result8, result9, result10]; + } else { + var result2 = null; + pos = savedPos1; + } + } else { + var result2 = null; + pos = savedPos1; + } + } else { + var result2 = null; + pos = savedPos1; + } + } else { + var result2 = null; + pos = savedPos1; + } + } else { + var result2 = null; + pos = savedPos1; + } + } else { + var result2 = null; + pos = savedPos1; + } + } else { + var result2 = null; + pos = savedPos1; + } + var result3 = result2 !== null + ? (function(t, s) { + return { + type : "elementFormat", + key : t, + val : s.val + }; + })(result2[1], result2[5]) + : null; + if (result3 !== null) { + var result1 = result3; + } else { + var result1 = null; + pos = savedPos0; + } + if (result1 !== null) { + var result0 = result1; + } else { + var result0 = null;; + }; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_pluralStyle() { + var cacheKey = 'pluralStyle@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var result1 = parse_pluralFormatPattern(); + var result2 = result1 !== null + ? (function(pfp) { + return { type: "pluralStyle", val: pfp }; + })(result1) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_selectStyle() { + var cacheKey = 'selectStyle@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var result1 = parse_selectFormatPattern(); + var result2 = result1 !== null + ? (function(sfp) { + return { type: "selectStyle", val: sfp }; + })(result1) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_pluralFormatPattern() { + var cacheKey = 'pluralFormatPattern@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var savedPos1 = pos; + var result6 = parse_offsetPattern(); + var result3 = result6 !== null ? result6 : ''; + if (result3 !== null) { + var result4 = []; + var result5 = parse_pluralForms(); + while (result5 !== null) { + result4.push(result5); + var result5 = parse_pluralForms(); + } + if (result4 !== null) { + var result1 = [result3, result4]; + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + var result2 = result1 !== null + ? (function(op, pf) { + var res = { + type: "pluralFormatPattern", + pluralForms: pf + }; + if ( op ) { + res.offset = op; + } + else { + res.offset = 0; + } + return res; + })(result1[0], result1[1]) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_offsetPattern() { + var cacheKey = 'offsetPattern@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var savedPos1 = pos; + var result3 = parse__(); + if (result3 !== null) { + if (input.substr(pos, 6) === "offset") { + var result4 = "offset"; + pos += 6; + } else { + var result4 = null; + if (reportMatchFailures) { + matchFailed("\"offset\""); + } + } + if (result4 !== null) { + var result5 = parse__(); + if (result5 !== null) { + if (input.substr(pos, 1) === ":") { + var result6 = ":"; + pos += 1; + } else { + var result6 = null; + if (reportMatchFailures) { + matchFailed("\":\""); + } + } + if (result6 !== null) { + var result7 = parse__(); + if (result7 !== null) { + var result8 = parse_digits(); + if (result8 !== null) { + var result9 = parse__(); + if (result9 !== null) { + var result1 = [result3, result4, result5, result6, result7, result8, result9]; + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + var result2 = result1 !== null + ? (function(d) { + return d; + })(result1[5]) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_selectFormatPattern() { + var cacheKey = 'selectFormatPattern@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var result1 = []; + var result3 = parse_pluralForms(); + while (result3 !== null) { + result1.push(result3); + var result3 = parse_pluralForms(); + } + var result2 = result1 !== null + ? (function(pf) { + return { + type: "selectFormatPattern", + pluralForms: pf + }; + })(result1) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_pluralForms() { + var cacheKey = 'pluralForms@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var savedPos1 = pos; + var result3 = parse__(); + if (result3 !== null) { + var result4 = parse_stringKey(); + if (result4 !== null) { + var result5 = parse__(); + if (result5 !== null) { + if (input.substr(pos, 1) === "{") { + var result6 = "{"; + pos += 1; + } else { + var result6 = null; + if (reportMatchFailures) { + matchFailed("\"{\""); + } + } + if (result6 !== null) { + var result7 = parse__(); + if (result7 !== null) { + var result8 = parse_messageFormatPattern(); + if (result8 !== null) { + var result9 = parse__(); + if (result9 !== null) { + if (input.substr(pos, 1) === "}") { + var result10 = "}"; + pos += 1; + } else { + var result10 = null; + if (reportMatchFailures) { + matchFailed("\"}\""); + } + } + if (result10 !== null) { + var result1 = [result3, result4, result5, result6, result7, result8, result9, result10]; + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + var result2 = result1 !== null + ? (function(k, mfp) { + return { + type: "pluralForms", + key: k, + val: mfp + }; + })(result1[1], result1[5]) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_stringKey() { + var cacheKey = 'stringKey@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos2 = pos; + var result7 = parse_id(); + var result8 = result7 !== null + ? (function(i) { + return i; + })(result7) + : null; + if (result8 !== null) { + var result6 = result8; + } else { + var result6 = null; + pos = savedPos2; + } + if (result6 !== null) { + var result0 = result6; + } else { + var savedPos0 = pos; + var savedPos1 = pos; + if (input.substr(pos, 1) === "=") { + var result4 = "="; + pos += 1; + } else { + var result4 = null; + if (reportMatchFailures) { + matchFailed("\"=\""); + } + } + if (result4 !== null) { + var result5 = parse_digits(); + if (result5 !== null) { + var result2 = [result4, result5]; + } else { + var result2 = null; + pos = savedPos1; + } + } else { + var result2 = null; + pos = savedPos1; + } + var result3 = result2 !== null + ? (function(d) { + return d; + })(result2[1]) + : null; + if (result3 !== null) { + var result1 = result3; + } else { + var result1 = null; + pos = savedPos0; + } + if (result1 !== null) { + var result0 = result1; + } else { + var result0 = null;; + }; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_string() { + var cacheKey = 'string@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var savedPos1 = pos; + var result3 = parse__(); + if (result3 !== null) { + var result4 = []; + var savedPos2 = pos; + var result6 = parse__(); + if (result6 !== null) { + var result7 = parse_chars(); + if (result7 !== null) { + var result8 = parse__(); + if (result8 !== null) { + var result5 = [result6, result7, result8]; + } else { + var result5 = null; + pos = savedPos2; + } + } else { + var result5 = null; + pos = savedPos2; + } + } else { + var result5 = null; + pos = savedPos2; + } + while (result5 !== null) { + result4.push(result5); + var savedPos2 = pos; + var result6 = parse__(); + if (result6 !== null) { + var result7 = parse_chars(); + if (result7 !== null) { + var result8 = parse__(); + if (result8 !== null) { + var result5 = [result6, result7, result8]; + } else { + var result5 = null; + pos = savedPos2; + } + } else { + var result5 = null; + pos = savedPos2; + } + } else { + var result5 = null; + pos = savedPos2; + } + } + if (result4 !== null) { + var result1 = [result3, result4]; + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + var result2 = result1 !== null + ? (function(ws, s) { + var tmp = []; + for( var i = 0; i < s.length; ++i ) { + for( var j = 0; j < s[ i ].length; ++j ) { + tmp.push(s[i][j]); + } + } + return { + type: "string", + val: ws + tmp.join('') + }; + })(result1[0], result1[1]) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_id() { + var cacheKey = 'id@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var savedPos1 = pos; + var result3 = parse__(); + if (result3 !== null) { + if (input.substr(pos).match(/^[a-zA-Z$_]/) !== null) { + var result4 = input.charAt(pos); + pos++; + } else { + var result4 = null; + if (reportMatchFailures) { + matchFailed("[a-zA-Z$_]"); + } + } + if (result4 !== null) { + var result5 = []; + if (input.substr(pos).match(/^[^ \n\r,.+={}]/) !== null) { + var result7 = input.charAt(pos); + pos++; + } else { + var result7 = null; + if (reportMatchFailures) { + matchFailed("[^ \\n\\r,.+={}]"); + } + } + while (result7 !== null) { + result5.push(result7); + if (input.substr(pos).match(/^[^ \n\r,.+={}]/) !== null) { + var result7 = input.charAt(pos); + pos++; + } else { + var result7 = null; + if (reportMatchFailures) { + matchFailed("[^ \\n\\r,.+={}]"); + } + } + } + if (result5 !== null) { + var result6 = parse__(); + if (result6 !== null) { + var result1 = [result3, result4, result5, result6]; + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + } else { + var result1 = null; + pos = savedPos1; + } + var result2 = result1 !== null + ? (function(s1, s2) { + return s1 + (s2 ? s2.join('') : ''); + })(result1[1], result1[2]) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_chars() { + var cacheKey = 'chars@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var result3 = parse_char(); + if (result3 !== null) { + var result1 = []; + while (result3 !== null) { + result1.push(result3); + var result3 = parse_char(); + } + } else { + var result1 = null; + } + var result2 = result1 !== null + ? (function(chars) { return chars.join(''); })(result1) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_char() { + var cacheKey = 'char@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos5 = pos; + if (input.substr(pos).match(/^[^{}\\\0- \n\r]/) !== null) { + var result19 = input.charAt(pos); + pos++; + } else { + var result19 = null; + if (reportMatchFailures) { + matchFailed("[^{}\\\\\\0- \\n\\r]"); + } + } + var result20 = result19 !== null + ? (function(x) { + return x; + })(result19) + : null; + if (result20 !== null) { + var result18 = result20; + } else { + var result18 = null; + pos = savedPos5; + } + if (result18 !== null) { + var result0 = result18; + } else { + var savedPos4 = pos; + if (input.substr(pos, 2) === "\\#") { + var result16 = "\\#"; + pos += 2; + } else { + var result16 = null; + if (reportMatchFailures) { + matchFailed("\"\\\\#\""); + } + } + var result17 = result16 !== null + ? (function() { + return "\\#"; + })() + : null; + if (result17 !== null) { + var result15 = result17; + } else { + var result15 = null; + pos = savedPos4; + } + if (result15 !== null) { + var result0 = result15; + } else { + var savedPos3 = pos; + if (input.substr(pos, 2) === "\\{") { + var result13 = "\\{"; + pos += 2; + } else { + var result13 = null; + if (reportMatchFailures) { + matchFailed("\"\\\\{\""); + } + } + var result14 = result13 !== null + ? (function() { + return "\u007B"; + })() + : null; + if (result14 !== null) { + var result12 = result14; + } else { + var result12 = null; + pos = savedPos3; + } + if (result12 !== null) { + var result0 = result12; + } else { + var savedPos2 = pos; + if (input.substr(pos, 2) === "\\}") { + var result10 = "\\}"; + pos += 2; + } else { + var result10 = null; + if (reportMatchFailures) { + matchFailed("\"\\\\}\""); + } + } + var result11 = result10 !== null + ? (function() { + return "\u007D"; + })() + : null; + if (result11 !== null) { + var result9 = result11; + } else { + var result9 = null; + pos = savedPos2; + } + if (result9 !== null) { + var result0 = result9; + } else { + var savedPos0 = pos; + var savedPos1 = pos; + if (input.substr(pos, 2) === "\\u") { + var result4 = "\\u"; + pos += 2; + } else { + var result4 = null; + if (reportMatchFailures) { + matchFailed("\"\\\\u\""); + } + } + if (result4 !== null) { + var result5 = parse_hexDigit(); + if (result5 !== null) { + var result6 = parse_hexDigit(); + if (result6 !== null) { + var result7 = parse_hexDigit(); + if (result7 !== null) { + var result8 = parse_hexDigit(); + if (result8 !== null) { + var result2 = [result4, result5, result6, result7, result8]; + } else { + var result2 = null; + pos = savedPos1; + } + } else { + var result2 = null; + pos = savedPos1; + } + } else { + var result2 = null; + pos = savedPos1; + } + } else { + var result2 = null; + pos = savedPos1; + } + } else { + var result2 = null; + pos = savedPos1; + } + var result3 = result2 !== null + ? (function(h1, h2, h3, h4) { + return String.fromCharCode(parseInt("0x" + h1 + h2 + h3 + h4)); + })(result2[1], result2[2], result2[3], result2[4]) + : null; + if (result3 !== null) { + var result1 = result3; + } else { + var result1 = null; + pos = savedPos0; + } + if (result1 !== null) { + var result0 = result1; + } else { + var result0 = null;; + }; + }; + }; + }; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_digits() { + var cacheKey = 'digits@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + if (input.substr(pos).match(/^[0-9]/) !== null) { + var result3 = input.charAt(pos); + pos++; + } else { + var result3 = null; + if (reportMatchFailures) { + matchFailed("[0-9]"); + } + } + if (result3 !== null) { + var result1 = []; + while (result3 !== null) { + result1.push(result3); + if (input.substr(pos).match(/^[0-9]/) !== null) { + var result3 = input.charAt(pos); + pos++; + } else { + var result3 = null; + if (reportMatchFailures) { + matchFailed("[0-9]"); + } + } + } + } else { + var result1 = null; + } + var result2 = result1 !== null + ? (function(ds) { + return parseInt((ds.join('')), 10); + })(result1) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_hexDigit() { + var cacheKey = 'hexDigit@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + if (input.substr(pos).match(/^[0-9a-fA-F]/) !== null) { + var result0 = input.charAt(pos); + pos++; + } else { + var result0 = null; + if (reportMatchFailures) { + matchFailed("[0-9a-fA-F]"); + } + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse__() { + var cacheKey = '_@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + var savedReportMatchFailures = reportMatchFailures; + reportMatchFailures = false; + var savedPos0 = pos; + var result1 = []; + var result3 = parse_whitespace(); + while (result3 !== null) { + result1.push(result3); + var result3 = parse_whitespace(); + } + var result2 = result1 !== null + ? (function(w) { return w.join(''); })(result1) + : null; + if (result2 !== null) { + var result0 = result2; + } else { + var result0 = null; + pos = savedPos0; + } + reportMatchFailures = savedReportMatchFailures; + if (reportMatchFailures && result0 === null) { + matchFailed("whitespace"); + } + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_whitespace() { + var cacheKey = 'whitespace@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + if (input.substr(pos).match(/^[ \n\r]/) !== null) { + var result0 = input.charAt(pos); + pos++; + } else { + var result0 = null; + if (reportMatchFailures) { + matchFailed("[ \\n\\r]"); + } + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function buildErrorMessage() { + function buildExpected(failuresExpected) { + failuresExpected.sort(); + + var lastFailure = null; + var failuresExpectedUnique = []; + for (var i = 0; i < failuresExpected.length; i++) { + if (failuresExpected[i] !== lastFailure) { + failuresExpectedUnique.push(failuresExpected[i]); + lastFailure = failuresExpected[i]; + } + } + + switch (failuresExpectedUnique.length) { + case 0: + return 'end of input'; + case 1: + return failuresExpectedUnique[0]; + default: + return failuresExpectedUnique.slice(0, failuresExpectedUnique.length - 1).join(', ') + + ' or ' + + failuresExpectedUnique[failuresExpectedUnique.length - 1]; + } + } + + var expected = buildExpected(rightmostMatchFailuresExpected); + var actualPos = Math.max(pos, rightmostMatchFailuresPos); + var actual = actualPos < input.length + ? quote(input.charAt(actualPos)) + : 'end of input'; + + return 'Expected ' + expected + ' but ' + actual + ' found.'; + } + + function computeErrorPosition() { + /* + * The first idea was to use |String.split| to break the input up to the + * error position along newlines and derive the line and column from + * there. However IE's |split| implementation is so broken that it was + * enough to prevent it. + */ + + var line = 1; + var column = 1; + var seenCR = false; + + for (var i = 0; i < rightmostMatchFailuresPos; i++) { + var ch = input.charAt(i); + if (ch === '\n') { + if (!seenCR) { line++; } + column = 1; + seenCR = false; + } else if (ch === '\r' | ch === '\u2028' || ch === '\u2029') { + line++; + column = 1; + seenCR = true; + } else { + column++; + seenCR = false; + } + } + + return { line: line, column: column }; + } + + + + var result = parseFunctions[startRule](); + + /* + * The parser is now in one of the following three states: + * + * 1. The parser successfully parsed the whole input. + * + * - |result !== null| + * - |pos === input.length| + * - |rightmostMatchFailuresExpected| may or may not contain something + * + * 2. The parser successfully parsed only a part of the input. + * + * - |result !== null| + * - |pos < input.length| + * - |rightmostMatchFailuresExpected| may or may not contain something + * + * 3. The parser did not successfully parse any part of the input. + * + * - |result === null| + * - |pos === 0| + * - |rightmostMatchFailuresExpected| contains at least one failure + * + * All code following this comment (including called functions) must + * handle these states. + */ + if (result === null || pos !== input.length) { + var errorPosition = computeErrorPosition(); + throw new this.SyntaxError( + buildErrorMessage(), + errorPosition.line, + errorPosition.column + ); + } + + return result; + }, + + /* Returns the parser source code. */ + toSource: function() { return this._source; } + }; + + /* Thrown when a parser encounters a syntax error. */ + + result.SyntaxError = function(message, line, column) { + this.name = 'SyntaxError'; + this.message = message; + this.line = line; + this.column = column; + }; + + result.SyntaxError.prototype = Error.prototype; + + return result; + })(); + + MessageFormat.prototype.parse = function () { + // Bind to itself so error handling works + return mparser.parse.apply( mparser, arguments ); + }; + + MessageFormat.prototype.precompile = function ( ast ) { + var self = this, + needOther = false, + fp = { + begin: 'function(d){\nvar r = "";\n', + end : "return r;\n}" + }; + + function interpMFP ( ast, data ) { + // Set some default data + data = data || {}; + var s = '', i, tmp, lastkeyname; + + switch ( ast.type ) { + case 'program': + return interpMFP( ast.program ); + case 'messageFormatPattern': + for ( i = 0; i < ast.statements.length; ++i ) { + s += interpMFP( ast.statements[i], data ); + } + return fp.begin + s + fp.end; + case 'messageFormatPatternRight': + for ( i = 0; i < ast.statements.length; ++i ) { + s += interpMFP( ast.statements[i], data ); + } + return s; + case 'messageFormatElement': + data.pf_count = data.pf_count || 0; + s += 'if(!d){\nthrow new Error("MessageFormat: No data passed to function.");\n}\n'; + if ( ast.output ) { + s += 'r += d["' + ast.argumentIndex + '"];\n'; + } + else { + lastkeyname = 'lastkey_'+(data.pf_count+1); + s += 'var '+lastkeyname+' = "'+ast.argumentIndex+'";\n'; + s += 'var k_'+(data.pf_count+1)+'=d['+lastkeyname+'];\n'; + s += interpMFP( ast.elementFormat, data ); + } + return s; + case 'elementFormat': + if ( ast.key === 'select' ) { + s += interpMFP( ast.val, data ); + s += 'r += (pf_' + + data.pf_count + + '[ k_' + (data.pf_count+1) + ' ] || pf_'+data.pf_count+'[ "other" ])( d );\n'; + } + else if ( ast.key === 'plural' ) { + s += interpMFP( ast.val, data ); + s += 'if ( pf_'+(data.pf_count)+'[ k_'+(data.pf_count+1)+' + "" ] ) {\n'; + s += 'r += pf_'+data.pf_count+'[ k_'+(data.pf_count+1)+' + "" ]( d ); \n'; + s += '}\nelse {\n'; + s += 'r += (pf_' + + data.pf_count + + '[ MessageFormat.locale["' + + self.fallbackLocale + + '"]( k_'+(data.pf_count+1)+' - off_'+(data.pf_count)+' ) ] || pf_'+data.pf_count+'[ "other" ] )( d );\n'; + s += '}\n'; + } + return s; + /* // Unreachable cases. + case 'pluralStyle': + case 'selectStyle':*/ + case 'pluralFormatPattern': + data.pf_count = data.pf_count || 0; + s += 'var off_'+data.pf_count+' = '+ast.offset+';\n'; + s += 'var pf_' + data.pf_count + ' = { \n'; + needOther = true; + // We're going to simultaneously check to make sure we hit the required 'other' option. + + for ( i = 0; i < ast.pluralForms.length; ++i ) { + if ( ast.pluralForms[ i ].key === 'other' ) { + needOther = false; + } + if ( tmp ) { + s += ',\n'; + } + else{ + tmp = 1; + } + s += '"' + ast.pluralForms[ i ].key + '" : ' + interpMFP( ast.pluralForms[ i ].val, + (function(){ var res = JSON.parse(JSON.stringify(data)); res.pf_count++; return res; })() ); + } + s += '\n};\n'; + if ( needOther ) { + throw new Error("No 'other' form found in pluralFormatPattern " + data.pf_count); + } + return s; + case 'selectFormatPattern': + + data.pf_count = data.pf_count || 0; + s += 'var off_'+data.pf_count+' = 0;\n'; + s += 'var pf_' + data.pf_count + ' = { \n'; + needOther = true; + + for ( i = 0; i < ast.pluralForms.length; ++i ) { + if ( ast.pluralForms[ i ].key === 'other' ) { + needOther = false; + } + if ( tmp ) { + s += ',\n'; + } + else{ + tmp = 1; + } + s += '"' + ast.pluralForms[ i ].key + '" : ' + interpMFP( ast.pluralForms[ i ].val, + (function(){ + var res = JSON.parse( JSON.stringify( data ) ); + res.pf_count++; + return res; + })() + ); + } + s += '\n};\n'; + if ( needOther ) { + throw new Error("No 'other' form found in selectFormatPattern " + data.pf_count); + } + return s; + /* // Unreachable + case 'pluralForms': + */ + case 'string': + return 'r += "' + MessageFormat.Utils.numSub( + MessageFormat.Utils.escapeExpression( ast.val ), + 'k_' + data.pf_count + ' - off_' + ( data.pf_count - 1 ), + data.pf_count + ) + '";\n'; + default: + throw new Error( 'Bad AST type: ' + ast.type ); + } + } + return interpMFP( ast ); + }; + + MessageFormat.prototype.compile = function ( message ) { + return (new Function( 'MessageFormat', + 'return ' + + this.precompile( + this.parse( message ) + ) + ))(MessageFormat); + }; + + + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = MessageFormat; + } + exports.MessageFormat = MessageFormat; + } + else if (typeof define === 'function' && define.amd) { + define(function() { + return MessageFormat; + }); + } + else { + root['MessageFormat'] = MessageFormat; + } + +})( this ); diff --git a/test/core/localizer-spec.js b/test/core/localizer-spec.js new file mode 100644 index 0000000000..8cb57c5fee --- /dev/null +++ b/test/core/localizer-spec.js @@ -0,0 +1,390 @@ +/*true
.
+ @type Boolean
+ @default false
+ @example
+// require localizer
+var defaultLocalizer = localizer.defaultLocalizer,
+ _ = localizer.localize;
+
+exports.Main = Montage.create(Component, {
+
+ didCreate: {
+ value: function() {
+ this.localizer = defaultLocalizer;
+ this.waitForLocalizerMessages = true;
+ }
+ },
+
+ // ...
+
+ // no draw happens until the localizer's messages have been loaded
+ prepareForDraw: {
+ value: function() {
+ this._greeting = _("hello", "Hello {name}!");
+ }
+ },
+ draw: {
+ value: function() {
+ // this is for illustration only. This example is simple enough that
+ // you should use a localizations binding
+ this._element.textContent = this._greeting({name: this.name});
+ }
}
+}
+ */
+ waitForLocalizerMessages: {
+ enumerable: false,
+ get: function() {
+ return this._waitForLocalizerMessages;
+ },
+ set: function(value) {
+ if (this._waitForLocalizerMessages !== value) {
+ if (value === true && !this.localizer.messages) {
+ if (!this.localizer) {
+ throw "Cannot wait for messages on localizer if it is not set";
+ }
+
+ this._waitForLocalizerMessages = true;
+
+ var self = this;
+ logger.debug(this, "waiting for messages from localizer");
+ this.canDrawGate.setField("messages", false);
+
+ this.localizer.messagesPromise.then(function(messages) {
+ if (logger.isDebug) {
+ logger.debug(self, "got messages from localizer");
+ }
+ self.canDrawGate.setField("messages", true);
+ });
+ } else {
+ this._waitForLocalizerMessages = false;
+ this.canDrawGate.setField("messages", true);
+ }
+ }
+ }
+ },
+
});