| 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 |
|---|
| 30 | if (!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 | */ |
|---|
| 41 | jala.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 | */ |
|---|
| 103 | jala.I18n.HANDLER = global; |
|---|
| 104 | |
|---|
| 105 | /** @ignore */ |
|---|
| 106 | jala.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 | */ |
|---|
| 116 | jala.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 | */ |
|---|
| 129 | jala.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 | */ |
|---|
| 156 | jala.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 | */ |
|---|
| 191 | jala.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 | */ |
|---|
| 218 | jala.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 | */ |
|---|
| 264 | jala.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 | */ |
|---|
| 288 | jala.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 | */ |
|---|
| 301 | jala.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 | */ |
|---|
| 323 | jala.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 | */ |
|---|
| 383 | jala.i18n = new jala.I18n(); |
|---|
| 384 | |
|---|
| 385 | /** |
|---|
| 386 | * For convenience reasons the public methods and macros are |
|---|
| 387 | * put into global scope too |
|---|
| 388 | */ |
|---|
| 389 | var gettext = function() { |
|---|
| 390 | return jala.i18n.gettext.apply(jala.i18n, arguments); |
|---|
| 391 | }; |
|---|
| 392 | var ngettext = function() { |
|---|
| 393 | return jala.i18n.ngettext.apply(jala.i18n, arguments); |
|---|
| 394 | }; |
|---|
| 395 | var markgettext = function() { |
|---|
| 396 | return jala.i18n.markgettext.apply(jala.i18n, arguments); |
|---|
| 397 | }; |
|---|
| 398 | var message_macro = function() { |
|---|
| 399 | return jala.i18n.message_macro.apply(jala.i18n, arguments); |
|---|
| 400 | }; |
|---|