工具效果如圖:
?多語言是個(gè)非常簡單且常用的功能。但是重復(fù)工作量大,程序手動把多語言Key配置到多語言表經(jīng)常會出現(xiàn)錯(cuò)漏,或者幾經(jīng)改版,有些Key已經(jīng)不用卻沒有剔除,久而久之造成冗余。這中簡單且重復(fù)的工作必須讓工具來完成。
功能設(shè)計(jì):
多語言通過Key,Value的形式保存,通過多語言API GF.Localization.GetText(Key)獲取當(dāng)前語言對應(yīng)的Value值。
1. 一鍵掃描多語言文本。掃描prefab資源、excel數(shù)據(jù)表以及代碼里的多語言文本,這里掃描的就是多語言的Key。
2. 多語言列表(添加到此列表即為支持該語言)。點(diǎn)擊"+"號彈出未添加的語言列表,點(diǎn)擊對應(yīng)語言添加到語言列表。多語言列表的第一項(xiàng)記為“母語”,其它語言以“母語”為基準(zhǔn)翻譯為對應(yīng)語言。
3. 一鍵翻譯。由于ChatGPT請求次數(shù)有限制,Google翻譯需要魔法上網(wǎng)。最終為了體驗(yàn)選擇了接入百度翻譯。我們只需要把“母語”的Value填寫好,其它語言直接通過百度翻譯生成Value。
4. 由于機(jī)器翻譯結(jié)果還需要人工審核修正。為了方便,工具先生成多語言Excel文件,方便交給其它部門翻譯。項(xiàng)目真正使用的多語言文件是工具將多語言Excel導(dǎo)出的json文件。
5. 多語言工具以列表的形式顯示“母語”,可以手動修改Key,Value值。
6. 細(xì)節(jié)體驗(yàn)優(yōu)化。由于每次掃描結(jié)果會覆蓋原多語言文件,可以通過勾選【鎖定】強(qiáng)制保留該行。同時(shí)也在Excel的第一列生成了【鎖定】勾選框方便策劃操作。


?7. 由于百度翻譯免費(fèi)翻譯字節(jié)數(shù)有上限,為了節(jié)省翻譯字節(jié)。一鍵翻譯默認(rèn)只翻譯Value值為空白的行,如果想強(qiáng)制翻譯所有行可以通過一鍵翻譯的下拉按鈕強(qiáng)制翻譯全部行。


