筆者在套用語系時,習慣將該 key 值對應的中文語系文字當作註解一併插入,但語系文字會經常被調來調去,此時註解在畫面的文字就會有不同步的情況;本文透過 C# 實作一個同步語系文字的工具,讓「語系檔」與「語系套用註解」永遠保持一致的狀態。
前言
筆者在套用語系時,習慣將該 key 值對應的中文語系文字當作註解一併插入,這樣的好處是可以透過畫面上的文字快速搜尋到該頁面位置,並且在檢視頁面原始碼時也會比較清楚各欄位標題,不用再從 key 值去猜想了。
但語系文字經常會被調來調去,此時註解在畫面的文字就會有不同步的情況,這對有點強迫症的開發人員就會很不舒服 (握拳);本文透過 C# 實作一個同步語系文字的工具,讓語系檔與語系註解永遠保持一致的狀態。
實作說明
首先將語系檔以 StreamReader 開啟後,透過 Regex 找出各 key-value pair 值存放在 Dictionary 中。
public Dictionary<string,string> GetLangDictionary(string langPath)
{
var langs = new Dictionary<string, string>();
var code = string.Empty;
using (StreamReader sr = new StreamReader(langPath))
{
code = sr.ReadToEnd();
string pattern = @"(__.*): ['|`](.*)['|`]";
MatchCollection matches = Regex.Matches(code, pattern);
foreach (Match match in matches)
{
var key = match.Groups[1].Value;
var value = match.Groups[2].Value;
langs[key] = value;
}
}
return langs;
}
專案中對於語系的取用都具有一致性,因此可以透過固定 pattern 將這些文字給篩選出來。
使用以下 Regular Expression 來進行篩選
找出程式碼中有套用語系的地方後,依據取出語系的 key 值來尋找先前存入 Dictionary 的語系檔文字,接著直接取代程式碼中的語系註解文字即可,這樣就可以保持註解與語系檔的一致性。
public void ReplaceLangComment(string filePath, Dictionary<string, string> langs)
{
var code = string.Empty;
var isModified = false;
using (StreamReader sr = new StreamReader(filePath))
{
code = sr.ReadToEnd();
string pattern = @"\$t\(*.[`|']([\s\S]*?)[`|'](\/\*.*?\*\/)?.*?\)";
MatchCollection matches = Regex.Matches(code, pattern);
foreach (Match match in matches)
{
string langAllText = "";
string langKey = "";
string langOther = "";
langAllText = match.Value; // ex. $t('__resetPCodeTitle'/*重設使用者名稱及固定密碼*/)
langKey = match.Groups[1].Value; // ex. __resetPCodeTitle
langOther = match.Groups[2].Value; // ex. /*重設使用者名稱及固定密碼*/
// 本來就沒有註解就略過
if (!langOther.Contains(@"/*"))
{ continue; }
// 找不到語系檔有對應的 key 值 (印出備查)
if (!langs.ContainsKey(langKey.Trim()))
{
langKey.Dump();
continue;
}
// 取出需要取代的字串
var startIndex = langOther.IndexOf("/*");
var endIndex = langOther.LastIndexOf("*/");
string replacedTarget = langOther.Substring(startIndex, endIndex - startIndex + 2);
// 取出正確的語系文字
var hasSpace = replacedTarget.Contains("/* ");
string correctLangValue = hasSpace ? $"/* {langs[langKey.Trim()]} */" : $"/*{langs[langKey.Trim()]}*/";
// 執行取代
string newLangAllText = langAllText.Replace(replacedTarget, correctLangValue);
code = code.Replace(langAllText, newLangAllText);
isModified = true;
}
}
if (isModified)
{
File.WriteAllText(filePath, code);
}
}
完整代碼
完整代碼如下,只要設定中文語系檔路徑位置後,再透過 Directory.GetFiles 方法過濾出需要同步語系註解的所有檔案清單,逐一執行 ReplaceLangComment 進行同步即可。以下代碼貼到 LINQPad 直接執行即可。
void Main()
{
// 中文語系檔位置
var langPath = @"D:\xxx\src\i18n\tw\lang.js";
var langs = GetLangDictionary(langPath);
// 掃描程式碼目錄 (取出所有 vue 檔)
string[] filePaths = Directory.GetFiles(@"D:\xxx\src", "*.vue", SearchOption.AllDirectories);
foreach (var filePath in filePaths)
{
// 進行同步
ReplaceLangComment(filePath, langs);
}
}
public Dictionary<string, string> GetLangDictionary(string langPath)
{
var langs = new Dictionary<string, string>();
var code = string.Empty;
using (StreamReader sr = new StreamReader(langPath))
{
code = sr.ReadToEnd();
string pattern = @"(__.*): ['|`](.*)['|`]";
MatchCollection matches = Regex.Matches(code, pattern);
foreach (Match match in matches)
{
var key = match.Groups[1].Value;
var value = match.Groups[2].Value;
langs[key] = value;
}
}
return langs;
}
public void ReplaceLangComment(string filePath, Dictionary<string, string> langs)
{
var code = string.Empty;
var isModified = false;
using (StreamReader sr = new StreamReader(filePath))
{
code = sr.ReadToEnd();
string pattern = @"\$t\(*.[`|']([\s\S]*?)[`|'](\/\*.*?\*\/)?.*?\)";
MatchCollection matches = Regex.Matches(code, pattern);
foreach (Match match in matches)
{
string langAllText = "";
string langKey = "";
string langOther = "";
langAllText = match.Value; // ex. $t('__resetPCodeTitle'/*重設使用者名稱及固定密碼*/)
langKey = match.Groups[1].Value; // ex. __resetPCodeTitle
langOther = match.Groups[2].Value; // ex. /*重設使用者名稱及固定密碼*/
// 本來就沒有註解就略過
if (!langOther.Contains(@"/*"))
{ continue; }
// 找不到語系檔有對應的 key 值 (印出備查)
if (!langs.ContainsKey(langKey.Trim()))
{
langKey.Dump();
continue;
}
// 取出需要取代的字串
var startIndex = langOther.IndexOf("/*");
var endIndex = langOther.LastIndexOf("*/");
string replacedTarget = langOther.Substring(startIndex, endIndex - startIndex + 2);
// 取出正確的語系文字
var hasSpace = replacedTarget.Contains("/* ");
string correctLangValue = hasSpace ? $"/* {langs[langKey.Trim()]} */" : $"/*{langs[langKey.Trim()]}*/";
// 執行取代
string newLangAllText = langAllText.Replace(replacedTarget, correctLangValue);
code = code.Replace(langAllText, newLangAllText);
isModified = true;
}
}
if (isModified)
{
File.WriteAllText(filePath, code);
}
}
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !