本来觉得没有太大必要分享这个功能的,因为有些涉及到业务了, 不太好拆了,而且有弊端,只适合一部分业务使用。先不说那么多, 有用得着的人自然就觉得有用了!
JF框架里自带的 I18nInterceptor 已经很方便了, 但是需要写properties文件,,,而且需要自己翻译,如果有翻译错的地方,被客户指出来了, 还不能立刻修改,,,或者客户给到了一篇自己的翻译内容(有英语日语等其他好几国的语言。。。),我们填进去也是需要时间的,现在有IDEA了还能自动打开好几个文件协同但是填写也是蛋疼特别好几个国家的一起填。。。在以前eclipse上还得自己比对key是不是填对着。。。
那为了解决上面这些梗,我们想到的是把文件内容迁移到数据库中, 用表存起来就好了嘛! 然后了发现以前都是用一个英文单词或者一个字符串作为了国际化内容的key,这样下来导致维护的时候,看着页面不知道写的是什么东西了(英语差)。。。然后我们就用了中文作为key。。。发现非常的好维护啊!再就是自动翻译了,这个很好做,调用一下第三方的翻译接口就可以了,23333 上石马~
I18nCacheInterceptor
package com.momathink.common.interceptor; import com.jfinal.aop.Interceptor; import com.jfinal.aop.Invocation; import com.jfinal.core.Const; import com.jfinal.core.Controller; import com.jfinal.kit.StrKit; import com.momathink.system.i18n.I18n; import com.momathink.system.i18n.I18nCache; import com.momathink.system.login.LoginService; import com.momathink.system.model.SysAccount; /** * 国际化_数据库存储方式<BR> * 页面调用 时: #(i18n.get('xxxx'))<BR> * 控制器调用 时: getI18n().get('xxxx') * * @author dufuzhong */ public class I18nCacheInterceptor implements Interceptor { public static final String LOCALE_PARA_NAME = "_locale_i18n"; public static final String I18N = "i18n"; @Override public void intercept(Invocation inv) { Controller c = inv.getController(); String locale = c.getPara(LOCALE_PARA_NAME); // 检查 是否携带 国际化 语言环境 if (StrKit.notBlank(locale)) { c.setCookie(LOCALE_PARA_NAME, locale, Const.DEFAULT_I18N_MAX_AGE_OF_COOKIE); } else { // 如果没有携带 语言环境, 从Cookie中取语言环境 locale = c.getCookie(LOCALE_PARA_NAME); if (StrKit.isBlank(locale)) { // 如果没有携带 语言环境, 从用户中取语言环境 SysAccount loginAccount = c .getAttr(LoginService.loginAccountCacheName); if (loginAccount != null && StrKit.notBlank(loginAccount.getLocale())) { locale = loginAccount.getLocale(); } // 如果没有 语言环境, 默认语言环境是 > 中文 if (StrKit.isBlank(locale)) { locale = I18n.ZH; } } } // 后续业务 可取到这个 语言容器 c.setAttr(I18N, I18nCache.me.getI18n(locale)); inv.invoke(); } }
上面使用的是i18n为命名,保留JF框架的_res取值 https://www.jfinal.com/doc/11-2
I18n
package com.momathink.system.i18n; import java.io.Serializable; /** * 语言容器<BR> * 装载着 某一种语言环境 的所有中文翻译语 * * @author dufuzhong */ public class I18n implements Serializable { private static final long serialVersionUID = 1L; public static final String ZH = "zh"; private static final String CACHE_NAME = "I18n"; private String locale = null; public I18n(String locale) { this.locale = locale; } /** * 获取中文翻译语内容 */ public String get(String k) { // 中文直接返回中文 if (ZH.equals(getLocale())) { return k; } String ck = getLocale().concat(k); // 关联着 某一种语言 对象集合 String v = CacheKit.get(CACHE_NAME, ck); if (v == null) { v = getV(getLocale(), k); CacheKit.put(CACHE_NAME, ck, v); } return v != null ? v : "待翻译:".concat(k); } public String getLocale() { return locale; } public boolean isLocale(String locale) { return this.locale.equals(locale); } /** * 数据库 查询 */ public String getV(String lang, String k) { return I18nServer.me.queryByZh(lang, k); } }
I18nCache
package com.momathink.system.i18n; /** * 国际化 缓存 管理 * * @author dufuzhong */ public class I18nCache { public static I18nCache me = new I18nCache(); private static final String CACHE_NAME = "I18nDb"; private I18nCache() { } /** * 根据 语言环境 取 语言容器 */ public I18n getI18n(String locale) { I18n i18n = CacheKit.get(CACHE_NAME, locale); if (i18n == null) { i18n = new I18n(locale); CacheKit.put(CACHE_NAME, locale, i18n); } return i18n; } }
这样就OK了, 通过
I18nCache.me.getI18n(locale)
进行调用的
然后就是数据库查询和保存了
I18nServer
package com.momathink.system.i18n; import com.momathink.system.model.SysI18n; /*** * 国际化服 * * @author dufuzhong * */ public class I18nServer { public static final I18nServer me = new I18nServer(); /** * dao私有化,禁止外面直接访问 */ private final SysI18n dao = new SysI18n().dao(); public String queryByZh(String lang, String k) { // 开发模式: 如果数据库中没有 就自动添加一条 SysI18n i18n = queryByZh(k); if (i18n == null) { I18nDbTask.add(k); return null; } if (i18n.getMy() != null) { return i18n.getMy(); } return i18n.get(lang); } /** * 根据中文查询 */ public SysI18n queryByZh(String zh) { return dao.findFirst("SELECT * FROM `sys_i18n` WHERE `zh` = ? LIMIT 1", zh); } }
再贴上数据库结构
-- ---------------------------- -- Table structure for sys_i18n -- ---------------------------- DROP TABLE IF EXISTS `sys_i18n`; CREATE TABLE `sys_i18n` ( `id` int(11) NOT NULL AUTO_INCREMENT, `zh` varchar(255) DEFAULT NULL COMMENT '中文', `my` varchar(255) DEFAULT NULL COMMENT '自定义', `en` text COMMENT '英语', `ru` text COMMENT '俄语', `cht` text COMMENT '繁体中文', `kor` text COMMENT '韩语', `jp` text COMMENT '日语', `fra` text COMMENT '法语', `vie` text COMMENT '越南语', `th` text COMMENT '泰语', `spa` text COMMENT '西班牙语', `ara` text COMMENT '阿拉伯语', `pt` text COMMENT '葡萄牙语', `de` text COMMENT '德语', `it` text COMMENT '意大利语', `el` text COMMENT '希腊语', `nl` text COMMENT '荷兰语', `pl` text COMMENT '波兰语', `bul` text COMMENT '保加利亚语', `est` text COMMENT '爱沙尼亚语', `dan` text COMMENT '丹麦语', `fin` text COMMENT '芬兰语', `cs` text COMMENT '捷克语', `rom` text COMMENT '罗马尼亚语', `slo` text COMMENT '斯洛文尼亚语', `swe` text COMMENT '瑞典语', `hu` text COMMENT '匈牙利语', `yue` text COMMENT '粤语', PRIMARY KEY (`id`), UNIQUE KEY `zh` (`zh`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='国际化词库'; SET FOREIGN_KEY_CHECKS = 1;
再贴上保存代码
package com.momathink.system.i18n; import com.jfinal.kit.LogKit; import com.momathink.common.kit.ExecKit; import com.momathink.system.model.SysI18n; /** * 国际化添加, 自动翻译 保存到 数据库 <br> * 增加新国际化的时候或者修改排序的时候 , 使用下面的main方法生成后, 粘贴控制台的内容就可以了 * * @author dufuzhong */ public class I18nDbTask { // 在平台申请的APP_ID 详见 // http://api.fanyi.baidu.com/api/trans/product/desktop?req=developer /** //杜福忠开发号 */ private static final String APP_ID = "手动马赛克,自己注册吧,我的就不放这里了"; /** //杜福忠开发号 */ private static final String SECURITY_KEY = "手动马赛克,自己注册吧,我的就不放这里了"; private static BaiDuFanYiApi api = new BaiDuFanYiApi(APP_ID, SECURITY_KEY); /** 国际化 语言简写K */ public static final String[] ARRAY = {"zh", "en", "ru", "cht", "kor", "jp", "fra", "vie", "th", "spa", "ara", "pt", "de", "it", "el", "nl", "pl", "bul", "est", "dan", "fin", "cs", "rom", "slo", "swe", "hu", "yue"}; /** 国际化 语言 名称 */ public static final String[] ARRAY_NAME = {"中文", "英语", "俄语", "繁体中文", "韩语", "日语", "法语", "越南语", "泰语", "西班牙语", "阿拉伯语", "葡萄牙语", "德语", "意大利语", "希腊语", "荷兰语", "波兰语", "保加利亚语", "爱沙尼亚语", "丹麦语", "芬兰语", "捷克语", "罗马尼亚语", "斯洛文尼亚语", "瑞典语", "匈牙利语", "粤语"}; /** 创建一个翻译任务 */ public static void add(final String query) { ExecKit.submit(new Runnable() { @Override public void run() { try { SysI18n i18n = new SysI18n()._setAttrs( api.getTransResult(query, ARRAY[0], ARRAY)); if (null == I18nServer.me.queryByZh(query)) { // 数据库有中文唯一检查 i18n.save(); } } catch (Exception e) { LogKit.error("国际化翻译异常: ".concat(query), e); } } }); } }
然后就是翻译接口的代码了,用的百度翻译接口,还行访问能快点,翻译准确率一般吧,毕竟免费。。。
package com.momathink.system.i18n; import java.net.URLDecoder; import java.util.HashMap; import java.util.Map; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.jfinal.kit.HashKit; import com.jfinal.kit.HttpKit; import com.jfinal.kit.LogKit; /*** * 百度翻译接口 * * @author dufuzhong * */ public class BaiDuFanYiApi { private static final String TRANS_API_HOST = "http://api.fanyi.baidu.com/api/trans/vip/translate"; private static String CHARSET = "UTF-8"; private String appid; private String securityKey; public BaiDuFanYiApi(String appid, String securityKey) { this.appid = appid; this.securityKey = securityKey; } public Map<String, Object> getTransResult(String query, String from, String... object) { Map<String, String> params = buildParams(query, from); Map<String, Object> ret = new HashMap<String, Object>(); for (String to : object) { params.put("to", to); String dst = getDst(HttpKit.get(TRANS_API_HOST, params)); ret.put(to, dst); } return ret; } private Map<String, String> buildParams(String query, String from) { Map<String, String> params = new HashMap<String, String>(); params.put("q", query); params.put("from", from); params.put("appid", appid); // 随机数 String salt = String.valueOf(System.currentTimeMillis()); params.put("salt", salt); // 签名 String src = appid + query + salt + securityKey; // 加密前的原文 params.put("sign", HashKit.md5(src)); return params; } private String getDst(String transResult) { JSONObject jsonObject = JSON.parseObject(transResult, JSONObject.class); JSONArray jsonArray = jsonObject.getJSONArray("trans_result"); String dst=""; if(null!=jsonArray) { JSONObject object = jsonArray.getJSONObject(0); dst = object.getString("dst"); } try { dst = URLDecoder.decode(dst, CHARSET); } catch (Exception e) { LogKit.error("百度翻译内容解码:" + dst + " 异常信息:", e); } return dst; } }
上面大家看到还有一个异步任务工具,这个是用来保证如果本地没有的时候,去百度API调用的时候不用等待页面,继续开发自己的,等下次刷新它就自动翻译好了
package com.momathink.common.kit; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 任务线程池 * * @author dufuzhong */ public class ExecKit { private static ExecutorService exec = null; /** * 添加一个任务 */ public static void submit(Runnable runnable) { getExec().submit(runnable); } /** 开发模式才会 进入这个 */ public static ExecutorService getExec() { if (exec == null) { synchronized (ExecKit.class) { if (exec == null) { exec = new ThreadPoolExecutor(5, 500, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r); } }, new ThreadPoolExecutor.AbortPolicy()); } } } return exec; } }
上面这个不是很严谨,但是能用,而且是开发时使用的,所以影响不大。
好了,下面放几张图,大致看下调用:
这个是在模板中用的,如果是前后分离的,也是可以渲染为js国际化文件的,再渲染过去就可以了。
好了,我们一直在用百度的翻译接口,还算稳定,很多项目都是这样干的
http://api.fanyi.baidu.com/api/trans/product/desktop
这个工具吧,特别适合像我们这样的业务和系统,在已知字段的情况下:
然后客户有需要纠正的词组,客户自己就在线修改了,不用我们双方沟通这个事情了,省开发时间~
OK, 看着代码挺多, 其实没两步,重在思路!
就是通过拦截器 获取语言环境, 然后根据语言key去取语言包的内容,再根据参数中文在语言包的缓存中找,缓存没有就去数据库中找,数据库没有就去百度找。。。 和人手动操作流程没啥两样~~~
好了,又水一篇~ 路过的记得点个赞哦~