1. 程式人生 > >讓現有vue前端專案快速支援多語言 - 用.net core程式快速替換中文為資源Key,咱不幹體力活

讓現有vue前端專案快速支援多語言 - 用.net core程式快速替換中文為資源Key,咱不幹體力活

前言

這是我第一次發部落格,2020年立個flag以後要經常發。

最近應公司上層要求,需要將現有專案儘快支援多語言,而中文內容可以找專業人員翻譯。那麼咱們說幹就幹,首先我們專案的前端是用vue寫的spa程式且元件方面用的element ui,那麼自然而然想到用vue官方推薦的vue i18n,我快速過了下i18n整個Guide官方文件,看來使用很簡單,主要步驟就是:

  • npm 安裝vue i18n外掛
  • 定義多語言資源字典物件
  • 例項化vue i18n例項
  • vue例項上掛載vue i18n 例項

這裡有一個最簡單的例項

相信大家都看就懂,但大家有沒想過,我前面說了公司要求是把現有專案儘快支援多語言,那說明我們的專案已經存在大量的程式碼。duang,尼瑪,那不是做“定義多語言資源字典物件”是一個體力活? 要知道,我們這個前端專案至少有成百上千個頁面,如果讓我一個一個的翻譯裡面的中文跟換成vue i18n要求的$t('key'),那估計我跟團隊得累死呀!!!所以,為了不幹體力活,就有了這篇文章。

先隨便拿一個前端檔案看看最終的效果圖:

自動替換前:

自動替換後:

感覺如何?最終的程式碼點這裡

下面我們來詳細介紹下實現的各個步驟

要想替換前端程式碼為vue i18n的語法,就涉及到自己編寫一套程式來實現準確替換,在替換之前,我們需要考慮:

  • 可以將<template><script>裡面的中文程式碼準確找出來(需要排除註釋等特殊情況,至少能替換98%以上的程式碼,少量程式碼可以自己手動替換)
  • 可以將中文匯出excel檔案,好讓專業的人可以進行翻譯
  • 可以將翻譯後的excel檔案生成我們程式碼要求的“定義多語言資源字典物件”

一,將所有中文Key找出來

由於vue i18n的資源key是可以包含中文英文跟特殊字元的,所以我們可以直接將文字直接當成key,這樣程式碼中的中文資訊就算換成多語言函式後也照樣能很容易讀懂,那麼這裡直接上這塊核心的.net core程式碼將所有key找出來(自然想到正則表示式去匹配),並保持到一個.txt檔案


        /// <summary>
        /// 抽離程式碼檔案中的中文到檔案
        /// </summary>
        /// <param name="filePath">程式碼檔案路徑</param>
        /// <param name="keyFilePath">需要保持的包含所有中文字典的文字檔案路徑</param>
        static void CreateKeyTxt(string filePath, string keyFilePath)
        {
            var regex = new Regex(@"((?<key>\w+) *= *['""](?<str>([^'""]*[\u4e00-\u9fa5]+[^'""]*))['""])|((?<start>[`""'>}]*)(?<str>[^\s`""'>}]*[\u4e00-\u9fa5]+[^\s`""'<{]*)(?<end>[`""'<{]*))");
            string fileContent = File.ReadAllText(filePath);

            var chineseMatchs = regex.Matches(fileContent);

            // 有中文需要處理
            if (chineseMatchs.Count > 0)
            {
                Dictionary<string, string> lines = new Dictionary<string, string>();
                foreach (Match htmlMatch in chineseMatchs)
                {
                    var str = htmlMatch.Groups["str"].Value.TrimEnd(':');

                    if (str.Contains("//") || str.Contains("/*") || str.Contains("*/") || str.Contains("<!--") || str.Contains("微軟雅黑"))
                    {
                        continue;
                    }

                    lines[str] = "";
                }

                using (StreamWriter fs = new StreamWriter(keyFilePath, true, Encoding.UTF8))
                {
                    foreach (var line in lines)
                    {
                        fs.WriteLine(line.Key);
                    }
                }
            }
        }

然後,你就可以拿這這個包含所有需要翻譯的內容,高高興興拿給翻譯人員讓他們辛苦勞作了!

二,根據包含所有中文Key跟翻譯內容的excel生成vue i18n要求的“定義多語言資源字典物件”檔案

這個步驟其實就是生成兩個js檔案,一個是zh-cn.js中文資原始檔,一個是比如en.js的英文資原始檔,而檔案的內容就是簡單的K-V檔案,比如:

export default {
    "取 消": "CANCEL",
    "確 定": "OK",
    "取消": "CANCEL",
    "確定": "OK",
    "確認": "OK",
    "@表示RR,- 表示AA": "@ is RR, - is AA",
}

