当前位置: 首页 > article >正文

【Unity编辑器扩展】语言国际化工具,生成多语言Excel自动翻译并导出多语言表

工具效果如图:

 多语言是个非常简单且常用的功能。但是重复工作量大,程序手动把多语言Key配置到多语言表经常会出现错漏,或者几经改版,有些Key已经不用却没有剔除,久而久之造成冗余。这中简单且重复的工作必须让工具来完成。

功能设计:

多语言通过Key,Value的形式保存,通过多语言API GF.Localization.GetText(Key)获取当前语言对应的Value值。

1. 一键扫描多语言文本。扫描prefab资源、excel数据表以及代码里的多语言文本,这里扫描的就是多语言的Key。

2. 多语言列表(添加到此列表即为支持该语言)。点击"+"号弹出未添加的语言列表,点击对应语言添加到语言列表。多语言列表的第一项记为“母语”,其它语言以“母语”为基准翻译为对应语言。

3. 一键翻译。由于ChatGPT请求次数有限制,Google翻译需要魔法上网。最终为了体验选择了接入百度翻译。我们只需要把“母语”的Value填写好,其它语言直接通过百度翻译生成Value。

4. 由于机器翻译结果还需要人工审核修正。为了方便,工具先生成多语言Excel文件,方便交给其它部门翻译。项目真正使用的多语言文件是工具将多语言Excel导出的json文件。

5. 多语言工具以列表的形式显示“母语”,可以手动修改Key,Value值。

6. 细节体验优化。由于每次扫描结果会覆盖原多语言文件,可以通过勾选【锁定】强制保留该行。同时也在Excel的第一列生成了【锁定】勾选框方便策划操作。

多语言”母语“

基于”母语“自动生成/翻译的其它语言

 7. 由于百度翻译免费翻译字节数有上限,为了节省翻译字节。一键翻译默认只翻译Value值为空白的行,如果想强制翻译所有行可以通过一键翻译的下拉按钮强制翻译全部行。

一键生成的多语言Excel

自动导出多语言Excel为json文件

功能实现:

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;
        }

② 扫描数据表Excel中的多语言文本:

首先需要标记数据表多语言列,在数据表备注行用”i18n“标识,程序就自动扫描添加标识的列:

 扫描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);//同时扫描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($"扫描数据表本地化文本失败!文件:{excelFile}, Error:{e.Message}");
                }

                if (File.Exists(tmpExcelFile))
                {
                    File.Delete(tmpExcelFile);
                }
            }
            return keyList;
        }

③ 扫描代码中的多语言文本:

原理:搜索代码中所有调用国际化函数GF.Localization.GetText(string key)的地方,然后把调用时传入参数key的字符串值扫描出来。

首先只能通过静态解析cs代码,获取函数调用时传入参数的值。这比想象中复杂得多,比如:

1. 如果传入的是字符串常量很容易获取,但如果传入的是变量,就需要找到该变量的初始值赋值,变量又涉及到局部变量和全局变量。

2. 如果key中包含特殊字符会影响正则表达式的匹配,所以不能使用正则表达式。

3. 注释的代码不应该扫描。

为了工具安全完善,最终选择了用"高射炮打蚊子", 使用微软Roslyn作为CSharp静态解析库。但是这个解析库依赖dll太多直接导入Unity会有各种冲突,为了Unity工程的兼容性索性写个C#命令行程序,由Unity代码调用命令行程序扫描代码,把扫描结果存入缓存文件供Unity读取使用。而且命令行程序可以发布跨平台包,不用担心跨平台问题。

用Visual Studio新建C#命令行程序,为工程添加CodeAnalysis.CSharp库:

 命令行程序代码:

其中命令行args, 第一参数是cs代码文件名(完整路径),第二个参数是扫描结果输出到的文件(通过文本追加的方式把扫描结果列表追加到文本文件),剩余参数是目标函数名,因为获取国际化文本的函数可能有多个。

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,实现一键翻译多语言

百度翻译官方接入文档:百度翻译开放平台

 注册后在开发者后台可以看到App id和密钥,用于发送翻译WebRequest请求参数。

开发者实名认证后可以变更为高级版,高级版每月可享受免费翻译100万个字符,相当于50万个汉字。一次请求能翻译6000个字符(3000汉字),每秒请求上限10次。

 以上限制就需要翻译时需要一次性塞入多条待翻译句子并且不能超过每次请求的上限字节。

比较坑的是百度翻译以换行符拆分句子,如果国际化文本中包含换行符翻译结果就不是我们想要的:

 所以我使用一个特殊字符"↕"做为自己的多条句子之间的分割符,拿到翻译结果再用"↕"分割字符串得到句子数组。

百度翻译上行字段:

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));//翻译到目标语言
            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帮我生成函数,结果只有几种是对的,无奈只能人工找对照表修改代号:

中文首字母名称代码语种检测名称代码语种检测名称代码语种检测
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卡纳达语kan卡努里语kau
卡舒比语kah康瓦尔语cor科萨语xho
科西嘉语cos克里克语cre克里米亚鞑靼语cri
克林贡语kli克罗地亚语hrv克丘亚语que
克什米尔语kas孔卡尼语kok库尔德语kur
L拉丁语lat老挝语lao罗马尼亚语rom
拉特加莱语lag拉脱维亚语lav林堡语lim
林加拉语lin卢干达语lug卢森堡语ltz
卢森尼亚语ruy卢旺达语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
文达语ven沃洛夫语wol乌尔都语urd
X西班牙语spa希伯来语heb希腊语el
匈牙利语hu西弗里斯语fry西里西亚语sil
希利盖农语hil下索布语los夏威夷语haw
新挪威语nno西非书面语nqo信德语snd
修纳语sna宿务语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

无私献上获取百度翻译语言代码:

/// <summary>
        /// 根据语言类型返回对应的百度语言缩写
        /// </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}");
            }
        }

接入百度翻译示例代码:

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; //如果长度超了下个请求接着这行
                            break;
                        }
                        totalText += srcText + TRANS_SPLIT_TAG;
                        totalTextIdx.Add(curTransIdx);
                    }
                }
                if (string.IsNullOrWhiteSpace(totalText))
                {
                    curTransIdx++;//如果一行字数就超过上限则跳过翻译这行
                    continue;
                }
                totalText = totalText.Substring(0, totalText.Length - TRANS_SPLIT_TAG.Length);//去掉结分隔符
                TMP_EditorCoroutine.StartCoroutine(TranslateCoroutine(totalText, srcLang, targetLang, (success, trans, userDt) =>
                {
                    if (success)
                    {
                        ParseAndSaveTransResults(langTexts, targetLang, trans, userDt as int[]);
                    }
                }, totalTextIdx.ToArray()));
            }
        }
        /// <summary>
        /// 解析翻译结果并保存到语言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($"翻译失败, 翻译结果数量和索引数不一致.result count:{resultTexts.Length}, but index count:{resultTextIdxArr.Length}\n 翻译结果:{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));//翻译到目标语言
            strBuilder.AppendFormat("&appid={0}", EditorToolSettings.Instance.BaiduTransAppId);
            strBuilder.AppendFormat("&salt={0}", randomCode);
            strBuilder.AppendFormat("&sign={0}", GenerateBaiduSign(srcText, randomCode));

            //Debug.Log($"发送:{strBuilder}");
            // 发送请求
            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}返回数据解析失败:{e.Message}---------");
                        onComplete?.Invoke(false, null, userData);
                    }
                }
            }

        }

internal class TranslationResult
    {
        public string src;
        public string dst;
    }

工具完整代码参考:GitHub - sunsvip/GF_HybridCLR


http://www.kler.cn/a/6662.html

相关文章:

  • 计算机网络之多路转接epoll
  • gpu硬件架构
  • 【Rust自学】4.4. 引用与借用
  • 【蓝桥杯】43696.小数第n位
  • C语言编程1.27汉诺塔
  • Python国内10个镜像源-地址汇总以及测评
  • 如何使用LaTeX中的命令【博文生成:ChatGPT 3.5】
  • SpringBoot - 集成RestTemplate模板(二) - GET请求
  • 如何恢复回收站被清空的文件
  • Troubleshooting 专题 - 问正确的问题 得到正确的答案
  • fastDDS之Domain
  • 聊聊MyBatis缓存机制(一)
  • java this
  • 【数据结构篇C++实现】- 哈希表
  • impala set设置MEM_LIMIT
  • 【Java】【并发编程】Synchronized
  • 警惕,3月20日WOS目录更新,50本SCI/SSCI被剔除,这个出版社多达18本
  • PX4从放弃到精通(二十七):固定翼姿态控制
  • Java:5大最佳Java库
  • 跳表skiplist
  • Android12 Launcher3客制化:添加非抽屉模式(可动态切换)、图标自动补位功能
  • 使用C#中的Aspose Word 在对Word文档中的书签详细总结(包括增删改查等操作)
  • python实战应用讲解-【numpy专题篇】numpy常见函数使用示例(十)(附python示例代码)
  • 2023年美赛春季赛 赛题浅析
  • 在Linux系统下使用GDB调试C++程序【命令行调试与vscode编译器调试】
  • django前后端不分离——图片验证码,基于Bootstrap3的自定义分页器,Bootstrap模态框无法使用解决方案