前言:最近,新的平台還沒有開發完成,原來的老項目又提出了新的需求:系統國際化。如果是前后端完全分離的開發模式,要做國際化,真的太簡單了,有現成的解決方案,基於Node構建的時下熱門的任何一種技術選型都有成熟的方案,比如:
-
vue
+vue-i18n
-
angular
+angular-translate
-
react
+react-intl
但現在的情況是老的項目並沒有使用這類架構。說起國際化,博主幾年前就做過,在MVC里面實現國際化有通用的解決方案,主要就是通過資源文件的方式定義多語言。最初接到這個任務,並沒有太多顧慮,畢竟這種東西有很成熟的解決方案,實現起來難點不會很大。可當真正動起來手來去實現的時候發現一些問題,這里先介紹下我們老平台的架構:MVC+WebApi,MVC項目負責頁面渲染,webapi負責數據接口,是一種很傳統的架構方式。國際化主要在MVC端去做就好了,可是由於MVC項目里面使用了大量第三方bootstrap組件,幾乎95%的組件都是通過js去實現的,比如bootstrapTable,比如bootstrap-select,比如bootstrap-fileinput。如果按照傳統的方式,僅僅在MVC里面去實現國際化,那么大量的js代碼里面的中文沒法統一處理,並且很多第三方組件有自己的本地化local文件,和后端的國際化很難統一處理;可能有人又說,那就前后端分開國際化唄,這種方案博主真的想過,但是想到要維護兩套資源文件,果斷放棄。最后還是決定直接維護一套,做前端國際化好了。於是在網上搜索基於jquery的國際化,千篇一律,幾乎都說的是jquery + jquery.i18n.properties這種方案,既然大家都這么選型,那博主也按照這種思路去做好了。
在實現的過程中,有很多值得注意和分享的東西,在此寫一個填坑筆記,希望對大家有幫助!接下來,博主就一步一步帶領大家解決這個過程中遇到的一些坑,如果有這個需求的童鞋可以關注下,可能這些問題你也會遇到。
本文原創地址:http://www.cnblogs.com/landeanfen/p/7581609.html
一、jquery.i18n.properties通用解決方案
關於jquery.i18n.properties的使用,網上資料很多,比較完整的使用可以參考 這篇 ,有比較詳細的使用說明。這里博主簡單概述下過程。
1、需要引用的js文件
先在你的項目文件里面添加如下目錄結構
首先頁面引用的js文件如下
<script src="~/Scripts/jquery-1.9.1.min.js"></script> <script src="~/Content/i18n/jquery.i18n.properties.js"></script> <script src="~/Content/i18n/language.extensions.js"></script>
其中jquery-1.9.1.min.js和jquery.i18n.properties.js文件是開源組件,直接去網上找到即可
第三個文件language.extensions.js是我們自定義的js文件,如果你將國際化的代碼直接寫在html頁面里面,這個文件就是不用的。
2、html文件和國際化組件的初始化
這里直接引用上面示例文章里面的代碼,首先需要一個切換中英文的標簽,比如是一個select
<select id="language"> <option value="zh-CN">中文簡體</option> <option value="en">English</option> </select>
然后是一些查看效果的html標簽
<div> <input type="search" class="i18n-input" selectname="searchPlaceholder" selectattr="placeholder"> </div>
最后就是我們需要封裝的language.extensions.js文件的內容了,里面做了以下幾件事:
- 初始化頁面的時候去當前域的cookie里面取當前瀏覽器保存語言的cookie,根據取到的當前語言版本去初始化國際化組件,然后初始化select組件的選中值
- 注冊select組件的change事件,根據當前選中的語言,更新cookie里面的語言信息,然后刷新頁面。
這個文件的內容這里就不展示了,可以參考上面的使用示例文章
3、資源文件准備
根據上面的目錄可以看出,我們打算將不同的語言的資源文件放到不同語言的文件夾里面,這里暫時不分文件,所有的語言資源放到一個文件common.properties里面,比如內容如下:
en/common.properties
searchPlaceholder=Please input serach information signOut=Login Out station=Station partno=Part No description=Description query=Query pleaseSelect=Please Select add=Add edit=Edit delete=Delete
zh-CN/common.properties
searchPlaceholder=請輸入關鍵字 signOut=退出 station=站點 partno=零件號 description=描述 query=查詢 pleaseSelect=請選擇 add=新增 edit=編輯 delete=刪除
貌似大功告成!當你down源碼直接在google瀏覽器里面運行的時候你會發現一個跨域的問題。
要求你在一種webServer里面去訪問.properties文件,這個問題你只需要使用任何一種webserver運行即可,比如IIS、Apache、Node的web服務器等。博主的代碼是在Visual Studio里面跑的,所以是基於IIS的,當你把代碼搬到VS里面跑的時候,第一個問題來了。
二、坑一:配置IIS對.properties文件的支持
如果本文僅僅是上面的內容,是沒啥意義的。接下來才是本文要介紹的重點!
將上述代碼直接搬到VS里面,運行的時候你會發現第一個問題:
為什么這里會請求三個properties文件?因為jquery.i18n.properties.js組件支持三種類型的命名方式,這點很多文章都有介紹,組件代碼運行的時候會去請求三種規則的properties文件,只要找到任何一種規則的文件都可以讀取到里面的內容。按照博主上文給出的文件目錄
根據這個目錄,那我們通過 http://localhost:12770/Content/i18n/zh-CN/common.properties 這個url應該能訪問到zh-CN/common.properties這個文件,可實際情況確實這樣:
對於這種IIS報錯404的問題,C#程序員肯定是不陌生的,無非就兩個原因:
- url不正確,這個目錄下面確實沒有找到這個資源文件
- 文件的類型iis默認不支持,直接拒絕請求
排除了第一個原因,那么只可能是第二個原因引起的了。那么我們如何處理呢,在網上搜索半天資料,找到一種解決方案:
這樣確實能繞過IIS的文件名驗證,但是改源碼不是一個好的解決方案,博主有一千個理由來說明改源碼的弊端。這種方式肯定不是一個最好的解決方案,於是博主決定另辟蹊徑。
還記得當初博主學習less的時候,iis默認也是不支持.less文件的,於是我們在web.config里面加了如下一些配置:
這絕對屬於同類型的問題,加這個配置是顯式告訴IIS,我們系統里面某種后綴的文件需要哪種Processer(處理器或處理組件)去處理,受此啟發,那么我們這里的.properties文件的404問題是不是也可以通過此種方式解決?如果需要通過這種思路去解決,首要問題是需要找到.properties文件的mimeType,博主思考,既然是在js里面調用這個.properties文件,那么我們是否可以使用JavaScript的處理機制來處理.properties文件呢,考慮到上面那種將所有.properties替換成.js的處理方式,似乎.properties和.js有很多相似之處,於是我們加上如下一條配置:
得到結果:
試驗成功,就是這么簡單。當然如果發布到IIS,可能需要在IIS的MIME類型里面添加.properties這種類型的映射。
好了,這個問題就這么愉快的解決了。如果你的WebServer不是基於IIS的,可能沒有這個問題,但我想思路或許相通,供參考!
三、坑二:使用html的data屬性初始化國際化內容
一般情況下,我們標簽里面的內容如果要做國際化,需要使用 $('#id').text($.i18n.prop('proName')); 來給標簽賦值,現在問題來了,我們開發一個界面,有很多地方都需要去做國際化,我們總不能這樣每一個頁面每一個標簽通過這種方式去賦值吧,這樣工作量不是一點大,於是乎博主想,有沒有一種比較好的通用的解決方案去給這些需要做國際化的標簽統一賦值呢。html的data屬性似乎是一個不錯的選擇!它具有可讀性強、可維護性強、兼容jquery的data()方法等優點。比如我們修改國際化組件的方法如下:
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寫入完畢"); } });
通過data屬性獲取標簽,然后對每個標簽通過對應的data-i18n-屬性進行國際化賦值即可,這里暫時定義了三種類型data-i18n-placeholder、data-i18n-text、data-i18n-value的屬性,如果有其他需求,可以增加其他類型。
然后看下我們html頁面的使用
<input class="typeahead" type="text" id="menu_search" data-i18n-placeholder = "searchPlaceholder"/>
<span data-i18n-text="setting"></span>
這樣不用寫一句標簽的賦值代碼,即可對標簽進行國際化。
四、坑三:第三方組件的國際化(一)
對於第三方組件,我們自定義的代碼里面的中文要做國際化,我只需要使用$.i18n.prop('key')即可,比如bootstrapTable:
{ field: 'AuditEventType', title: '業務類型', width: '12%' }
直接使用
{ field: 'AuditEventType', title: $.i18n.prop('bllType'), width: '12%' }
即可。這個解決思路很簡單,沒啥好說的,可是有一些第三方組件,自己有國際化的功能,初始化的時候需要指定國際化的類型,形如:
$(".date").datetimepicker({ format: 'YYYY-MM-DD',//日期格式化,只顯示日期 locale: 'zh-CN' //中文化 });
目前想到的解決方案,就是根據cookie里面存儲的當前語言來顯式賦值
//獲取cookie里面的語言 var userLanguage = getlanguageCookie("userLanguage"); $(".date").datetimepicker({ format: 'YYYY-MM-DD',//日期格式化,只顯示日期 locale: userLanguage =='zh-CN'?'zh-CN':'en-US' //國際化 });
如果是多種語言,這里可以在前端自己去處理。
五、坑四:第三方組件的國際化(二)
上面介紹了第三方組件初始化時候指定國際化,除此之外,還有另外一種情況,就是很多組件有自己的本地化(關於國際化和本地化的區別,請自行谷歌)文件,它的國際化是通過引用不同的本地化js文件來實現的,比如博主常用的bootstrapTable組件,它的目錄:
還有其他組件也是這樣,比如:
那么針對這種情況,我們的國際化該如何實現了,這里博主提供的思路是動態引用js,通過當前cookie里面保存的語言的類型來引用對應語言的js文件,比如針對bootstrapTable,我們這樣去動態引用js
//組件根據國際化動態引入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); }
如果要想代碼寫得更加優雅,可以自己去實現前端的抽象工廠,這里只是提供一種實現思路。
六、總結
排除了以上幾步的困難,我們的國際化在項目里面基本就能正常運行起來了,至於WebApi里面返回消息的中文,如果你也想做國際化,我們可以通過將返回消息封裝,統一返回前端處理。本篇文章的“填坑方式”或許不是最好的,但至少給大家提供了一種實現思路,如果大家有更好的實現方式,歡迎留言交流。如果你覺得本文能夠幫助你,可以右邊隨意 打賞 博主。
本文原創出處:http://www.cnblogs.com/landeanfen/
歡迎各位轉載,但是未經作者本人同意,轉載文章之后必須在文章頁面明顯位置給出作者和原文連接,否則保留追究法律責任的權利