国际化原因

为了更加方便切换版本,让代码应该一次完成,多国使用,除了使用英语外,还要可以进行单独语言包的一个添加,文章就是这样的一个例子.

公司接到一个国外的项目,需要法文版本的,但是公司通晓法文的基本没有,于是商量降低要求之后开始国际化采用英文展示就行,于是任务就开始了.

目录

[TOC]

需求分析

项目的代码全部国际化任务量不小,公司基本没有用什么框架,基本采用的是js,html实现数据的展示,没有采用框架,只是有一些简单的逻辑分层,加大了不少国际化的难度.

但是针对java部分的代码,虽说稍微熟悉一些,但是国际化就暂时不需要我负责了,虽说我也只是之前弄过纯java的一些简单的国际化.

我的话暂时只是需要复杂光伏预测系统的web展示界面.其他的国际化暂时不需要修改,毕竟最为直接的只是能够看到的部分.计算过程中的一些说明日志什么的,可以后面再继续更改.

准备工作

开始准手国际化之前,先查阅了一下相关的文章,在前端方面需要修改的部分方法有如下几种:

由于考虑到公司原本的代码没有使用什么现在的最新框架,只是采用的最后一个,也就是jquery.i18n.properties这样子的一个解决方案了,目前方案就暂时这么定来下了,然后下载对应的jQuery文件和jQuery.i18n文件了.

由于文件更新比较快,这里就不单独提供下载连接了,自行搜索,测试版本的时候使用的是3.3的版本.对应的浏览器需要ie10及以上,谷歌基本没问题.

开始着手

引入jQuery相关js

首先是引入jQuery的文件.有两种方案了,一种是在每个页面一个个添加,这样子效率不高,但是稳定,还有一种是采用js直接内部引入外部的js的方法,两种方法的代码如下:

<!-- 页面添加连接 -->
<script type="text/javascript" language="JavaScript"
  src="../jsCommon/jquery-3.3.1.js"></script>
<script type="text/javascript" language="JavaScript"
  src="../jsCommon/jquery.i18n.properties.min.js"></script> 
<!--           <script type="text/javascript" language="JavaScript"
  src="../jsCommon/jquery.i18n.properties.js"></script> -->

这里其实是指需要两个,多的一个min的包,只是为了后面加速加载之用,开发测试的话,一般用不压缩的js包,因为看起来有些难度.

//js内部添加外部js
document.write('<script type="text/javascript" language="JavaScript"  src="../jsCommon/jquery-3.3.1.js"></script>');
document.write('<!-- <script type="text/javascript" language="JavaScript"  src="../jsCommon/jquery.i18n.properties.min.js">	</script> -->');
document.write('<script type="text/javascript" language="JavaScript"  src="../jsCommon/jquery.i18n.properties.js"></script>');

这里需要注意一下的是,添加这个之后,才能使用这个引入的js,并且不能在这个js里面使用引入的一些方法,因为会找不到,你可以尝试一下.

采用第二种方法的话,需要找一个所有页面都存在的js文件才可以,可以根据自己的需要选择,没有这样的一个js的话,就差不多只能选择第一个方法,或者自己写这么一个js了.

初始化参数

引入之后js就需要初始化了,代码以及参数如下:

var currentLang = navigator.language;   //判断除IE外其他浏览器使用语言
if(!currentLang){//判断IE浏览器使用语言
   currentLang = navigator.browserLanguage;
}
console.info("Browser: "+ currentLang);
//currentLang = 'en-US';
//currentLang = 'zh_CN';