主要程式碼是:

        static void SaveI18N()
        {
            // 需要生成或者更新的i18n js資原始檔夾地址
            var i18nResourceOutputFolderPath = Configuration["i18nResourceOutputFolderPath"];

            // 需要生成或者更新的i18n js資原始檔名
            var i18nResourceFileName = Configuration["i18nResourceFileName"];

            if (string.IsNullOrEmpty(i18nResourceOutputFolderPath))
            {
                throw new ApplicationException("失敗:請先配置需要生成或者更新的i18n js資原始檔夾地址");
            }
            if (string.IsNullOrEmpty(i18nResourceFileName))
            {
                throw new ApplicationException("失敗:請先配置需要生成或者更新的i18n js資原始檔名");
            }

            // 獲取前端資源字典檔案資料
            Dictionary<string, string> chineseDic = new Dictionary<string, string>();
            Dictionary<string, string> tDic = new Dictionary<string, string>();
            for (int i = 1; i < ExcelResourceFileData.Rows.Count; i++)
            {
                var shortName = (ExcelResourceFileData.Rows[i][0].ToString()).Trim();
                var chineseContent = (ExcelResourceFileData.Rows[i][1].ToString()).Trim();
                var tContent = (ExcelResourceFileData.Rows[i][2].ToString()).Trim();

                if (string.IsNullOrEmpty(shortName))
                {
                    throw new ApplicationException($"失敗:在第{i + 1}行存在空白的簡稱");
                }
                if (string.IsNullOrEmpty(chineseContent))
                {
                    throw new ApplicationException($"失敗:在第{i + 1}行存在空白的中文");
                }

                var key = $"\"{shortName}\"";
                chineseDic[key] = $"\"{chineseContent}\"";
                tDic[key] = $"\"{tContent}\"";
            }

            SaveI18NFile(i18nResourceOutputFolderPath, "zh-cn.js", chineseDic);
            SaveI18NFile(i18nResourceOutputFolderPath, i18nResourceFileName, tDic);
        }

        private static void SaveI18NFile(string i18nResourceOutputFolderPath, string fileName, Dictionary<string, string> resourceDic)
        {
            resourceDic = GetNewestResourceDic(i18nResourceOutputFolderPath, fileName, resourceDic);

            // 構建資原始檔內容
            StringBuilder newFileContent = new StringBuilder();
            newFileContent.AppendLine("export default {");
            foreach (var chineseKeyValue in resourceDic)
            {
                newFileContent.AppendLine($"    {chineseKeyValue.Key}: {chineseKeyValue.Value},");
            }
            newFileContent.AppendLine("}");

            File.WriteAllText(Path.Combine(i18nResourceOutputFolderPath, fileName), newFileContent.ToString(), Encoding.UTF8);
        }

三,最後當然就是重頭戲,替換中文前端程式碼

對於 <template> 裡面的程式碼,我們需要給property還有innerText分別單獨處理,比如

<el-button @click="onCancel" title="取消此上傳功能">取 消</el-button>

其中的title="取消此上傳功能"這個property是需要替換成:title="$t('取消此上傳功能')" 而innerText 取 消是需要替換成{{$t(取 消)}}的,最終替換為

<el-button @click="onCancel" :title="$t('取消此上傳功能')">{{$t(`取 消`)}}</el-button>

其中對 <template> 的替換核心程式碼為:

        /// <summary>
        /// 替換程式碼檔案template中的中文為資源key
        /// </summary>
        /// <param name="input">程式碼內容</param>
        /// <param name="resourceTypeStr">資源型別</param>
        /// <param name="pattern">正則表示式</param>
        /// <param name="isProperty">是否是屬性</param>
        /// <returns></returns>
        static string ReplaceChineseToI18NKeyForTemplate(string input, string resourceTypeStr, string pattern, bool isProperty = false)
        {
            var htmlMatchs = Regex.Matches(input, pattern, RegexOptions.IgnoreCase);
            int changedLength = 0;
            foreach (Match htmlMatch in htmlMatchs)
            {
                var newHtmlMatchIndex = htmlMatch.Index + changedLength;
                var chineseWordsMatch = Regex.Match(htmlMatch.Value, wordPattern, RegexOptions.IgnoreCase);
                var key = GetResourceKey(chineseWordsMatch.Value);

                // key不會空才需要替換
                if (!string.IsNullOrEmpty(key))
                {
                    string newHtml;

                    if (isProperty)
                    {
                        newHtml = ":" + Regex.Replace(htmlMatch.Value, wordPattern, "$t('" + key + "')");
                    }
                    else
                    {
                        newHtml = Regex.Replace(htmlMatch.Value, wordPattern, "{{$t('" + key + "')}}");
                    }

                    input = input.Substring(0, newHtmlMatchIndex) + newHtml + input.Substring(newHtmlMatchIndex + htmlMatch.Length);

                    changedLength += newHtml.Length - htmlMatch.Length;
                }
            }

            return input;
        }

<script> 的替換核心程式碼跟<template> 類似,最主要的區別是js程式碼裡面使用的是this.$t('key')。

到這裡我們就將整個前端系統的中文文字程式碼全部修改成通過資源key動態顯示對應的語言文字了。

本程式碼的一些不足

  • 上面的正則只能提取跟替換大部分中文情況,少數情況還需要手動修改;但即使這樣,也比一個一個替換節省了大量的人力物力。
  • <script> 替換為this.$t('key')中的this有不對的情況,改進方案是在程式碼前面import i18n物件,然後將this換成i18n物件,由於時間有限,本文的程式碼並沒涉及這塊,但實現起來比較容易。

一些思考

各位,相信你們跟我一樣,認為本文並沒有多少技術亮點,說白了,無非就是運用正則表示式替換一些程式碼而已。但我想說的,如果我們一開始就一個頁面一個頁面弄,那得弄多久才能完成老闆給我們的任務,所以往往解決問題,需要我們多靜下心來多思考一下,然後運用一些簡單的技術,即可快速實現我們想要的東西。特別是現在是一個快速發展的時代,更需要我們高效的解決問題,這樣才能體現我們的價值。

最終的程式碼點這裡

最後希望大家多多評論,2020年身體健康,過得順心!!!