功能實(shí)現(xiàn):
1. 一鍵掃描多語言文本:
①掃描Prefab資源上的多語言文本:
GameFramework框架提供了UIStringKey專門用來填寫多語言文本Key, 所以只需要從所有Prefab上獲取UIStringKey腳本上填寫的Key即可。
?掃描prefab上的多語言Key:
/// <summary>
/// 掃描Prefab中的國際化語言
/// </summary>
public static List<string> ScanLocalizationTextFromPrefab(Action<string, int, int> onProgressUpdate = null)
{
var assetGUIDs = AssetDatabase.FindAssets("t:Prefab", ConstEditor.PrefabsPath);
List<string> keyList = new List<string>();
int totalCount = assetGUIDs.Length;
for (int i = 0; i < totalCount; i++)
{
string path = AssetDatabase.GUIDToAssetPath(assetGUIDs[i]);
var pfb = AssetDatabase.LoadAssetAtPath<GameObject>(path);
onProgressUpdate?.Invoke(path, totalCount, i);
var keyArr = pfb.GetComponentsInChildren<UnityGameFramework.Runtime.UIStringKey>(true);
foreach (var newKey in keyArr)
{
if (string.IsNullOrWhiteSpace(newKey.Key) || keyList.Contains(newKey.Key)) continue;
keyList.Add(newKey.Key);
}
}
return keyList;
}
② 掃描數(shù)據(jù)表Excel中的多語言文本:
首先需要標(biāo)記數(shù)據(jù)表多語言列,在數(shù)據(jù)表備注行用”i18n“標(biāo)識,程序就自動掃描添加標(biāo)識的列:
?掃描excel中的多語言文本:
/// <summary>
/// 從DataTable Excel文件掃描本地化文本
/// </summary>
/// <param name="onProgressUpdate"></param>
/// <returns></returns>
public static List<string> ScanLocalizationTextFromDataTables(Action<string, int, int> onProgressUpdate = null)
{
List<string> keyList = new List<string>();
var appConfig = AppConfigs.GetInstanceEditor();
var mainTbFullFiles = GameDataGenerator.GameDataExcelRelative2FullPath(GameDataType.DataTable, appConfig.DataTables);
var tbFullFiles = GameDataGenerator.GetGameDataExcelWithABFiles(GameDataType.DataTable, mainTbFullFiles);//同時(shí)掃描AB測試表
for (int i = 0; i < tbFullFiles.Length; i++)
{
var excelFile = tbFullFiles[i];
var fileInfo = new FileInfo(excelFile);
if (!fileInfo.Exists) continue;
onProgressUpdate?.Invoke(excelFile, tbFullFiles.Length, i);
string tmpExcelFile = UtilityBuiltin.ResPath.GetCombinePath(fileInfo.Directory.FullName, GameFramework.Utility.Text.Format("{0}.temp", fileInfo.Name));
try
{
File.Copy(excelFile, tmpExcelFile, true);
using (var excelPackage = new ExcelPackage(tmpExcelFile))
{
var excelSheet = excelPackage.Workbook.Worksheets.FirstOrDefault();
if (excelSheet.Dimension.End.Row >= 4)
{
for (int colIndex = excelSheet.Dimension.Start.Column; colIndex <= excelSheet.Dimension.End.Column; colIndex++)
{
if (excelSheet.GetValue<string>(4, colIndex)?.ToLower() != EXCEL_I18N_TAG)
{
continue;
}
for (int rowIndex = 5; rowIndex <= excelSheet.Dimension.End.Row; rowIndex++)
{
string langKey = excelSheet.GetValue<string>(rowIndex, colIndex);
if (string.IsNullOrWhiteSpace(langKey) || keyList.Contains(langKey)) continue;
keyList.Add(langKey);
}
}
}
}
}
catch (Exception e)
{
Debug.LogError($"掃描數(shù)據(jù)表本地化文本失敗!文件:{excelFile}, Error:{e.Message}");
}
if (File.Exists(tmpExcelFile))
{
File.Delete(tmpExcelFile);
}
}
return keyList;
}
③ 掃描代碼中的多語言文本:
原理:搜索代碼中所有調(diào)用國際化函數(shù)GF.Localization.GetText(string key)的地方,然后把調(diào)用時(shí)傳入?yún)?shù)key的字符串值掃描出來。
首先只能通過靜態(tài)解析cs代碼,獲取函數(shù)調(diào)用時(shí)傳入?yún)?shù)的值。這比想象中復(fù)雜得多,比如:
1. 如果傳入的是字符串常量很容易獲取,但如果傳入的是變量,就需要找到該變量的初始值賦值,變量又涉及到局部變量和全局變量。
2. 如果key中包含特殊字符會影響正則表達(dá)式的匹配,所以不能使用正則表達(dá)式。
3. 注釋的代碼不應(yīng)該掃描。
為了工具安全完善,最終選擇了用"高射炮打蚊子", 使用微軟Roslyn作為CSharp靜態(tài)解析庫。但是這個(gè)解析庫依賴dll太多直接導(dǎo)入U(xiǎn)nity會有各種沖突,為了Unity工程的兼容性索性寫個(gè)C#命令行程序,由Unity代碼調(diào)用命令行程序掃描代碼,把掃描結(jié)果存入緩存文件供Unity讀取使用。而且命令行程序可以發(fā)布跨平臺包,不用擔(dān)心跨平臺問題。
用Visual Studio新建C#命令行程序,為工程添加CodeAnalysis.CSharp庫:
?命令行程序代碼:
其中命令行args, 第一參數(shù)是cs代碼文件名(完整路徑),第二個(gè)參數(shù)是掃描結(jié)果輸出到的文件(通過文本追加的方式把掃描結(jié)果列表追加到文本文件),剩余參數(shù)是目標(biāo)函數(shù)名,因?yàn)楂@取國際化文本的函數(shù)可能有多個(gè)。
internal class Program
{
static int Main(string[] args)
{
try
{
string csFile = args[0];
string outputFile = args[1];
List<string> funcNames = new List<string>();
for (int i = 2; i < args.Length; i++)
{
funcNames.Add(args[i]);
}
List<string> resultList = new List<string>();
if ((File.GetAttributes(csFile) & FileAttributes.Directory) == FileAttributes.Directory)
{
//如果傳的是文件夾,掃描該文件夾下的所有cs文件
var csFiles = Directory.GetFiles(csFile, "*.cs", SearchOption.AllDirectories);
foreach (var item in csFiles)
{
var codeText = File.ReadAllText(item);
var strList = GetTextArgumentValues(codeText, funcNames);
if (strList.Count > 0)
{
resultList.AddRange(strList);
}
}
}
else
{
if (File.Exists(csFile))
{
var codeText = File.ReadAllText(csFile);
var strList = GetTextArgumentValues(codeText, funcNames);
if (strList.Count > 0)
{
resultList.AddRange(strList);
}
}
}
resultList.Distinct();//去重
resultList.RemoveAll(x => string.IsNullOrWhiteSpace(x));
Console.WriteLine($"\n\n--------------Result List Count:{resultList.Count}--------------");
for (int i = 0; i < resultList.Count; i++)
{
var str = resultList[i];
Console.WriteLine($"{i + 1}.\t[{str}]");
}
Console.WriteLine("--------------Result List End--------------");
if (resultList.Count > 0)
{
File.AppendAllLines(outputFile, resultList);
}
return 0;
}
catch (Exception err)
{
Console.WriteLine($"Error:{err}");
}
return 1;
}
public static List<string> GetTextArgumentValues(string codeText, List<string> funcNames)
{
List<string> argumentValues = new List<string>();
SyntaxTree tree = CSharpSyntaxTree.ParseText(codeText);
var root = (CompilationUnitSyntax)tree.GetRoot();
var methodCalls = root.DescendantNodes().OfType<InvocationExpressionSyntax>().Where(i =>
{
return funcNames.Contains(i.Expression.ToString());
});
var compilation = CSharpCompilation.Create(typeof(object).Assembly.FullName, new SyntaxTree[] { tree })
.WithOptions(new CSharpCompilationOptions(OutputKind.ConsoleApplication))
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location));
var semanticModel = compilation.GetSemanticModel(tree);
var methodCallsArr = methodCalls.ToArray();
for (int i = 0; i < methodCallsArr.Length; i++)
{
var call = methodCallsArr[i];
var argumentList = call.ArgumentList;
if (argumentList.Arguments.Count >= 1)
{
var argExp = argumentList.Arguments[0].Expression;
if (argExp is LiteralExpressionSyntax literal)
{
Console.WriteLine($"{call} ------> {literal.Token.ValueText}");
argumentValues.Add(literal.Token.ValueText);
}
else if (argExp is IdentifierNameSyntax variable)
{
SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(variable);
if (symbolInfo.Symbol is IFieldSymbol fieldSymbol)
{
if (fieldSymbol.HasConstantValue)
{
argumentValues.Add((string)fieldSymbol.ConstantValue);
Console.WriteLine($"{call} ------> {fieldSymbol.ConstantValue}");
}
}
else if (symbolInfo.Symbol is ILocalSymbol localSymbol)
{
var localVar = localSymbol.DeclaringSyntaxReferences.Last()?.GetSyntax() as VariableDeclaratorSyntax;
if (localVar != null && localVar.Initializer != null)
{
var localVarValue = semanticModel.GetConstantValue(localVar.Initializer.Value);
if (localVarValue.Value != null)
{
argumentValues.Add((string)localVarValue.Value);
Console.WriteLine($"{call} ------> {localVarValue.Value}");
}
}
}
}
}
}
return argumentValues;
}
}
2.??接入百度翻譯開放API,實(shí)現(xiàn)一鍵翻譯多語言
百度翻譯官方接入文檔:百度翻譯開放平臺
?注冊后在開發(fā)者后臺可以看到App id和密鑰,用于發(fā)送翻譯WebRequest請求參數(shù)。
開發(fā)者實(shí)名認(rèn)證后可以變更為高級版,高級版每月可享受免費(fèi)翻譯100萬個(gè)字符,相當(dāng)于50萬個(gè)漢字。一次請求能翻譯6000個(gè)字符(3000漢字),每秒請求上限10次。
?以上限制就需要翻譯時(shí)需要一次性塞入多條待翻譯句子并且不能超過每次請求的上限字節(jié)。
比較坑的是百度翻譯以換行符拆分句子,如果國際化文本中包含換行符翻譯結(jié)果就不是我們想要的:
?所以我使用一個(gè)特殊字符"?"做為自己的多條句子之間的分割符,拿到翻譯結(jié)果再用"?"分割字符串得到句子數(shù)組。
百度翻譯上行字段:
var randomCode = System.DateTime.Now.Ticks.ToString();
var strBuilder = new StringBuilder();
strBuilder.Append(BAIDU_TRANS_URL);
strBuilder.AppendFormat("q={0}", UnityWebRequest.EscapeURL(srcText));
strBuilder.AppendFormat("&from={0}", GetBaiduLanguage(srcLang) ?? "auto"); //自動識別源文字語言
strBuilder.AppendFormat("&to={0}", GetBaiduLanguage(targetLang));//翻譯到目標(biāo)語言
strBuilder.AppendFormat("&appid={0}", EditorToolSettings.Instance.BaiduTransAppId);
strBuilder.AppendFormat("&salt={0}", randomCode);
strBuilder.AppendFormat("&sign={0}", GenerateBaiduSign(srcText, randomCode));
生成簽名:
/// <summary>
/// 生成百度翻譯請求簽名
/// </summary>
/// <param name="srcText"></param>
/// <returns></returns>
private static string GenerateBaiduSign(string srcText, string randomCode)
{
MD5 md5 = MD5.Create();
var fullStr = GameFramework.Utility.Text.Format("{0}{1}{2}{3}", EditorToolSettings.Instance.BaiduTransAppId, srcText, randomCode, EditorToolSettings.Instance.BaiduTransSecretKey);
byte[] byteOld = Encoding.UTF8.GetBytes(fullStr);
byte[] byteNew = md5.ComputeHash(byteOld);
StringBuilder sb = new StringBuilder();
foreach (byte b in byteNew)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
百度翻譯語言代號獲取,用ChatGPT幫我生成函數(shù),結(jié)果只有幾種是對的,無奈只能人工找對照表修改代號:
中文首字母 | 名稱 | 代碼 | 語種檢測 | 名稱 | 代碼 | 語種檢測 | 名稱 | 代碼 | 語種檢測 |
---|---|---|---|---|---|---|---|---|---|
A | 阿拉伯語 | ara | 是 | 愛爾蘭語 | gle | 是 | 奧克語 | oci | 是 |
阿爾巴尼亞語 | alb | 是 | 阿爾及利亞阿拉伯語 | arq | 否 | 阿肯語 | aka | 否 | |
阿拉貢語 | arg | 否 | 阿姆哈拉語 | amh | 是 | 阿薩姆語 | asm | 是 | |
艾馬拉語 | aym | 否 | 阿塞拜疆語 | aze | 是 | 阿斯圖里亞斯語 | ast | 是 | |
奧塞梯語 | oss | 否 | 愛沙尼亞語 | est | 是 | 奧杰布瓦語 | oji | 否 | |
奧里亞語 | ori | 是 | 奧羅莫語 | orm | 否 | ||||
B | 波蘭語 | pl | 是 | 波斯語 | per | 是 | 布列塔尼語 | bre | 是 |
巴什基爾語 | bak | 否 | 巴斯克語 | baq | 是 | 巴西葡萄牙語 | pot | 否 | |
白俄羅斯語 | bel | 是 | 柏柏爾語 | ber | 是 | 邦板牙語 | pam | 否 | |
保加利亞語 | bul | 是 | 北方薩米語 | sme | 否 | 北索托語 | ped | 否 | |
本巴語 | bem | 否 | 比林語 | bli | 否 | 比斯拉馬語 | bis | 否 | |
俾路支語 | bal | 否 | 冰島語 | ice | 是 | 波斯尼亞語 | bos | 是 | |
博杰普爾語 | bho | 否 | |||||||
C | 楚瓦什語 | chv | 否 | 聰加語 | tso | 否 | |||
D | 丹麥語 | dan | 是 | 德語 | de | 是 | 韃靼語 | tat | 是 |
撣語 | sha | 否 | 德頓語 | tet | 否 | 迪維希語 | div | 否 | |
低地德語 | log | 是 | |||||||
E | 俄語 | ru | 是 | ||||||
F | 法語 | fra | 是 | 菲律賓語 | fil | 是 | 芬蘭語 | fin | 是 |
梵語 | san | 否 | 弗留利語 | fri | 否 | 富拉尼語 | ful | 否 | |
法羅語 | fao | 否 | |||||||
G | 蓋爾語 | gla | 否 | 剛果語 | kon | 否 | 高地索布語 | ups | 否 |
高棉語 | hkm | 是 | 格陵蘭語 | kal | 否 | 格魯吉亞語 | geo | 是 | |
古吉拉特語 | guj | 是 | 古希臘語 | gra | 否 | 古英語 | eno | 否 | |
瓜拉尼語 | grn | 否 | |||||||
H | 韓語 | kor | 是 | 荷蘭語 | nl | 是 | 胡帕語 | hup | 否 |
哈卡欽語 | hak | 否 | 海地語 | ht | 否 | 黑山語 | mot | 否 | |
豪薩語 | hau | 否 | |||||||
J | 吉爾吉斯語 | kir | 否 | 加利西亞語 | glg | 是 | 加拿大法語 | frn | 否 |
加泰羅尼亞語 | cat | 是 | 捷克語 | cs | 是 | ||||
K | 卡拜爾語 | kab | 是 | 卡納達(dá)語 | kan | 是 | 卡努里語 | kau | 否 |
卡舒比語 | kah | 否 | 康瓦爾語 | cor | 否 | 科薩語 | xho | 是 | |
科西嘉語 | cos | 否 | 克里克語 | cre | 否 | 克里米亞韃靼語 | cri | 否 | |
克林貢語 | kli | 否 | 克羅地亞語 | hrv | 是 | 克丘亞語 | que | 否 | |
克什米爾語 | kas | 否 | 孔卡尼語 | kok | 否 | 庫爾德語 | kur | 是 | |
L | 拉丁語 | lat | 是 | 老撾語 | lao | 否 | 羅馬尼亞語 | rom | 是 |
拉特加萊語 | lag | 否 | 拉脫維亞語 | lav | 是 | 林堡語 | lim | 否 | |
林加拉語 | lin | 否 | 盧干達(dá)語 | lug | 否 | 盧森堡語 | ltz | 否 | |
盧森尼亞語 | ruy | 否 | 盧旺達(dá)語 | kin | 是 | 立陶宛語 | lit | 是 | |
羅曼什語 | roh | 否 | 羅姆語 | ro | 否 | 邏輯語 | loj | 否 | |
M | 馬來語 | may | 是 | 緬甸語 | bur | 是 | 馬拉地語 | mar | 否 |
馬拉加斯語 | mg | 是 | 馬拉雅拉姆語 | mal | 是 | 馬其頓語 | mac | 是 | |
馬紹爾語 | mah | 否 | 邁蒂利語 | mai | 是 | 曼克斯語 | glv | 否 | |
毛里求斯克里奧爾語 | mau | 否 | 毛利語 | mao | 否 | 孟加拉語 | ben | 是 | |
馬耳他語 | mlt | 是 | 苗語 | hmn | 否 | ||||
N | 挪威語 | nor | 是 | 那不勒斯語 | nea | 否 | 南恩德貝萊語 | nbl | 否 |
南非荷蘭語 | afr | 是 | 南索托語 | sot | 否 | 尼泊爾語 | nep | 是 | |
P | 葡萄牙語 | pt | 是 | 旁遮普語 | pan | 是 | 帕皮阿門托語 | pap | 否 |
普什圖語 | pus | 否 | |||||||
Q | 齊切瓦語 | nya | 否 | 契維語 | twi | 否 | 切羅基語 | chr | 否 |
R | 日語 | jp | 是 | 瑞典語 | swe | 是 | |||
S | 薩丁尼亞語 | srd | 否 | 薩摩亞語 | sm | 否 | 塞爾維亞-克羅地亞語 | sec | 否 |
塞爾維亞語 | srp | 是 | 桑海語 | sol | 否 | 僧伽羅語 | sin | 是 | |
世界語 | epo | 是 | 書面挪威語 | nob | 是 | 斯洛伐克語 | sk | 是 | |
斯洛文尼亞語 | slo | 是 | 斯瓦希里語 | swa | 是 | 塞爾維亞語(西里爾) | src | 否 | |
索馬里語 | som | 是 | |||||||
T | 泰語 | th | 是 | 土耳其語 | tr | 是 | 塔吉克語 | tgk | 是 |
泰米爾語 | tam | 是 | 他加祿語 | tgl | 是 | 提格利尼亞語 | tir | 否 | |
泰盧固語 | tel | 是 | 突尼斯阿拉伯語 | tua | 否 | 土庫曼語 | tuk | 否 | |
W | 烏克蘭語 | ukr | 是 | 瓦隆語 | wln | 是 | 威爾士語 | wel | 是 |
文達(dá)語 | ven | 否 | 沃洛夫語 | wol | 否 | 烏爾都語 | urd | 是 | |
X | 西班牙語 | spa | 是 | 希伯來語 | heb | 是 | 希臘語 | el | 是 |
匈牙利語 | hu | 是 | 西弗里斯語 | fry | 是 | 西里西亞語 | sil | 否 | |
希利蓋農(nóng)語 | hil | 否 | 下索布語 | los | 否 | 夏威夷語 | haw | 否 | |
新挪威語 | nno | 是 | 西非書面語 | nqo | 否 | 信德語 | snd | 否 | |
修納語 | sna | 否 | 宿務(wù)語 | ceb | 否 | 敘利亞語 | syr | 否 | |
巽他語 | sun | 否 | |||||||
Y | 英語 | en | 是 | 印地語 | hi | 是 | 印尼語 | id | 是 |
意大利語 | it | 是 | 越南語 | vie | 是 | 意第緒語 | yid | 否 | |
因特語 | ina | 否 | 亞齊語 | ach | 否 | 印古什語 | ing | 否 | |
伊博語 | ibo | 否 | 伊多語 | ido | 否 | 約魯巴語 | yor | 否 | |
亞美尼亞語 | arm | 是 | 伊努克提圖特語 | iku | 否 | 伊朗語 | ir | 否 | |
Z | 中文(簡體) | zh | 是 | 中文(繁體) | cht | 是 | 中文(文言文) | wyw | 是 |
中文(粵語) | yue | 是 | 扎扎其語 | zaz | 否 | 中古法語 | frm | 否 | |
祖魯語 | zul | 否 | 爪哇語 | jav | 否 |
無私獻(xiàn)上獲取百度翻譯語言代碼:文章來源:http://www.zghlxwxcb.cn/news/detail-675811.html
/// <summary>
/// 根據(jù)語言類型返回對應(yīng)的百度語言縮寫
/// </summary>
/// <param name="lang"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static string GetBaiduLanguage(Language lang)
{
switch (lang)
{
case Language.Afrikaans:
return "afr";
case Language.Albanian:
return "alb";
case Language.Arabic:
return "ara";
case Language.Basque:
return "baq";
case Language.Belarusian:
return "bel";
case Language.Bulgarian:
return "bul";
case Language.Catalan:
return "cat";
case Language.ChineseSimplified:
return "zh";
case Language.ChineseTraditional:
return "cht";
case Language.Croatian:
return "hrv";
case Language.Czech:
return "cs";
case Language.Danish:
return "dan";
case Language.Dutch:
return "nl";
case Language.English:
return "en";
case Language.Estonian:
return "est";
case Language.Faroese:
return "fao";
case Language.Finnish:
return "fin";
case Language.French:
return "fra";
case Language.Georgian:
return "geo";
case Language.German:
return "de";
case Language.Greek:
return "el";
case Language.Hebrew:
return "heb";
case Language.Hungarian:
return "hu";
case Language.Icelandic:
return "ice";
case Language.Indonesian:
return "id";
case Language.Italian:
return "it";
case Language.Japanese:
return "jp";
case Language.Korean:
return "kor";
case Language.Latvian:
return "lav";
case Language.Lithuanian:
return "lit";
case Language.Macedonian:
return "mac";
case Language.Malayalam:
return "may";
case Language.Norwegian:
return "nor";
case Language.Persian:
return "per";
case Language.Polish:
return "pl";
case Language.PortugueseBrazil:
return "pt";
case Language.PortuguesePortugal:
return "pt";
case Language.Romanian:
return "rom";
case Language.Russian:
return "ru";
case Language.SerboCroatian:
return "sec";
case Language.SerbianCyrillic:
return "src";
case Language.SerbianLatin:
return "srp";
case Language.Slovak:
return "sk";
case Language.Slovenian:
return "slo";
case Language.Spanish:
return "spa";
case Language.Swedish:
return "swe";
case Language.Thai:
return "th";
case Language.Turkish:
return "tr";
case Language.Ukrainian:
return "ukr";
case Language.Vietnamese:
return "vie";
default:
throw new NotSupportedException($"暫不支持該語言:{lang}");
}
}
接入百度翻譯示例代碼:文章來源地址http://www.zghlxwxcb.cn/news/detail-675811.html
private static void TranslateAndSave(List<LocalizationText> mainLangTexts, Language srcLang, List<LocalizationText> langTexts, Language targetLang, bool forceAll)
{
int curTransIdx = 0;
while (curTransIdx < langTexts.Count)
{
string totalText = "";
List<int> totalTextIdx = new List<int>();
for (; curTransIdx < langTexts.Count; curTransIdx++)
{
var text = langTexts[curTransIdx];
string srcText = "";
if (forceAll)
{
var mainText = mainLangTexts.FirstOrDefault(tmpItm => tmpItm.Key.CompareTo(text.Key) == 0);
if (mainText != null && !string.IsNullOrWhiteSpace(mainText.Value))
{
srcText = mainText.Value;
}
}
else
{
if (string.IsNullOrWhiteSpace(text.Value))
{
var mainText = mainLangTexts.FirstOrDefault(tmpItm => tmpItm.Key.CompareTo(text.Key) == 0);
if (mainText != null && !string.IsNullOrWhiteSpace(mainText.Value))
{
srcText = mainText.Value;
}
}
}
if (!string.IsNullOrWhiteSpace(srcText))
{
if ((totalText.Length + srcText.Length) > EditorToolSettings.Instance.BaiduTransMaxLength)
{
curTransIdx -= 1; //如果長度超了下個(gè)請求接著這行
break;
}
totalText += srcText + TRANS_SPLIT_TAG;
totalTextIdx.Add(curTransIdx);
}
}
if (string.IsNullOrWhiteSpace(totalText))
{
curTransIdx++;//如果一行字?jǐn)?shù)就超過上限則跳過翻譯這行
continue;
}
totalText = totalText.Substring(0, totalText.Length - TRANS_SPLIT_TAG.Length);//去掉結(jié)分隔符
TMP_EditorCoroutine.StartCoroutine(TranslateCoroutine(totalText, srcLang, targetLang, (success, trans, userDt) =>
{
if (success)
{
ParseAndSaveTransResults(langTexts, targetLang, trans, userDt as int[]);
}
}, totalTextIdx.ToArray()));
}
}
/// <summary>
/// 解析翻譯結(jié)果并保存到語言Excel
/// </summary>
/// <param name="targetTexts"></param>
/// <param name="targetLang"></param>
/// <param name="resultStr"></param>
/// <param name="resultTextIdxArr"></param>
private static void ParseAndSaveTransResults(List<LocalizationText> targetTexts, Language targetLang, TranslationResult trans, int[] resultTextIdxArr)
{
if (string.IsNullOrWhiteSpace(trans.dst) || resultTextIdxArr == null) return;
var srcTexts = trans.src.Split(TRANS_SPLIT_TAG);
var resultTexts = trans.dst.Split(TRANS_SPLIT_TAG);
if (resultTexts.Length != resultTextIdxArr.Length || resultTexts.Length != srcTexts.Length)
{
Debug.LogError($"翻譯失敗, 翻譯結(jié)果數(shù)量和索引數(shù)不一致.result count:{resultTexts.Length}, but index count:{resultTextIdxArr.Length}\n 翻譯結(jié)果:{trans.dst}");
return;
}
for (int i = 0; i < resultTextIdxArr.Length; i++)
{
var idx = resultTextIdxArr[i];
var srcStr = srcTexts[i];
var dstStr = resultTexts[i].Trim();
int leadingSpaces = srcStr.Length - srcStr.TrimStart().Length;
int trailingSpaces = srcStr.Length - srcStr.TrimEnd().Length;
dstStr = dstStr.PadLeft(dstStr.Length + leadingSpaces);
dstStr = dstStr.PadRight(dstStr.Length + trailingSpaces);
targetTexts[idx].Value = dstStr;
}
SaveLanguage(targetLang, targetTexts);
}
private static IEnumerator TranslateCoroutine(string srcText, Language srcLang, Language targetLang, Action<bool, TranslationResult, object> onComplete, object userData)
{
var randomCode = System.DateTime.Now.Ticks.ToString();
var strBuilder = new StringBuilder();
strBuilder.Append(BAIDU_TRANS_URL);
strBuilder.AppendFormat("q={0}", UnityWebRequest.EscapeURL(srcText));
strBuilder.AppendFormat("&from={0}", GetBaiduLanguage(srcLang) ?? "auto"); //自動識別源文字語言
strBuilder.AppendFormat("&to={0}", GetBaiduLanguage(targetLang));//翻譯到目標(biāo)語言
strBuilder.AppendFormat("&appid={0}", EditorToolSettings.Instance.BaiduTransAppId);
strBuilder.AppendFormat("&salt={0}", randomCode);
strBuilder.AppendFormat("&sign={0}", GenerateBaiduSign(srcText, randomCode));
//Debug.Log($"發(fā)送:{strBuilder}");
// 發(fā)送請求
using (var webRequest = UnityEngine.Networking.UnityWebRequest.Get(strBuilder.ToString()))
{
webRequest.SetRequestHeader("Content-Type", "text/html;charset=UTF-8");
webRequest.certificateHandler = new WebRequestCertNoValidate();
webRequest.SendWebRequest();
while (!webRequest.isDone) yield return null;
if (webRequest.result != UnityEngine.Networking.UnityWebRequest.Result.Success)
{
Debug.LogError($"---------翻譯{targetLang}請求失敗:{webRequest.error}---------");
onComplete?.Invoke(false, null, userData);
}
else
{
var json = webRequest.downloadHandler.text;
//Debug.Log($"接收:{json}");
try
{
var responseJson = UtilityBuiltin.Json.ToObject<JObject>(json);
if (responseJson.ContainsKey("trans_result"))
{
var resultArray = responseJson["trans_result"].ToObject<TranslationResult[]>();
if (resultArray != null && resultArray.Length > 0)
{
var resultTrans = resultArray[0];
onComplete?.Invoke(true, resultTrans, userData);
}
else
{
Debug.LogError($"---------翻譯{targetLang}失敗:{responseJson}---------");
onComplete?.Invoke(false, null, userData);
}
}
else
{
Debug.LogError($"---------翻譯{targetLang}失敗:{responseJson}---------");
onComplete?.Invoke(false, null, userData);
}
}
catch (System.Exception e)
{
Debug.LogError($"---------翻譯{targetLang}返回?cái)?shù)據(jù)解析失敗:{e.Message}---------");
onComplete?.Invoke(false, null, userData);
}
}
}
}
internal class TranslationResult
{
public string src;
public string dst;
}
工具完整代碼參考:GitHub - sunsvip/GF_HybridCLR
到了這里,關(guān)于【Unity編輯器擴(kuò)展】語言國際化工具,生成多語言Excel自動翻譯并導(dǎo)出多語言表的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!