jQuery.i18n.properties({
		name: 'nari', //必须和配置文件一致
		path : '../jsCommon/', // 资源文件路径
		mode : 'map', // 用 Map 的方式使用资源文件中的值
//		language : 'en_US',
		language : currentLang, 
//		cache:false, 
//		encoding: 'UTF-8', 
		callback: function(){ 
			console.info( $.i18n.prop('info'));
			console.info(currentLang);
		}

这里的话分为两个部分了,前面部分只是为了自动识别浏览器的语言,做的一个判断,可以不要,然后下面的参数的话,没有注释的部分,一般就是都需要填写的了,除了那个language除外,那个也是可以省略.

国际化页面

上面的那个初始化的代码可以放在页面加载完成之前,也可以放在页面加载完成之后.加载的方法有两种,一种是统一加载,一种是按需加载.

统一加载

放在页面加载完成之后的话,这个部分的代码就需要放在js的最后面了,然后在callback方法里面,通过id号或者其他的标志进行国际化操作了. 网络上面多是这么一个案例,找到应该不难,这里就copy一份例子,没有经过实际代码验证:

jQuery.i18n.properties({
        name: 'common',
        path: '/Content/i18n/' + i18nLanguage + '/', //资源文件路径
        mode: 'map', //用Map的方式使用资源文件中的值
        language: i18nLanguage,
        callback: function () {//加载成功后设置显示内容
            console.log("i18n赋值中...");
            try {
                //初始化页面元素
                $('[data-i18n-placeholder]').each(function () {
                    $(this).attr('placeholder', $.i18n.prop($(this).data('i18n-placeholder')));
                });
                $('[data-i18n-text]').each(function () {
                    //如果text里面还有html需要过滤掉
                    var html = $(this).html();
                    var reg = /<(.*)>/;
                    if (reg.test(html)) {
                        var htmlValue = reg.exec(html)[0];
                        $(this).html(htmlValue + $.i18n.prop($(this).data('i18n-text')));
                    }
                    else {
                        $(this).text($.i18n.prop($(this).data('i18n-text')));
                    }
                });
                $('[data-i18n-value]').each(function () {
                    $(this).val($.i18n.prop($(this).data('i18n-value')));
                });
            }
            catch(ex){ }
            console.log("i18n写入完毕");
        }
    });

这里需要注意的是,这里需要每一个需要更改的位置都添加类似如下的标记:

<input class="typeahead" type="text" id="menu_search" data-i18n-placeholder = "searchPlaceholder"/>

<span data-i18n-text="setting"></span>

这里的话如果是开始开发或者只是短小一些的英文的话,还是可以考虑的,但是如果是完整的项目的话,任务栏就不小了.自行选择处理.下面介绍第二种方法.

按需加载

前面的那段初始化代码如果放在页面加载之前的话,就是放在引入jQuery之后,直接初始化,然后在其他位置,需要替换成国际化的位置,按照如下的格式替换就行.

//old
alert("hello");

//new
alert($.i18n.prop('hello'));

配置文件

能够知道国际化,上面的那个替换位置的用处自然不用多说,还是提一句吧 ,这样子看似复杂了,但是后面需要修改的话, 不需要改动这里的js,而是只需要更改对应的nari.properties(具体名字根据自己的配置来)就行了,还可以根据需要自动判断语言包.

对应的nari.properties文件中文文件包的名字是nari_zh.properties和nari_zh-CN.properties, 也可以多一个繁体字的nari_zh-TW.properties,英文的对应的是nari_en.properties, nari_en-US.properties,nari_en-UK.properties,其他国家和语言的就自己查询了,然后里面的内容大致如下格式:

#nari_zh.properties
#Chinese character need transfer to unicode 
hello = \u4F60\u597D

#nari_en.properties
hello = hello 

其他国际化内容

上面基本上可以完成大部分页面的处理了,但是还是有一些其他的需要注意一下.

标题的国际化

这里由于标题在最先加载,不管上面的那种方法,都是在这个标题之后,只能在加载之后,通过js的方法进行标题更改了.这里简单罗列一下几个有趣的方法,作用看名字就懂了.

document.getElementsByTagName("title")[0].innerText = '需要设置的值';

window.onfocus = function () {
 document.title = '恢复正常了...';
};
window.onblur = function () {
 document.title = '快回来~页面崩溃了';
};
 
$('title').html('')

需要修改的位置,只是需要把对应的字符串替换成$.i18n.prop('Title')就行了.

这里还有个问题是,如果你的浏览器加载比较慢的话,在浏览英文时候,可能会看到标题先是英文,之后才变成中文,这是应为标题加载是在js运行之前的缘故了,这个暂时没有找到好的解决办法, 一般浏览器的加载速度基本都发现不了这个现象,如果非要解决的话,就把原先的html里面的标题设置成空格把,这样就看不到中文了.

一些未启用的界面的国际化

这个部分,emmmmm,既然没启用,也没有直接的连接跳转过去,我也不启用吧.这次里面有两个不知道干嘛的页面FSDayGrid和OverhaulCapacity.

针对一些插件的处理

针对一些插件的处理的话,如果插件本身没有国际化的功能,就直接按照上面的那个按需国际化加载就行了,比方说根据自身需要引入的一些数据表格之类的插件.还有一些固定的菜单之类的等等, 当你需要同样的差不多的插件,但是有不完全相同时候,需要全部国际化是多么令人头疼的一件事情.

你能猜猜下面三个文件的作用吗?

//差不多的功能,就不能放一个文件里面处理吗?

// /PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/command/MyEWISUtil.js
/PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/Mcommand/MyEWISUtil.js
/PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/Mcommand/MyEWISUtil1.js//runalarm
/PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/command_cx/MyEWISUtil.js//StationRTList
//page部分的双引用已经更改为一个引用,两个部分完全一致,但是不能自动国际化,则选择性修改后者.

<!--<script type="text/javascript" src="../jsCommon/ext/build/locale/ext-lang-zh_CN.js"></script> -->   
//index StationRTLists PlanCapacity
       
 <script type="text/javascript" src="../jsCommon/ext/source/locale/ext-lang-zh_CN.js"></script>
//RunAlarm SolRadFigureF PowerFigureF ContrastPowerFigureF ContrastUShortRealtimeTheory ContrastLists

 <script type="text/javascript" src="../Datetime/ext-lang-zh_CN.js"></script>
/PVForecast(s)1.24/WebRoot/PVForecast/Datetime/ext-lang-zh_CN.js
//针对一些表格位置的中文,发现是createGrid这个函数加载的,主要是针对xml文档的一个解析分析了.
//这个如果是返回或得到的数据的话,就不是很好处理了,需要在发送请求时候特殊处理.
// 数据显示的空间,针对角标的显示,有四处不同的引用,修改需要针对不同的部分进行修改.文件名至少都是GeneralGrid.js类似的名字.
//时间选择控件针对google的兼容性不是很好,需要进行处理,
//同时,日期对应的格式需要转化为国际化的形式,注意文件的位置,和时间控件有三个

/PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/command/EditorGrid.js
/PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/command/GeneralGrid.js
/PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/command/GeneralGrid1.js
/PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/command/GeneralGrid2.js
/PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/command/GeneralGrid3.js
/PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/command/GeneralGrid4.js
/PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/command_cx/GeneralGrid.js
/PVForecast(s)1.24/WebRoot/PVForecast/jsCommon/command_cx/GeneralGrid1.js

如果一些插件本身是带有国际化的,一般通过一些方法引入就行了.下面的一个例子可以参考一下:

var userLanguage = getlanguageCookie("userLanguage");
//如果cookie里面没有,则使用默认值
if (!userLanguage) {
	userLanguage = 'zh-CN';
}
if (userLanguage == 'zh-CN') {
	var script = $('<script><\/script>');
	script.attr('src', '/Content/bootstrap-table/locale/bootstrap-table-zh-CN.js');
	$('body').append(script);
}
else if (userLanguage == 'en') {
	var script = $('<script><\/script>');
	script.attr('src', '/Content/bootstrap-table/locale/bootstrap-table-en-US.js');
	$('body').append(script);
}

这个是自动判断中英文然后加载了,代码没有验证成功,getlanguageCookie函数加载失败了,可能要自己写.不过有一个自动化一点的可以参考,代码已经经过验证:

console.info(currentLang);
currentLang=currentLang.replace('-','_');
var scr= '<script type="text/javascript" src="../jsCommon/ext/source/locale/ext-lang-'+currentLang+'.js"></script>';
document.write(scr);

代码是可以正常运行了,但是需要注意一下文件的名字和路径的问题.

针对返回数据的处理

有些位置通过定时查询数据库是否有异常数据之后直接alert获取到的数据,你就没办法了,如果是java代码还好,还可以采用java代码的国际化,这里后面有机会再提了.方法和这个类似.

如果你硬是要通过解析判断获取到数据的中文,然后写一个替换的过程的话,我在精神上面支持你,也欢迎你把写好的代码评论一番.

针对xml文件的配置国际化

针对一些通过xml动态配置页面的部分,解决方案就太多了,一个是在所有的节点旁边配置一个英文节点,但是后面维护,估计不好添加其他的语言.

那就再重写一套英文的?暂时就这么处理吧,复制一份为英文,中文的还是保留,可以在需要中文时候加载中文的配置文件,这里暂时就先自动化处理了,等后面有时间的话再添加.

复制之后,基本把所有的中文,改成英文,就完成了.

配置文件的双语处理

在处理国际化时候,为了方便对比中英文,我是专门坐了一个中英文的文件的,但是分开时候就很伤脑经了,因为有六百多行的中文英文都是如下的一个格式:

index = index 主页
help = help 帮助

想要分开,有些难度,索性好在有正则表达式这个东西,大部分的数据是可以处理了,少部分带标点的就自行修改了.

寿险,复制一份之后(你不想失误操作之后就全没了吧?),通过浏览器转换成unicode的形式,然后代码成这个样子了:

index = index \u4E3B\u9875
help = help \u5E2E\u52A9

然后在能够使用正则表达式的位置查找替换\=[\s\w+]+\\这个就行了,这个是将=之后和\之前的英文选中.全部处理完差不多就是中文的了,可能要注意一些带有符号的中英文了.

再复制一份之后通过这个\\[\w\S+]+$\r,选中所有的中文,替换为空格就解决了,这个就是英文的配置文档了.

网上了解到有直接通过unicode查找汉字的,但是通过notepad+没有测试成功,就没有实际使用了,而且还要主意一下中文之间的符号问题,虽说是少数了.有更好的解决方案欢迎点评指正.

驼峰式样转换为正常式样

本来,现在的配置样式是这个样子的

hello = NiceToMeetYou
good = ItIsGood
nice = ItVeryNice

这个样子的,应为是菜单选项,所以就直接连在一块了,但是现在要分开的话,全部行数太多,只能寻求高效方法,最后通过正则替换实现了,当然,有大佬造了更为方便的轮子的话,欢迎分享.

我的话,使用的查找目标是\=([\s\w]+[a-z])([A-Z]),这里的括号就是两个变量了,替换时候直接使用=\1 \2就行了.

不过需要注意的是,这里只是每一行的匹配,每一行最多有多少个连在一块的单词,就需要执行多少次,下面的例子的话,需要3次全部替换才能完成了.

最后的结果如下:

hello = Nice To Meet You
good = It Is Good
nice = It Very Nice

有其他有用的正则也欢迎点评.

其他异常

PowerFigureF.html等页面的数据类型字符存在重叠,分析之后发现是因为长度设置的问题,直接延长宽度可以解决.改正后代码如下:

//创建数据类型面板
function createDataTypePanel() {
    for (var i = 0; i < 3; i++) {
        var obj = new Ext.form.Checkbox({
            name: 'type',
            boxLabel: LineType[i],
            //width: 90,//old
            width: 120,
            height: 20
        });
        obj.checked = true;
        contrastLines.push(obj);
    }
    MyEWISUtilObj.CreateTypePanel($.i18n.prop('all.DataType'), "west", onRadioRegionButton, contrastLines);
}

SolRadFigureF.html等页面的图表类型数据显示本应该一行,实际分为两行,分析之后是上面同样问题,解决代码如下:

var QueryTypePanel = new Ext.Panel({
        title: $.i18n.prop('all.ChartConfig'),
        labelWidth: 200, // label settings here cascade unless
        id: 'QueryTypePanel',
        frame: true,
        bodyStyle: 'padding:5px 5px 0',
        width: 200,
        defaults: {
            //width: 70
            width: 90
        },
        buttonAlign: 'center',
        layout: 'table',
        layoutConfig: {
            columns: 2
        },
        viewConfig: {
            forceFit: true
        },
        items: [LinePlotRadio, BarPlotRadio, LabelCheck],
        renderTo: 'west'
    });

PowerFigureF.html等页面的数据图标显示只是部分数据选项,没有全部选择,分析之后发现被人添加了一个异常的高度,最后注释即可.

 var QueryTypePanel = new Ext.Panel({
        title: $.i18n.prop('all.ChartConfig'),
        labelWidth: 60, // label settings here cascade unless
        // overridden
        id: 'QueryTypePanel',
        frame: true,
        bodyStyle: 'padding:5px 5px 0',
        width: 200,
        defaults: {
            width: 100
        },
        buttonAlign: 'center',
        layout: 'table',
        layoutConfig: {
            columns: 2
        },
        viewConfig: {
            forceFit: true
        },
        items: [LinePlotRadio, BarPlotRadio, LabelCheck],
        renderTo: 'west',
        //height: 20//old
    });

说明

欢迎评论,欢迎指正,转载也请注明出处.

参考博客

前端系列_jquery.i18n.properties前端国际化解决方案“填坑日记”

js 前端实现国际化配置

CommanderXL/di18n_translate: di18n_translate

Java中的国际化

JS判断浏览器语言及终端类型

js笔记浏览器及版本判断

js 获取浏览器版本信

BrowserInfo

JavaScript进阶(二)在一个JS文件中引用另一个JS文件

版本记录

20180606 起草任务

20180627 完成翻译

20180628 完成文章

20190528 更新文章格式