前言 然后 算了, 今天一天好像心情都太平静了, 似乎是没有什么起伏啊, 呵呵呵 倒也正常???, 写下关于 i18n 吧??? 老项目, servlet + jsp, 需求是国际化处理 思路 先整理一下思路, 项目是ser
前言
然后 算了, 今天一天好像心情都太平静了, 似乎是没有什么起伏啊, 呵呵呵 倒也正常???, 写下关于 i18n 吧???
老项目, servlet + jsp, 需求是国际化处理
思路
先整理一下思路, 项目是servlet + jsp, 然后网络上面搜索了一下, 主要是使用 fmt 标签, 把页面上写死的字符串打标签, 以及js中的字符串也需要替换为标签核心思路, 大概是如上, 然后 具体的实现的细节, 在这个过程中又有一些变化
首先页面上的字符串使用 fmt 标签处理, 这一点没有太大的疑问, 然后 剩下的是关于js脚本中的字符串的标签化这里面有一些值得思考的地方
1. 最开始的时候, 我的想法是 dict = {"ch" : {}, "en" : {} }, 放在一个js里面, 然后 页面上根据语言选择对应的字典
这样的实现方式, 这个字典会拉下 很多不需要的数据, 因为我们只需要其中一种语言, 使用率为 1/N, 其他的似乎倒是没什么大问题
2. 然后 中午到同事这边, 他有另外的一个想法, 通过Filter 拦截, 拦截所有的js, 将js中的占位符 根据当前语言替换为字典中的数据
然后 对于这种思路, 我首先考虑到的是对于浏览器的静态资源缓存是否会存在影响, 是否这样处理了之后客户端每次都会来拉对应的js, 有影响
然后 还有一个问题, 就是, 到时候我们的静态资源服务器 可能和动态资源服务器分离开来[我们系统有这样的需求], 这么做就必须将静态资源放在动态资源服务器上面了, 增加了耦合
3. 然后 后面的一种思路是 思路一的更细粒度版本, 语言字典放在几个js里面, lang_ch.js, lang_en.js, 里面存放各自的字典[字典的名字要约定要, js里面使用]
这种思路 相较于思路一更好一点, 之拉去了自己需要的东西
4. 然后 还有更细粒度一点的思路, 根据各个模块[抽象, 可以引申为各个模块, 甚至单个文件], 来提供对应的三个版本的 语言包
但是这样会存在一些问题, 各个jsp导入的js 可能不一样, 然后以后 如果切换包的时候可能需要重新生成字典
然后 还有就是, 虽然减小了每次传输的大小, 但是增加了冗余, 客户端需要缓存的语言包也多了不少
最后综合评估一下, 还是决定 使用三个语言包的思路
然后具体的替换的细节, 这里就不说了, 替换的过程是有规则的, 因此 可以使用脚本完成
几个脚本, 1. 获取所有的中文, 2. 更新对应的jsp, 3. 更新对应的js, 4. 生成字典的key, 5. 生成对应的 lang_*.properties, lang_*.js
这里着重介绍一下前1, 2, 3 个步骤, 当然 仅供参考
script1. 遍历项目下面的所有需要处理的文件[jsp + js], 二级根据行遍历[当然可以使用相关的xml包来处理, 我这里为了简单就直接一行为单位], 搜索中文字符串script2. 遍历项目下面的所有需要处理的文件[jsp], 二级根据行遍历, 主要是增加 jsp i18n 先关的标签, 以及字符串标签化, 我这里拆分成了两个步骤
script3. 遍历项目下面的所有需要处理的文件[js], 二级根据行遍历, 字符串的标签化["js你好js" -> "js" + i18n._js_nh + "js"], 和上面中文类似, 只是需要找一下前面的外围引号
script4. 我这里就简单的根据对应的中文的简拼, 最多取10位, 如果存在重复, 添加后缀 "_$counter"
script5. 差不多就是读取对应的字典, 然后 输出一下, 复制到对应的目标文件中就好
script1 核心代码如下 :
@Override public void consume(String line) { int len = line.length(); for (int i = 0; i < len; i++) { // 过滤注释行 if (line.startsWith("//", i)) { return; } if (isChinese(line.charAt(i))) { int j = i; for (; j < len; j++) { if ((!isChinese(line.charAt(j))) && (!isBlank(line.charAt(j)))) { break; // int idxOfSpecialTag = idxOfSpecialTag(line, j); // if(idxOfSpecialTag < 0) { // break; // } // // j = idxOfSpecialTag; // continue ; } } // j 到达len, 或者 line[j] 不是中文 String chineseStr = line.substring(i, j); if (result.add(chineseStr)) { chineseCounter.incrementAndGet(); } cnt.incrementAndGet(); if (showChineseInFile) { info(" 搜索到中文字符串 : [" + chineseStr + "]"); } i = j; } } } /** * 判断给定的字符是否是中文 * * @param ch ch * @return boolean * @author Jerry.X.He * @date 2018/1/10 12:35 */ private boolean isChinese(char ch) { return ch >= '\u4e00' && ch <= '\u9FA5'; } private boolean isBlank(char ch) { boolean rangeReturn = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '\uFF00' && ch <= '\uFFEF') || (ch >= '\u3000' && ch <= '\u303F') || (ch >= '\uFE10' && ch <= '\uFE1F') || (ch >= '\uFE30' && ch <= '\uFE4F'); if (rangeReturn) { return rangeReturn; } // String specialStr = " \t\b\f\r\n.。,《》%〔〕"; String specialStr = " \t\b\f\r\n.。,《》%〔〕&;-_=@"; return specialStr.contains(String.valueOf(ch)); }
script2 核心代码如下 :
/** * 添加 fmt:bundle, 需要的标签信息 * * @param file file * @return void * @author Jerry.X.He * @date 2018/1/11 15:09 */ private void appendFmtBundle(File file) throws Exception { StringBuilder sb = new StringBuilder(); String fileContent = Tools.getContent(file); sb.append("<!-- i18n 相关 -->\n" + "<%@ taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\"%>\n" + "<%@ taglib prefix=\"fn\" uri=\"http://java.sun.com/jsp/jstl/functions\"%>\n" + "<%@ taglib prefix=\"fmt\" uri=\"http://java.sun.com/jsp/jstl/fmt\"%>\n" + "\n" + "<fmt:setLocale value=\"${param.locale}\" />\n" + "<fmt:bundle basename=\"pageDict\">\n\n\n"); sb.append(fileContent); sb.append("\n\n<!-- i18n 相关 -->\n" + "</fmt:bundle>"); // info(sb.toString()); Tools.save(sb.toString(), file, Tools.UTF_8); } /** * 中文 -> 替换为中文对应的 占位符 * 注意 : chinese2Key 需要根据value的长度排序, 长 -> 短 * * @param file file * @return void * @author Jerry.X.He * @date 2018/1/11 16:14 */ private void chinese2FmtKey(File file) throws Exception { final StringBuilder sb = new StringBuilder(); FileUtils.consumeContentList(file, new StringConsumer<Object>() { @Override public void consume(String s) { for (Map.Entry<String, String> entry : chinese2Key.entrySet()) { String chinese = entry.getKey(); if (s.contains(chinese)) { s = s.replaceAll(chinese, "<fmt:message key=\"" + entry.getValue() + "\" />"); continue ; } } Tools.appendCRLF(sb, s); } @Override public Object get() { return null; } }); // info(sb.toString()); Tools.save(sb.toString(), file, Tools.UTF_8); }
当然, 有些地方可能还需要手动处理下[比如 多行注释/**/], 以及限定文件需要限定好, 第三. 备份备份备份
完