摘要:最近的工作當中有個任務是做國際化。下文有對的源碼進行分析。因為英文的閱讀方向也是從左到右,因此語言展示的方向不予考慮。服務端數據翻譯前端樣式的調整中文轉英文后部分文案過長圖片第三方插件地圖等中文轉英文后肯定會遇到文案過長的情況。
最近的工作當中有個任務是做國際化。這篇文章也是做個簡單的總結。
部分網站的當前解決的方案不同語言對應不同的頁面。在本地開發的時候就分別打包輸出了不同語言版本的靜態及模板文件,通過頁面及資源的url進行區分,需要維護多份代碼。
在線翻譯
統一模板文件,前端根據相應的語言映射表去做文案的替換。
面對的問題
語言標識誰來做?
頁面完全由服務端直出(所有的數據均由服務端來處理)
服務端根據IP去下發語言標識字段(前端根據下發的表示字段切換語言環境)
前端去根據useragent.lang瀏覽器環境語言進行設定
當前項目中入口頁面由服務端來渲染,其他的頁面由前端來接管路由。在入口頁面由服務器下發lang字段去做語言標識,在頁面渲染出來前,前端來決定使用的語言包。語言包是在本地編譯的過程中就將語言包編譯進了代碼當中,沒有采用異步加載的方式。
前端靜態資源翻譯
單/復數
中文轉英文
語言展示的方向
前端靜態資源文案的翻譯使用vue-i18n這個插件來進行。插件提供了單復數,中文轉英文的方法。a下文有對vue-i18n的源碼進行分析。因為英文的閱讀方向也是從左到右,因此語言展示的方向不予考慮。但是在一些阿拉伯地區國家的語言是從右到左進行閱讀的。
服務端數據翻譯
前端樣式的調整
中文轉英文后部分文案過長
圖片
第三方插件(地圖,SDK等)
a.中文轉英文后肯定會遇到文案過長的情況。那么可能需要精簡翻譯,使文案保持在一定的可接受的長度范圍內。但是大部分的情況都是文案在保持原意的情況下無法再進行精簡。這時必須要前端來進行樣式上的調整,那么可能還需要設計的同學參與進來,對一些文案過多出現折行的情況再多帶帶做樣式的定義。在細調樣式這塊,主要還是通過不同的語言標識去控制不同標簽的class,來多帶帶定義樣式。
此外,還有部分圖片也是需要做調整,在C端中,大部分由產品方去輸出內容,那么圖片這塊的話,還需要設計同學多帶帶出圖。
在第三方插件中這個環節當中,因為使用了騰訊地圖插件,由于騰訊地圖并未推出國內地圖的英文版,所以整個頁面的地圖部分暫時無法做到國際化。由此聯想到,在你的應用當中使用的其他一些第三方插件或者SDK,在國際化的過程中需要去解決哪些問題。
跨地區xxxx
貨幣及支付方式
時間的格式
在一些支付場景下,貨幣符號,單位及價格的轉化等。不同國家地區在時間的格式顯示上有差異。
項目的長期維護
翻譯工作
map表的維護
當前翻譯的工作流程是拆頁面,每拆一個頁面,FE同學整理好可能會出現的中文文案,再交由翻譯的同學去完成翻譯的工作。負責不同頁面的同學維護著不同的map表,在當前的整體頁面架構中,不同功能模塊和頁面被拆分出去交由不同的同學去做,那么通過跳頁面的方式去暫時緩解map表的維護問題。如果哪一天頁面需要收斂,這也是一個需要去考慮的問題。如果從整個項目的一開始就考慮到國際化的問題并采取相關的措施都能減輕后期的工作量及維護成本。同時以后一旦map表內容過多,是否需要考慮需要將map表進行異步加載。
Vue-i18n的基本使用 // 入口main.js文件
import VueI18n from "vue-i18n"
Vue.use(VueI18n) // 通過插件的形式掛載
const i18n = new VueI18n({
locale: CONFIG.lang, // 語言標識
messages: {
"zh-CN": require("./common/lang/zh"), // 中文語言包
"en-US": require("./common/lang/en") // 英文語言包
}
})
const app = new Vue({
i18n,
...App
}).$mout("#root")
// 單vue文件
{{$t("你好")}}
Vue-i18n是以插件的形式配合Vue進行工作的。通過全局的mixin的方式將插件提供的方法掛載到Vue的實例上。
具體的源碼分析其中install.jsVue的掛載函數,主要是為了將mixin.js里面的提供的方法掛載到Vue實例當中:
import { warn } from "./util" import mixin from "./mixin" import Asset from "./asset" export let Vue // 注入root Vue export function install (_Vue) { Vue = _Vue const version = (Vue.version && Number(Vue.version.split(".")[0])) || -1 if (process.env.NODE_ENV !== "production" && install.installed) { warn("already installed.") return } install.installed = true if (process.env.NODE_ENV !== "production" && version < 2) { warn(`vue-i18n (${install.version}) need to use Vue 2.0 or later (Vue: ${Vue.version}).`) return } // 通過mixin的方式,將插件提供的methods,鉤子函數等注入到全局,之后每次創建的vue實例都用擁有這些methods或者鉤子函數 Vue.mixin(mixin) Asset(Vue) }
接下來就看下在Vue上混合了哪些methods或者鉤子函數. 在mixin.js文件中:
/* @flow */ // VueI18n構造函數 import VueI18n from "./index" import { isPlainObject, warn } from "./util" // $i18n 是每創建一個Vue實例都會產生的實例對象 // 調用以下方法前都會判斷實例上是否掛載了$i18n這個屬性 // 最后實際調用的方法是插件內部定義的方法 export default { // 這里混合了computed計算屬性, 注意這里計算屬性返回的都是函數,這樣就可以在vue模板里面使用{{ $t("hello") }}, 或者其他方法當中使用 this.$t("hello")。這種函數接收參數的方式 computed: { // 翻譯函數, 調用的是VueI18n實例上提供的方法 $t () { if (!this.$i18n) { throw Error(`Failed in $t due to not find VueI18n instance`) } // add dependency tracking !! const locale: string = this.$i18n.locale // 語言配置 const messages: Messages = this.$i18n.messages // 語言包 // 返回一個函數. 接受一個key值. 即在map文件中定義的key值, 在模板中進行使用 {{ $t("你好") }} // ...args是傳入的參數, 例如在模板中定義的一些替換符, 具體的支持的形式可翻閱文檔https://kazupon.github.io/vue-i18n/formatting.html return (key: string, ...args: any): string => { return this.$i18n._t(key, locale, messages, this, ...args) } }, // tc方法可以多帶帶定義組件內部語言設置選項, 如果沒有定義組件內部語言,則還是使用global的配置 $tc () { if (!this.$i18n) { throw Error(`Failed in $tc due to not find VueI18n instance`) } // add dependency tracking !! const locale: string = this.$i18n.locale const messages: Messages = this.$i18n.messages return (key: string, choice?: number, ...args: any): string => { return this.$i18n._tc(key, locale, messages, this, choice, ...args) } }, // te方法 $te () { if (!this.$i18n) { throw Error(`Failed in $te due to not find VueI18n instance`) } // add dependency tracking !! const locale: string = this.$i18n.locale const messages: Messages = this.$i18n.messages return (key: string, ...args: any): boolean => { return this.$i18n._te(key, locale, messages, ...args) } } }, // 鉤子函數 // 被渲染前,在vue實例上添加$i18n屬性 // 在根組件初始化的過程中: /** * new Vue({ * i18n // 這里是提供了自定義的屬性 那么實例當中可以通過this.$option.i18n去訪問這個屬性 * // xxxx * }) */ beforeCreate () { const options: any = this.$options // 如果有i18n這個屬性. 根實例化的時候傳入了這個參數 if (options.i18n) { if (options.i18n instanceof VueI18n) { // 如果是VueI18n的實例,那么掛載在Vue實例的$i18n屬性上 this.$i18n = options.i18n // 如果是個object } else if (isPlainObject(options.i18n)) { // 如果是一個pobj // component local i18n // 訪問root vue實例。 if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) { options.i18n.root = this.$root.$i18n } this.$i18n = new VueI18n(options.i18n) // 創建屬于component的local i18n if (options.i18n.sync) { this._localeWatcher = this.$i18n.watchLocale() } } else { if (process.env.NODE_ENV !== "production") { warn(`Cannot be interpreted "i18n" option.`) } } } else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) { // root i18n // 如果子Vue實例沒有傳入$i18n方法,且root掛載了$i18n,那么子實例也會使用root i18n this.$i18n = this.$root.$i18n } }, // 實例被銷毀的回調函數 destroyed () { if (this._localeWatcher) { this.$i18n.unwatchLocale() delete this._localeWatcher } // 組件銷毀后,同時也銷毀實例上的$i18n方法 this.$i18n = null } }
這里注意下這幾個方法的區別:
$tc這個方法可以用以返回翻譯的復數字符串, 及一個key可以對應的翻譯文本,通過|進行連接:
例如:
// main.js new VueI18n({ messages: { car: "car | cars" } }) // template {{$tc("car", 1)}} ===>>> car {{$tc("car", 2)}} ===>>> cars
$te這個方法用以判斷需要翻譯的key在你提供的語言包(messages)中是否存在.
接下來就看看VueI18n構造函數及原型上提供了哪些可以被實例繼承的屬性或者方法
/* @flow */ import { install, Vue } from "./install" import { warn, isNull, parseArgs, fetchChoice } from "./util" import BaseFormatter from "./format" // 轉化函數 封裝了format, 里面包含了template模板替換的方法 import getPathValue from "./path" import type { PathValue } from "./path" // VueI18n構造函數 export default class VueI18n { static install: () => void static version: string _vm: any _formatter: Formatter _root: ?I18n _sync: ?boolean _fallbackRoot: boolean _fallbackLocale: string _missing: ?MissingHandler _exist: Function _watcher: any // 實例化參數配置 constructor (options: I18nOptions = {}) { const locale: string = options.locale || "en-US" // vue-i18n初始化的時候語言參數配置 const messages: Messages = options.messages || {} // 本地配置的所有語言環境都是掛載到了messages這個屬性上 this._vm = null // ViewModel this._fallbackLocale = options.fallbackLocale || "en-US" // 缺省語言配置 this._formatter = options.formatter || new BaseFormatter() // 翻譯函數 this._missing = options.missing this._root = options.root || null this._sync = options.sync || false this._fallbackRoot = options.fallbackRoot || false this._exist = (message: Object, key: string): boolean => { if (!message || !key) { return false } return !isNull(getPathValue(message, key)) } this._resetVM({ locale, messages }) } // VM // 重置viewModel _resetVM (data: { locale: string, messages: Messages }): void { const silent = Vue.config.silent Vue.config.silent = true this._vm = new Vue({ data }) Vue.config.silent = silent } // 根實例的vm監聽locale這個屬性 watchLocale (): any { if (!this._sync || !this._root) { return null } const target: any = this._vm // vm.$watch返回的是一個取消觀察的函數,用來停止觸發回調 this._watcher = this._root.vm.$watch("locale", (val) => { target.$set(target, "locale", val) }, { immediate: true }) return this._watcher } // 停止觸發vm.$watch觀察函數 unwatchLocale (): boolean { if (!this._sync || !this._watcher) { return false } if (this._watcher) { this._watcher() delete this._watcher } return true } get vm (): any { return this._vm } get messages (): Messages { return this._vm.$data.messages } // get 獲取messages參數 set messages (messages: Messages): void { this._vm.$set(this._vm, "messages", messages) } // set 設置messages參數 get locale (): string { return this._vm.$data.locale } // get 獲取語言配置參數 set locale (locale: string): void { this._vm.$set(this._vm, "locale", locale) } // set 重置語言配置參數 get fallbackLocale (): string { return this._fallbackLocale } // fallbackLocale 是什么? set fallbackLocale (locale: string): void { this._fallbackLocale = locale } get missing (): ?MissingHandler { return this._missing } set missing (handler: MissingHandler): void { this._missing = handler } get formatter (): Formatter { return this._formatter } // get 轉換函數 set formatter (formatter: Formatter): void { this._formatter = formatter } // set 轉換函數 _warnDefault (locale: string, key: string, result: ?any, vm: ?any): ?string { if (!isNull(result)) { return result } if (this.missing) { this.missing.apply(null, [locale, key, vm]) } else { if (process.env.NODE_ENV !== "production") { warn( `Cannot translate the value of keypath "${key}". ` + "Use the value of keypath as default." ) } } return key } _isFallbackRoot (val: any): boolean { return !val && !isNull(this._root) && this._fallbackRoot } // 插入函數 _interpolate (message: Messages, key: string, args: any): any { if (!message) { return null } // 獲取key對應的字符串 let val: PathValue = getPathValue(message, key) if (Array.isArray(val)) { return val } if (isNull(val)) { val = message[key] } if (isNull(val)) { return null } if (typeof val !== "string") { warn(`Value of key "${key}" is not a string!`) return null } // TODO ?? 這里的links是干什么的? // Check for the existance of links within the translated string if (val.indexOf("@:") >= 0) { // Match all the links within the local // We are going to replace each of // them with its translation const matches: any = val.match(/(@:[w|.]+)/g) for (const idx in matches) { const link = matches[idx] // Remove the leading @: const linkPlaceholder = link.substr(2) // Translate the link const translatedstring = this._interpolate(message, linkPlaceholder, args) // Replace the link with the translated string val = val.replace(link, translatedstring) } } // 如果沒有傳入需要替換的obj, 那么直接返回字符串, 否則調用this._format進行變量等的替換 return !args ? val : this._format(val, args) // 獲取替換后的字符 } _format (val: any, ...args: any): any { return this._formatter.format(val, ...args) } // 翻譯函數 _translate (messages: Messages, locale: string, fallback: string, key: string, args: any): any { let res: any = null /** * messages[locale] 使用哪個語言包 * key 語言映射表的key * args 映射替換關系 */ res = this._interpolate(messages[locale], key, args) if (!isNull(res)) { return res } res = this._interpolate(messages[fallback], key, args) if (!isNull(res)) { if (process.env.NODE_ENV !== "production") { warn(`Fall back to translate the keypath "${key}" with "${fallback}" locale.`) } return res } else { return null } } // 翻譯的核心函數 /** * 這里的方法傳入的參數參照mixin.js里面的定義的方法 * key map的key值 (為接受的外部參數) * _locale 語言配置選項: "zh-CN" | "en-US" (內部變量) * messages 映射表 (內部變量) * host為這個i18n的實例 (內部變量) * */ _t (key: string, _locale: string, messages: Messages, host: any, ...args: any): any { if (!key) { return "" } // parseArgs函數用以返回傳入的局部語言配置, 及映射表 const parsedArgs = parseArgs(...args) // 接收的參數{ locale, params(映射表) } const locale = parsedArgs.locale || _locale // 語言配置 // 字符串替換 /** * @params messages 語言包 * @params locale 語言配置 * @params fallbackLocale 缺省語言配置 * @params key 替換的key值 * @params parsedArgs.params 需要被替換的參數map表 */ const ret: any = this._translate(messages, locale, this.fallbackLocale, key, parsedArgs.params) if (this._isFallbackRoot(ret)) { if (process.env.NODE_ENV !== "production") { warn(`Fall back to translate the keypath "${key}" with root locale.`) } if (!this._root) { throw Error("unexpected error") } return this._root.t(key, ...args) } else { return this._warnDefault(locale, key, ret, host) } } // 轉化函數 t (key: string, ...args: any): string { return this._t(key, this.locale, this.messages, null, ...args) } _tc (key: string, _locale: string, messages: Messages, host: any, choice?: number, ...args: any): any { if (!key) { return "" } if (choice !== undefined) { return fetchChoice(this._t(key, _locale, messages, host, ...args), choice) } else { return this._t(key, _locale, messages, host, ...args) } } tc (key: string, choice?: number, ...args: any): any { return this._tc(key, this.locale, this.messages, null, choice, ...args) } _te (key: string, _locale: string, messages: Messages, ...args: any): boolean { const locale = parseArgs(...args).locale || _locale return this._exist(messages[locale], key) } te (key: string, ...args: any): boolean { return this._te(key, this.locale, this.messages, ...args) } } VueI18n.install = install VueI18n.version = "__VERSION__" // 如果是通過CDN或者外鏈的形式引入的Vue if (typeof window !== "undefined" && window.Vue) { window.Vue.use(VueI18n) }
另外還有一個比較重要的庫函數format.js:
/** * String format template * - Inspired: * https://github.com/Matt-Esch/string-template/index.js */ // 變量的替換, 在字符串模板中寫的站位符 {xxx} 進行替換 const RE_NARGS: RegExp = /(%|){([0-9a-zA-Z_]+)}/g /** * template * * @param {String} string * @param {Array} ...args * @return {String} */ // 模板替換函數 export function template (str: string, ...args: any): string { // 如果第一個參數是一個obj if (args.length === 1 && typeof args[0] === "object") { args = args[0] } else { args = {} } if (!args || !args.hasOwnProperty) { args = {} } // str.prototype.replace(substr/regexp, newSubStr/function) 第二個參數如果是個函數的話,每次匹配都會調用這個函數 // match 為匹配的子串 return str.replace(RE_NARGS, (match, prefix, i, index) => { let result: string // match是匹配到的字符串 // prefix ??? // i 括號中需要替換的字符換 // index是偏移量 // 字符串中如果出現{xxx}不需要被替換。那么應該寫成{{xxx}} if (str[index - 1] === "{" && str[index + match.length] === "}") { return i } else { // 判斷args obj是否包含這個key值 // 返回替換值, 或者被匹配上的字符串的值 result = hasOwn(args, i) ? args[i] : match if (isNull(result)) { return "" } return result } }) }總結
這個頁面是使用vue作為前端框架,使用vue-i18n作為國際化的工具:
和后端同學約定好語言標識字段
前端根據后端下發的語言標識字段來調用不同的語言包
文本內容使用vue-i18n進行替換
圖片內容需要視覺同學提供多語言版本
樣式需要根據多語言進行定制。比如在body上添加多語言的標識class屬性
第三方的SDK或插件的國際化推動
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82100.html
摘要:直接上預覽鏈接國際化處理以及項目自動切換中英文環境搭建命令進入項目目錄,執行以下命令安裝國際化插件項目增加國際化翻譯文件在項目的下添加文件夾增加中文翻譯文件以及英文翻譯文件,里面分別存儲項目中需要翻譯的信息。 0. 直接上 預覽鏈接 Vue國際化處理 vue-i18n 以及項目自動切換中英文 1. 環境搭建 命令進入項目目錄,執行以下命令安裝vue 國際化插件vue-i18n...
摘要:前言上個月月底開源組開源了使用適配人人企業版專業版的前端工程具體詳情見人人企業版適配發布。當然,也督促自己產出一篇相關的文章,來記錄這次有趣的學習之旅。 Created by huqi at 2019-5-5 13:01:14 Updated by huqi at 2019-5-20 15:57:37 前言 上個月月底@D2開源組 開源了使用 D2Admin 適配 人人企業版(專業版) 的...
摘要:官網已經做了詳細介紹,這里依葫蘆畫瓢跟著實現一下為了實現插件的多語言切換按照如上把國際化文件都整合到一起,避免中大段引入相關代碼。 使用方法: 在配合 Element-UI 一起使用時,會有2個問題: ####(1)、頁面刷新后,通過按鈕切換的語言還原成了最初的語言,無法保存 ####(2)、框架內部自帶的提示文字無法更改,比如像時間選擇框內部中的提示文字 關于第一個問題,可以在初始化...
摘要:需求公司項目需要國際化,點擊按鈕切換中文英文安裝注入實例中,項目中實現調用和模板語法語言標識通過切換的值來實現語言切換中文語言包英文語言包最后對應語言包中文語言包首頁概覽公司概述財務報表更多附錄主要財務指標對比分析新聞事件檔案 需求 公司項目需要國際化,點擊按鈕切換中文/英文 1、安裝 npm install vue-i18n --save 2、注入 vue 實例中,項目中實現調用 ...
摘要:國內主要主要三點,一個是港澳臺采用中文繁體英文,內陸通俗中文簡體,新疆等地區采用文化標準。 I18n (internationalization ) ---未完善 產品國際化是產品后期維護及推廣中重要的一環,通過國際化操作使得產品能更好適應不同語言和地區的需求 國際化重點:1、 語言語言本地化2、 文化顏色、習俗等3、 書寫習慣日期格式、時區、數字格式、書寫方向備...
閱讀 3735·2023-01-11 11:02
閱讀 4244·2023-01-11 11:02
閱讀 3050·2023-01-11 11:02
閱讀 5181·2023-01-11 11:02
閱讀 4737·2023-01-11 11:02
閱讀 5534·2023-01-11 11:02
閱讀 5313·2023-01-11 11:02
閱讀 3990·2023-01-11 11:02