root/trunk/code/I18n.js

Revision 135, 13.0 KB (checked in by tobi, 3 years ago)
  • Added private property localeGetter assigned with default method for retrieving the locale
  • Added public setter and getter methods setLocaleGetter and getLocaleGetter for writing and reading localeGetter
  • Replaced occurrences of res.meta.locale with getLocaleGetter()() (fixes #11)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author HeadURL Id
Line 
1//
2// Jala Project [http://opensvn.csie.org/traccgi/jala]
3//
4// Copyright 2004 ORF Online und Teletext GmbH
5//
6// Licensed under the Apache License, Version 2.0 (the ``License'');
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//    http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an ``AS IS'' BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17//
18// $Revision$
19// $LastChangedBy$
20// $LastChangedDate$
21// $HeadURL$
22//
23
24/**
25 * @fileoverview Methods and macros for internationalization
26 * of Helma applications.
27 */
28
29// Define the global namespace for Jala modules
30if (!global.jala) {
31   global.jala = {};
32}
33
34/**
35 * Constructs a new instance of jala.I18n
36 * @class This class provides various functions and macros for
37 * internationalization of Helma applications.
38 * @constructor
39 * @type jala.I18n
40 */
41jala.I18n = function() {
42   /**
43    * The default object containing the messages.
44    * @ignore
45    */
46   var messages = global.messages;
47   
48   /**
49    * The default method for retrieving the locale.
50    * @ignore
51    */
52   var localeGetter = function() {
53      return java.util.Locale.getDefault();
54   };
55   
56   /**
57    * Overwrite the default object containing
58    * the messages (ie. a vanilla EcmaScript object).
59    * @param {Object} msgObject The object containing the messages
60    */
61   this.setMessages = function(msgObject) {
62      messages = msgObject;
63   };
64   
65   /**
66    * Get the message object.
67    * @returns The object containing the messages
68    * @type Object
69    */
70   this.getMessages = function() {
71      return messages;
72   };
73   
74   /**
75    * Set the method for retrieving the locale.
76    * @param {Function} func The getter method
77    */
78   this.setLocaleGetter = function(func) {
79      if (func && func.constructor == Function) {
80         localeGetter = func;
81      } else {
82         throw Error("Getter method to retrieve locale must be a function");
83      }
84      return;
85   };
86   
87   /**
88    * Get the method for retrieving the locale.
89    * @returns The getter method
90    * @type Function
91    */
92   this.getLocaleGetter = function() {
93      return localeGetter;
94   };
95   
96   return this;
97};
98
99/**
100 * The default handler containing the messages.
101 * @ignore
102 */
103jala.I18n.HANDLER = global;
104
105/** @ignore */
106jala.I18n.prototype.toString = function() {
107   return "[Jala i18n]";
108};
109
110/**
111 * Set (overwrite) the default handler containing
112 * the messages (ie. a vanilla EcmaScript object).
113 * @param {Object} handler The handler containing the message object
114 * @deprecated Use {@link #setMessages} instead
115 */
116jala.I18n.prototype.setHandler = function(handler) {
117   this.setMessages(handler.messages);
118   return;
119};
120
121/**
122 * Returns the locale for the given id, which is expected to follow
123 * the form <code>language[_COUNTRY][_variant]</code>, where <code>language</code>
124 * is a valid ISO Language Code (eg. "de"), <code>COUNTRY</code> a valid ISO
125 * Country Code (eg. "AT"), and variant an identifier for the variant to use.
126 * @returns The locale for the given id
127 * @type java.util.Locale
128 */
129jala.I18n.prototype.getLocale = function(localeId) {
130   if (localeId) {
131      if (localeId.indexOf("_") > -1) {
132         var arr = localeId.split("_");
133         if (arr.length == 3) {
134            return new java.util.Locale(arr[0], arr[1], arr[2]);
135         } else {
136            return new java.util.Locale(arr[0], arr[1]);
137         }
138      } else {
139         return new java.util.Locale(localeId);
140      }
141   }
142   return java.util.Locale.getDefault();
143}
144
145/**
146 * Tries to "translate" the given message key into a localized
147 * message.
148 * @param {String} key The message to translate (required)
149 * @param {String} plural The plural form of the message to translate
150 * @param {Number} amount A number to determine whether to use the
151 * singular or plural form of the message
152 * @returns The localized message or the appropriate key if no
153 * localized message was found
154 * @type String
155 */
156jala.I18n.prototype.translate = function(singularKey, pluralKey, amount) {
157   var translation = null;
158   if (singularKey) {
159      // use the getter method for retrieving the locale
160      var locale = this.getLocaleGetter()();
161      var catalog, key;
162      if ((catalog = jala.i18n.getCatalog(locale))) {
163         if (arguments.length == 3 && amount != 1) { // is plural
164            key = pluralKey;
165         } else {
166            key = singularKey;
167         }
168         if (!(translation = catalog[key])) {
169            translation = key;
170            app.logger.debug("jala.i18n.translate(): Can't find message '" + 
171                             key + "' for locale '" + locale + "'");         
172         }
173      } else {
174         app.logger.debug("jala.i18n.translate(): Can't find message catalog for locale '" + locale + "'");
175         if (!pluralKey || amount == 1) {
176            translation = singularKey;
177         } else {
178            translation = pluralKey;
179         }
180      }
181   }
182   return translation;
183};
184
185/**
186 * Helper method to get the message catalog 
187 * corresponding to the actual locale.
188 * @params {java.util.Locale} locale
189 * @returns The message catalog.
190 */
191jala.I18n.prototype.getCatalog = function(locale) {
192   if (!jala.I18n.catalogs) {
193      jala.I18n.catalogs = {};
194   }
195   var cache = jala.I18n.catalogs;
196   var catalog, messages;
197   if (!cache[locale] && (messages = this.getMessages()) != null) {
198      var arr = [locale.getLanguage(), locale.getCountry(), locale.getVariant()];
199      while (arr.length > 0 && !(catalog = messages[arr.join("_")])) {
200         arr.pop();
201      }
202      cache[locale] = catalog;
203   }
204   return cache[locale];
205};
206
207/**
208 * Converts the message passed as argument into an instance
209 * of java.text.MessageFormat, and formats it using the
210 * replacement values passed.
211 * @param {String} message The message to format
212 * @param {Array} values An optional array containing replacement values
213 * @returns The formatted message or, if the formatting fails, the
214 * message passed as argument.
215 * @type String
216 * @see http://java.sun.com/j2se/1.5.0/docs/api/java/text/MessageFormat.html
217 */
218jala.I18n.prototype.formatMessage = function(message, values) {
219   if (message) {
220      var args = null;
221      if (values != null && values.length > 0) {
222         args = java.lang.reflect.Array.newInstance(java.lang.Object, values.length);
223         var arg;
224         for (var i=0;i<values.length;i++) {
225            if ((arg = values[i]) != null) {
226               // MessageFormat can't deal with javascript date objects
227               // so we need to convert them into java.util.Date instances
228               if (arg instanceof Date) {
229                  args[i] = new java.util.Date(arg.getTime());
230               } else {
231                  args[i] = arg;
232               }
233            }
234         }
235      }
236      // use the getter method for retrieving the locale
237      var locale = this.getLocaleGetter()();
238      // format the message
239      try {
240         var msgFormat = new java.text.MessageFormat(message, locale);
241         return msgFormat.format(args);
242      } catch (e) {
243         app.logger.warn("jala.i18n.formatMessage(): Unable to format message '"
244                         + message + "', reason: " + e, e.javaException);
245      }
246   }
247   return null;
248};
249
250/**
251 * Returns a localized message for the message key passed as
252 * argument. If no localization is found, the message key
253 * is returned. Any additional arguments passed to this function
254 * will be used as replacement values during message rendering.
255 * To reference these values the message can contain placeholders
256 * following "{number}" notation, where <code>number</code> must
257 * match the number of the additional argument (starting with zero).
258 * @param {String} key The message to localize
259 * @returns The translated message
260 * @type String
261 * @see #translate
262 * @see #formatMessage
263 */
264jala.I18n.prototype.gettext = function(key /** [value 0][, value 1][, ...] */) {
265   return this.formatMessage(this.translate(key),
266                             Array.prototype.splice.call(arguments, 1));
267};
268
269/**
270 * Returns a localized message for the message key passed as
271 * argument. In contrast to gettext() this method
272 * can handle plural forms based on the amount passed as argument.
273 * If no localization is found, the appropriate message key is
274 * returned. Any additional arguments passed to this function
275 * will be used as replacement values during message rendering.
276 * To reference these values the message can contain placeholders
277 * following "{number}" notation, where <code>number</code> must
278 * match the number of the additional argument (starting with zero).
279 * @param {String} singularKey The singular message to localize
280 * @param {String} pluralKey The plural form of the message to localize
281 * @param {Number} amount The amount which is used to determine
282 * whether the singular or plural form of the message should be returned.
283 * @returns The translated message
284 * @type String
285 * @see #translate
286 * @see #formatMessage
287 */
288jala.I18n.prototype.ngettext = function(singularKey, pluralKey, amount /** [value 0][, value 1][, ...] */) {
289   return this.formatMessage(this.translate(singularKey, pluralKey, amount || 0),
290                             Array.prototype.splice.call(arguments, 2));
291};
292
293/**
294 * A simple proxy method which is used to mark a message string
295 * for the i18n parser as to be translated.
296 * @param {String} key The message that should be seen by the
297 * i18n parser as to be translated.
298 * @returns The message in unmodified form
299 * @type String
300 */
301jala.I18n.prototype.markgettext = function(key) {
302   return key;
303};
304
305/**
306 * Returns a translated message. The following macro attributes
307 * are accepted:
308 * <ul>
309 * <li>text: The message to translate (required)</li>
310 * <li>plural: The plural form of the message</li>
311 * <li>values: A list of replacement values. Use a comma to separate more
312 * than one value. Each value is either interpreted as a global property
313 * (if it doesn't containg a dot) or as a property name of the given macro
314 * handler object (eg. "user.name"). If the value of the property is a
315 * HopObject or an Array this macro uses the size() resp. length of the
316 * object, otherwise the string representation of the object will be used.</li>
317 * </ul>
318 * @returns The translated message
319 * @type String
320 * @see #gettext
321 * @see #ngettext
322 */
323jala.I18n.prototype.message_macro = function(param) {
324   if (param.text) {
325      var args = [param.text];
326      if (param.plural) {
327         args[args.length] = param.plural;
328      }
329      if (param.values != null) {
330         var arr = param.values.split(/\s*,\s*/g);
331         // convert replacement values: if the value name doesn't contain
332         // a dot, look for a global property with that name, otherwise
333         // for a property of the specified macro handler object.
334         var propName, dotIdx, handlerName, handler;
335         for (var i=0;i<arr.length;i++) {
336            if ((propName = arr[i]) != null) {
337               var value = null;
338               if ((dotIdx = propName.indexOf(".")) > 0) {
339                  var handlerName = propName.substring(0, dotIdx);
340                  if (handlerName == "request") {
341                     handler = req.data;
342                  } else if (handlerName == "response") {
343                     handler = res.data;
344                  } else if (!(handler = res.handlers[handlerName])) {
345                     continue;
346                  }
347                  propName = propName.substring(dotIdx + 1);
348                  // primitive security: don't allow access to internal properties
349                  // and a property named "password"
350                  if (propName.charAt(0) != "_" && propName.toLowerCase() != "password") {
351                     value = handler[propName];
352                  }
353               } else {
354                  value = global[propName];
355               }
356               if (value != null) {
357                  // if its a HopObject collection or Array, use its size/length
358                  // as value
359                  if (value instanceof HopObject) {
360                     value = value.size();
361                  } else if (value instanceof Array) {
362                     value = value.length;
363                  }
364               }
365               args[args.length] = value;
366            }
367         }
368      }
369      if (param.plural) {
370         return this.ngettext.apply(this, args);
371      } else {
372         return this.gettext.apply(this, args);
373      }
374   }
375   return;
376};
377
378/**
379 * Default i18n class instance.
380 * @type jala.I18n
381 * @final
382 */
383jala.i18n = new jala.I18n();
384
385/**
386 * For convenience reasons the public methods and macros are
387 * put into global scope too
388 */
389var gettext = function() {
390   return jala.i18n.gettext.apply(jala.i18n, arguments);
391};
392var ngettext = function() {
393   return jala.i18n.ngettext.apply(jala.i18n, arguments);
394};
395var markgettext = function() {
396   return jala.i18n.markgettext.apply(jala.i18n, arguments);
397};
398var message_macro = function() {
399   return jala.i18n.message_macro.apply(jala.i18n, arguments);
400};
Note: See TracBrowser for help on using the browser.