上次已经讲了如何在MVC项目中实现中英文转写,但是本子资源文件只能直接作用于html页面或者后台代码中,对于引用中的js代码无法做到转写。
JS代码中的转写我们用到了jQuery.i18n.properties 这款插件,它能实现web前端的国际化。
使用jQuery.i18n.properties 时,我们用到两个核心的js脚本:(也可自行下载)
(1) jquery.i18n.js
1 /*! 2 * jQuery i18n plugin 3 * @requires jQuery v1.1 or later 4 * 5 * See https://github.com/recurser/jquery-i18n 6 * 7 * Licensed under the MIT license. 8 * 9 * Version: 1.1.1 (Sun, 05 Jan 2014 05:26:50 GMT) 10 */ 11 (function($) { 12 /** 13 * i18n provides a mechanism for translating strings using a jscript dictionary. 14 * 15 */ 16 17 var __slice = Array.prototype.slice; 18 19 /* 20 * i18n property list 21 */ 22 var i18n = { 23 24 dict: null, 25 26 /** 27 * load() 28 * 29 * Load translations. 30 * 31 * @param property_list i18n_dict : The dictionary to use for translation. 32 */ 33 load: function(i18n_dict) { 34 if (this.dict !== null) { 35 $.extend(this.dict, i18n_dict); 36 } else { 37 this.dict = i18n_dict; 38 } 39 }, 40 41 /** 42 * _() 43 * 44 * Looks the given string up in the dictionary and returns the translation if 45 * one exists. If a translation is not found, returns the original word. 46 * 47 * @param string str : The string to translate. 48 * @param property_list params.. : params for using printf() on the string. 49 * 50 * @return string : Translated word. 51 */ 52 _: function (str) { 53 dict = this.dict; 54 if (dict && dict.hasOwnProperty(str)) { 55 str = dict[str]; 56 } 57 args = __slice.call(arguments); 58 args[0] = str; 59 // Substitute any params. 60 return this.printf.apply(this, args); 61 }, 62 63 /* 64 * printf() 65 * 66 * Substitutes %s with parameters given in list. %%s is used to escape %s. 67 * 68 * @param string str : String to perform printf on. 69 * @param string args : Array of arguments for printf. 70 * 71 * @return string result : Substituted string 72 */ 73 printf: function(str, args) { 74 if (arguments.length < 2) return str; 75 args = $.isArray(args) ? args : __slice.call(arguments, 1); 76 return str.replace(/([^%]|^)%(?:(\d+)\$)?s/g, function(p0, p, position) { 77 if (position) { 78 return p + args[parseInt(position)-1]; 79 } 80 return p + args.shift(); 81 }).replace(/%%s/g, '%s'); 82 } 83 84 }; 85 86 /* 87 * _t() 88 * 89 * Allows you to translate a jQuery selector. 90 * 91 * eg $('h1')._t('some text') 92 * 93 * @param string str : The string to translate . 94 * @param property_list params : Params for using printf() on the string. 95 * 96 * @return element : Chained and translated element(s). 97 */ 98 $.fn._t = function(str, params) { 99 return $(this).html(i18n._.apply(i18n, arguments)); 100 }; 101 102 $.i18n = i18n; 103 })(jQuery);
(2) jquery.i18n.properties.js
1 /****************************************************************************** 2 * jquery.i18n.properties 3 * 4 * Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and 5 * MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. 6 * 7 * @version 1.1.x 8 * @author Nuno Fernandes 9 * Matthew Lohbihler 10 * @url www.codingwithcoffee.com 11 * @inspiration Localisation assistance for jQuery (http://keith-wood.name/localisation.html) 12 * by Keith Wood (kbwood{at}iinet.com.au) June 2007 13 * 14 *****************************************************************************/ 15 16 (function($) { 17 $.i18n = {}; 18 19 /** Map holding bundle keys (if mode: 'map') */ 20 $.i18n.map = {}; 21 22 /** 23 * Load and parse message bundle files (.properties), 24 * making bundles keys available as javascript variables. 25 * 26 * i18n files are named <name>.js, or <name>_<language>.js or <name>_<language>_<country>.js 27 * Where: 28 * The <language> argument is a valid ISO Language Code. These codes are the lower-case, 29 * two-letter codes as defined by ISO-639. You can find a full list of these codes at a 30 * number of sites, such as: http://www.loc.gov/standards/iso639-2/englangn.html 31 * The <country> argument is a valid ISO Country Code. These codes are the upper-case, 32 * two-letter codes as defined by ISO-3166. You can find a full list of these codes at a 33 * number of sites, such as: http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html 34 * 35 * Sample usage for a bundles/Messages.properties bundle: 36 * $.i18n.properties({ 37 * name: 'Messages', 38 * language: 'en_US', 39 * path: 'bundles' 40 * }); 41 * @param name (string/string[], optional) names of file to load (eg, 'Messages' or ['Msg1','Msg2']). Defaults to "Messages" 42 * @param language (string, optional) language/country code (eg, 'en', 'en_US', 'pt_PT'). if not specified, language reported by the browser will be used instead. 43 * @param path (string, optional) path of directory that contains file to load 44 * @param mode (string, optional) whether bundles keys are available as JavaScript variables/functions or as a map (eg, 'vars' or 'map') 45 * @param cache (boolean, optional) whether bundles should be cached by the browser, or forcibly reloaded on each page load. Defaults to false (i.e. forcibly reloaded) 46 * @param encoding (string, optional) the encoding to request for bundles. Property file resource bundles are specified to be in ISO-8859-1 format. Defaults to UTF-8 for backward compatibility. 47 * @param callback (function, optional) callback function to be called after script is terminated 48 */ 49 $.i18n.properties = function(settings) { 50 // set up settings 51 var defaults = { 52 name: 'Messages', 53 language: '', 54 path: '', 55 mode: 'vars', 56 cache: false, 57 encoding: 'UTF-8', 58 callback: null 59 }; 60 settings = $.extend(defaults, settings); 61 if(settings.language === null || settings.language == '') { 62 settings.language = $.i18n.browserLang(); 63 } 64 if(settings.language === null) {settings.language='';} 65 66 // load and parse bundle files 67 var files = getFiles(settings.name); 68 for(i=0; i<files.length; i++) { 69 // 1. load base (eg, Messages.properties) 70 loadAndParseFile(settings.path + files[i] + '.properties', settings); 71 // 2. with language code (eg, Messages_pt.properties) 72 if(settings.language.length >= 2) { 73 loadAndParseFile(settings.path + files[i] + '_' + settings.language.substring(0, 2) +'.properties', settings); 74 } 75 // 3. with language code and country code (eg, Messages_pt_PT.properties) 76 if(settings.language.length >= 5) { 77 loadAndParseFile(settings.path + files[i] + '_' + settings.language.substring(0, 5) +'.properties', settings); 78 } 79 } 80 81 // call callback 82 if(settings.callback){ settings.callback(); } 83 }; 84 85 86 /** 87 * When configured with mode: 'map', allows access to bundle values by specifying its key. 88 * Eg, jQuery.i18n.prop('com.company.bundles.menu_add') 89 */ 90 $.i18n.prop = function(key /* Add parameters as function arguments as necessary */) { 91 var value = $.i18n.map[key]; 92 if (value == null) 93 return '[' + key + ']'; 94 95 var phvList; 96 if (arguments.length == 2 && $.isArray(arguments[1])) 97 // An array was passed as the only parameter, so assume it is the list of place holder values. 98 phvList = arguments[1]; 99 100 // Place holder replacement 101 /** 102 * Tested with: 103 * test.t1=asdf ''{0}'' 104 * test.t2=asdf '{0}' '{1}'{1}'zxcv 105 * test.t3=This is \"a quote" 'a''{0}''s'd{fgh{ij' 106 * test.t4="'''{'0}''" {0}{a} 107 * test.t5="'''{0}'''" {1} 108 * test.t6=a {1} b {0} c 109 * test.t7=a 'quoted \\ s\ttringy' \t\t x 110 * 111 * Produces: 112 * test.t1, p1 ==> asdf 'p1' 113 * test.t2, p1 ==> asdf {0} {1}{1}zxcv 114 * test.t3, p1 ==> This is "a quote" a'{0}'sd{fgh{ij 115 * test.t4, p1 ==> "'{0}'" p1{a} 116 * test.t5, p1 ==> "'{0}'" {1} 117 * test.t6, p1 ==> a {1} b p1 c 118 * test.t6, p1, p2 ==> a p2 b p1 c 119 * test.t6, p1, p2, p3 ==> a p2 b p1 c 120 * test.t7 ==> a quoted \ s tringy x 121 */ 122 123 var i; 124 if (typeof(value) == 'string') { 125 // Handle escape characters. Done separately from the tokenizing loop below because escape characters are 126 // active in quoted strings. 127 i = 0; 128 while ((i = value.indexOf('\\', i)) != -1) { 129 if (value.charAt(i+1) == 't') 130 value = value.substring(0, i) + '\t' + value.substring((i++) + 2); // tab 131 else if (value.charAt(i+1) == 'r') 132 value = value.substring(0, i) + '\r' + value.substring((i++) + 2); // return 133 else if (value.charAt(i+1) == 'n') 134 value = value.substring(0, i) + '\n' + value.substring((i++) + 2); // line feed 135 else if (value.charAt(i+1) == 'f') 136 value = value.substring(0, i) + '\f' + value.substring((i++) + 2); // form feed 137 else if (value.charAt(i+1) == '\\') 138 value = value.substring(0, i) + '\\' + value.substring((i++) + 2); // \ 139 else 140 value = value.substring(0, i) + value.substring(i+1); // Quietly drop the character 141 } 142 143 // Lazily convert the string to a list of tokens. 144 var arr = [], j, index; 145 i = 0; 146 while (i < value.length) { 147 if (value.charAt(i) == '\'') { 148 // Handle quotes 149 if (i == value.length-1) 150 value = value.substring(0, i); // Silently drop the trailing quote 151 else if (value.charAt(i+1) == '\'') 152 value = value.substring(0, i) + value.substring(++i); // Escaped quote 153 else { 154 // Quoted string 155 j = i + 2; 156 while ((j = value.indexOf('\'', j)) != -1) { 157 if (j == value.length-1 || value.charAt(j+1) != '\'') { 158 // Found start and end quotes. Remove them 159 value = value.substring(0,i) + value.substring(i+1, j) + value.substring(j+1); 160 i = j - 1; 161 break; 162 } 163 else { 164 // Found a double quote, reduce to a single quote. 165 value = value.substring(0,j) + value.substring(++j); 166 } 167 } 168 169 if (j == -1) { 170 // There is no end quote. Drop the start quote 171 value = value.substring(0,i) + value.substring(i+1); 172 } 173 } 174 } 175 else if (value.charAt(i) == '{') { 176 // Beginning of an unquoted place holder. 177 j = value.indexOf('}', i+1); 178 if (j == -1) 179 i++; // No end. Process the rest of the line. Java would throw an exception 180 else { 181 // Add 1 to the index so that it aligns with the function arguments. 182 index = parseInt(value.substring(i+1, j)); 183 if (!isNaN(index) && index >= 0) { 184 // Put the line thus far (if it isn't empty) into the array 185 var s = value.substring(0, i); 186 if (s != "") 187 arr.push(s); 188 // Put the parameter reference into the array 189 arr.push(index); 190 // Start the processing over again starting from the rest of the line. 191 i = 0; 192 value = value.substring(j+1); 193 } 194 else 195 i = j + 1; // Invalid parameter. Leave as is. 196 } 197 } 198 else 199 i++; 200 } 201 202 // Put the remainder of the no-empty line into the array. 203 if (value != "") 204 arr.push(value); 205 value = arr; 206 207 // Make the array the value for the entry. 208 $.i18n.map[key] = arr; 209 } 210 211 if (value.length == 0) 212 return ""; 213 if (value.lengh == 1 && typeof(value[0]) == "string") 214 return value[0]; 215 216 var s = ""; 217 for (i=0; i<value.length; i++) { 218 if (typeof(value[i]) == "string") 219 s += value[i]; 220 // Must be a number 221 else if (phvList && value[i] < phvList.length) 222 s += phvList[value[i]]; 223 else if (!phvList && value[i] + 1 < arguments.length) 224 s += arguments[value[i] + 1]; 225 else 226 s += "{"+ value[i] +"}"; 227 } 228 229 return s; 230 }; 231 232 /** Language reported by browser, normalized code */ 233 $.i18n.browserLang = function() { 234 return normaliseLanguageCode(navigator.language /* Mozilla */ || navigator.userLanguage /* IE */); 235 }; 236 237 238 /** Load and parse .properties files */ 239 function loadAndParseFile(filename, settings) { 240 $.ajax({ 241 url: filename, 242 async: false, 243 cache: settings.cache, 244 contentType:'text/plain;charset='+ settings.encoding, 245 dataType: 'text', 246 success: function(data, status) { 247 parseData(data, settings.mode); 248 } 249 }); 250 } 251 252 /** Parse .properties files */ 253 function parseData(data, mode) { 254 var parsed = ''; 255 var parameters = data.split( /\n/ ); 256 var regPlaceHolder = /(\{\d+\})/g; 257 var regRepPlaceHolder = /\{(\d+)\}/g; 258 var unicodeRE = /(\\u.{4})/ig; 259 for(var i=0; i<parameters.length; i++ ) { 260 parameters[i] = parameters[i].replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' ); // trim 261 if(parameters[i].length > 0 && parameters[i].match("^#")!="#") { // skip comments 262 var pair = parameters[i].split('='); 263 if(pair.length > 0) { 264 /** Process key & value */ 265 var name = unescape(pair[0]).replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' ); // trim 266 var value = pair.length == 1 ? "" : pair[1]; 267 // process multi-line values 268 while(value.match(/\\$/)=="\\") { 269 value = value.substring(0, value.length - 1); 270 value += parameters[++i].replace( /\s\s*$/, '' ); // right trim 271 } 272 // Put values with embedded '='s back together 273 for(var s=2;s<pair.length;s++){ value +='=' + pair[s]; } 274 value = value.replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' ); // trim 275 276 /** Mode: bundle keys in a map */ 277 if(mode == 'map' || mode == 'both') { 278 // handle unicode chars possibly left out 279 var unicodeMatches = value.match(unicodeRE); 280 if(unicodeMatches) { 281 for(var u=0; u<unicodeMatches.length; u++) { 282 value = value.replace( unicodeMatches[u], unescapeUnicode(unicodeMatches[u])); 283 } 284 } 285 // add to map 286 $.i18n.map[name] = value; 287 } 288 289 /** Mode: bundle keys as vars/functions */ 290 if(mode == 'vars' || mode == 'both') { 291 value = value.replace( /"/g, '\\"' ); // escape quotation mark (") 292 293 // make sure namespaced key exists (eg, 'some.key') 294 checkKeyNamespace(name); 295 296 // value with variable substitutions 297 if(regPlaceHolder.test(value)) { 298 var parts = value.split(regPlaceHolder); 299 // process function args 300 var first = true; 301 var fnArgs = ''; 302 var usedArgs = []; 303 for(var p=0; p<parts.length; p++) { 304 if(regPlaceHolder.test(parts[p]) && (usedArgs.length == 0 || usedArgs.indexOf(parts[p]) == -1)) { 305 if(!first) {fnArgs += ',';} 306 fnArgs += parts[p].replace(regRepPlaceHolder, 'v$1'); 307 usedArgs.push(parts[p]); 308 first = false; 309 } 310 } 311 parsed += name + '=function(' + fnArgs + '){'; 312 // process function body 313 var fnExpr = '"' + value.replace(regRepPlaceHolder, '"+v$1+"') + '"'; 314 parsed += 'return ' + fnExpr + ';' + '};'; 315 316 // simple value 317 }else{ 318 parsed += name+'="'+value+'";'; 319 } 320 } // END: Mode: bundle keys as vars/functions 321 } // END: if(pair.length > 0) 322 } // END: skip comments 323 } 324 eval(parsed); 325 } 326 327 /** Make sure namespace exists (for keys with dots in name) */ 328 // TODO key parts that start with numbers quietly fail. i.e. month.short.1=Jan 329 function checkKeyNamespace(key) { 330 var regDot = /\./; 331 if(regDot.test(key)) { 332 var fullname = ''; 333 var names = key.split( /\./ ); 334 for(var i=0; i<names.length; i++) { 335 if(i>0) {fullname += '.';} 336 fullname += names[i]; 337 if(eval('typeof '+fullname+' == "undefined"')) { 338 eval(fullname + '={};'); 339 } 340 } 341 } 342 } 343 344 /** Make sure filename is an array */ 345 function getFiles(names) { 346 return (names && names.constructor == Array) ? names : [names]; 347 } 348 349 /** Ensure language code is in the format aa_AA. */ 350 function normaliseLanguageCode(lang) { 351 lang = lang.toLowerCase(); 352 if(lang.length > 3) { 353 lang = lang.substring(0, 3) + lang.substring(3).toUpperCase(); 354 } 355 return lang; 356 } 357 358 /** Unescape unicode chars ('\u00e3') */ 359 function unescapeUnicode(str) { 360 // unescape unicode codes 361 var codes = []; 362 var code = parseInt(str.substr(2), 16); 363 if (code >= 0 && code < Math.pow(2, 16)) { 364 codes.push(code); 365 } 366 // convert codes to text 367 var unescaped = ''; 368 for (var i = 0; i < codes.length; ++i) { 369 unescaped += String.fromCharCode(codes[i]); 370 } 371 return unescaped; 372 } 373 374 /* Cross-Browser Split 1.0.1 375 (c) Steven Levithan <stevenlevithan.com>; MIT License 376 An ECMA-compliant, uniform cross-browser split method */ 377 var cbSplit; 378 // avoid running twice, which would break `cbSplit._nativeSplit`'s reference to the native `split` 379 if (!cbSplit) { 380 cbSplit = function(str, separator, limit) { 381 // if `separator` is not a regex, use the native `split` 382 if (Object.prototype.toString.call(separator) !== "[object RegExp]") { 383 if(typeof cbSplit._nativeSplit == "undefined") 384 return str.split(separator, limit); 385 else 386 return cbSplit._nativeSplit.call(str, separator, limit); 387 } 388 389 var output = [], 390 lastLastIndex = 0, 391 flags = (separator.ignoreCase ? "i" : "") + 392 (separator.multiline ? "m" : "") + 393 (separator.sticky ? "y" : ""), 394 separator = RegExp(separator.source, flags + "g"), // make `global` and avoid `lastIndex` issues by working with a copy 395 separator2, match, lastIndex, lastLength; 396 397 str = str + ""; // type conversion 398 if (!cbSplit._compliantExecNpcg) { 399 separator2 = RegExp("^" + separator.source + "$(?!\\s)", flags); // doesn't need /g or /y, but they don't hurt 400 } 401 402 /* behavior for `limit`: if it's... 403 - `undefined`: no limit. 404 - `NaN` or zero: return an empty array. 405 - a positive number: use `Math.floor(limit)`. 406 - a negative number: no limit. 407 - other: type-convert, then use the above rules. */ 408 if (limit === undefined || +limit < 0) { 409 limit = Infinity; 410 } else { 411 limit = Math.floor(+limit); 412 if (!limit) { 413 return []; 414 } 415 } 416 417 while (match = separator.exec(str)) { 418 lastIndex = match.index + match[0].length; // `separator.lastIndex` is not reliable cross-browser 419 420 if (lastIndex > lastLastIndex) { 421 output.push(str.slice(lastLastIndex, match.index)); 422 423 // fix browsers whose `exec` methods don't consistently return `undefined` for nonparticipating capturing groups 424 if (!cbSplit._compliantExecNpcg && match.length > 1) { 425 match[0].replace(separator2, function () { 426 for (var i = 1; i < arguments.length - 2; i++) { 427 if (arguments[i] === undefined) { 428 match[i] = undefined; 429 } 430 } 431 }); 432 } 433 434 if (match.length > 1 && match.index < str.length) { 435 Array.prototype.push.apply(output, match.slice(1)); 436 } 437 438 lastLength = match[0].length; 439 lastLastIndex = lastIndex; 440 441 if (output.length >= limit) { 442 break; 443 } 444 } 445 446 if (separator.lastIndex === match.index) { 447 separator.lastIndex++; // avoid an infinite loop 448 } 449 } 450 451 if (lastLastIndex === str.length) { 452 if (lastLength || !separator.test("")) { 453 output.push(""); 454 } 455 } else { 456 output.push(str.slice(lastLastIndex)); 457 } 458 459 return output.length > limit ? output.slice(0, limit) : output; 460 }; 461 462 cbSplit._compliantExecNpcg = /()??/.exec("")[1] === undefined; // NPCG: nonparticipating capturing group 463 cbSplit._nativeSplit = String.prototype.split; 464 465 } // end `if (!cbSplit)` 466 String.prototype.split = function (separator, limit) { 467 return cbSplit(this, separator, limit); 468 }; 469 470 })(jQuery); 471
(3)定义调用的核心函数,此处做简单封装
1 var simpleLauage = simpleLauage || { 2 htmlLang: function () { 3 return $("html").attr("lang").replace("-", "_"); 4 }, 5 lang: null, 6 init: function () { 7 jQuery.i18n.properties({ 8 name: 'lang', 9 path: '/i18n/', 10 mode: 'map', 11 language: simpleLauage.htmlLang(), 12 callback: function () { 13 simpleLauage.lang = $.i18n.prop; 14 } 15 }); 16 }, 17 merge: function () { 18 var a = arguments; 19 if (a.length < 2) { 20 return; 21 } 22 if (a[0] != null) { 23 for (var i = 1; i < a.length; i++) { 24 for (var r in a[i]) { 25 a[0][r] = a[i][r]; 26 } 27 } 28 } 29 return a[0]; 30 }, 31 mergeIfNotExist: function () { 32 var a = arguments; 33 if (a.length < 2) { 34 return; 35 } 36 for (var i = 1; i < a.length; i++) { 37 for (var r in a[i]) { 38 if (a[0][r] == null) { 39 a[0][r] = a[i][r]; 40 } 41 } 42 } 43 return a[0]; 44 } 45 }; 46 47 // 使用前记得先初始化哦 48 simpleLauage.init();
(4)在上面封装函数中,可以看到path中指向的是 资源文件路径,所以我们需要在项目中添加此路径,并在路径中添加如下文件
只需要在lang_en.properties 和lang_zh.properties中添加内容即可,内容格式如: Signin=登录 这种形式。
步骤完毕,我们直接调用 alert(simpleLauage.lang("Signin")); 是不是会提示配置错误?没错,我们需要在IIS中添加
.properties的MIME映射(application/octet-stream即可)。
常用的 HttpContent-Type映射可参考:http://tool.oschina.net/commons
这些都是对日常常用功能的一个总结,对于碰到这种业务无法处理的同学来说可能会有一些帮助,加油!为自己!