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

这篇具有很好参考价值的文章主要介绍了【Unity编辑器扩展】语言国际化工具,生成多语言Excel自动翻译并导出多语言表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

工具效果如图:

unity 多语言插件,Unity,Unity游戏框架,unity,游戏引擎,百度翻译,接入百度翻译

 多语言是个非常简单且常用的功能。但是重复工作量大,程序手动把多语言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的第一列生成了【锁定】勾选框方便策划操作。

unity 多语言插件,Unity,Unity游戏框架,unity,游戏引擎,百度翻译,接入百度翻译
多语言”母语“
unity 多语言插件,Unity,Unity游戏框架,unity,游戏引擎,百度翻译,接入百度翻译
基于”母语“自动生成/翻译的其它语言

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

unity 多语言插件,Unity,Unity游戏框架,unity,游戏引擎,百度翻译,接入百度翻译

unity 多语言插件,Unity,Unity游戏框架,unity,游戏引擎,百度翻译,接入百度翻译
一键生成的多语言Excel
unity 多语言插件,Unity,Unity游戏框架,unity,游戏引擎,百度翻译,接入百度翻译
自动导出多语言Excel为json文件

功能实现:

1. 一键扫描多语言文本:

①扫描Prefab资源上的多语言文本:

GameFramework框架提供了UIStringKey专门用来填写多语言文本Key, 所以只需要从所有Prefab上获取UIStringKey脚本上填写的Key即可。

unity 多语言插件,Unity,Unity游戏框架,unity,游戏引擎,百度翻译,接入百度翻译

 扫描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“标识,程序就自动扫描添加标识的列:

unity 多语言插件,Unity,Unity游戏框架,unity,游戏引擎,百度翻译,接入百度翻译

 扫描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库:

unity 多语言插件,Unity,Unity游戏框架,unity,游戏引擎,百度翻译,接入百度翻译

 命令行程序代码:

其中命令行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次。

unity 多语言插件,Unity,Unity游戏框架,unity,游戏引擎,百度翻译,接入百度翻译

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

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

unity 多语言插件,Unity,Unity游戏框架,unity,游戏引擎,百度翻译,接入百度翻译

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

百度翻译上行字段:

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

接入百度翻译示例代码:文章来源地址https://www.toymoban.com/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; //如果长度超了下个请求接着这行
                            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

到了这里,关于【Unity编辑器扩展】语言国际化工具,生成多语言Excel自动翻译并导出多语言表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Unity编辑器扩展之GenericMenu菜单扩展

    内容将会持续更新,有错误的地方欢迎指正,谢谢!   Unity编辑器扩展之GenericMenu自定义菜单       TechX 坚持将创新的科技带给世界! 拥有更好的学习体验 —— 不断努力,不断进步,不断探索 TechX —— 心探索、心进取! 助力快速掌握 GenericMenu 菜单扩展 为初学者节省宝贵的

    2024年02月01日
    浏览(75)
  • Unity 扩展自定义编辑器窗口

    在Assets文件夹路径下任意位置创建Editor文件夹,将扩展编辑器的代码放在Editor文件夹下 代码中首先引用命名空间 然后将创建的类继承自EditorWindow 然后通过扩展编辑器菜单功能调用创建窗口的方法 要注意方法中泛型参数需要传入的是自己代码的类,这个功能是根据后面OnGUI方

    2024年04月27日
    浏览(56)
  • Unity 编辑器扩展之 Attribute

    Unity内置属性[Attribute]是一种类似修饰功能的标签。可以对OnSceneGUI,InspectorGUI,MenuGUI,WindowGUI等实现各种各样的GUI扩展。用户只要添加上特性标签,就能够自由的使用这些扩展功能。下面列出一些常用的标签: 隐藏属性在Inspector面板上的显示。在继承了MonoBehaviour的类中,用

    2023年04月09日
    浏览(70)
  • Unity编辑扩展:功能篇之Json数据编辑器

    前言 编辑器扩展算是比较纯粹的功能开发,基本没有什么理论知识,都是一些 Unity 相关接口的使用与数据类型的设计操作等。在本篇文章主要的文字描述基本都是在做代码解释,为了使内容接受度更高,我会尽量描述到代码结构中的每个细节。如果有对此不太了解又很感兴

    2024年02月06日
    浏览(103)
  • 【Unity编辑器扩展】| Inspector监视器面板扩展

    前言 前面我们介绍了Unity中编辑器扩展的一些基本概念及基础知识,还有编辑器扩展中用到的相关特性Attribute介绍。 后面就来针对Uniity编辑器扩展中比较常用的模块进行学习介绍。 本文就来详细介绍一下Unity编辑器扩展中关于 Inspector面板 扩展功能学习。

    2024年02月08日
    浏览(65)
  • 盘点Unity几款编辑器扩展工具

    unity 编辑器一个不容忽视的强大之处就是非常易于自定义扩展工具,来满足各种各样的美术、策划及程序上的需求。今天为大家介绍Asset Store资源商店中几款实用的编辑器扩展工具,帮助大家直接在Unity编辑器中完成3D建模与调整工作,免去与其它软件进行数据转换的过程,从

    2024年04月09日
    浏览(48)
  • Unity编辑器扩展-第四集-获取物体的方法

    第三集链接:Unity编辑器扩展-第三集-添加按钮到组件菜单并且重置组件_菌菌巧乐兹的博客-CSDN博客  一、本节目标+效果展示 1.改选中单个物体的名字 2.改选中所有物体的名字  3.选中了所有的物体,但只改第一层物体的名称  4.来个有用的(选中的所有物体,按顺序起名)

    2024年02月14日
    浏览(58)
  • 【Unity UIToolkit】UIBuilder基础教程-制作简易的对话系统编辑器 3步教你玩转Unity编辑器扩展工具

    随着Unity开发的深入,基本的Unity编辑器界面并不能满足大部分玩家高阶开发的要求。为了提高开发的效率,有针对性的定制化扩展编辑器界面是提高开发效率的不错选择。 今天就给大家带来Unity官方提高的编辑器扩展工具UIToolkit(集成了UIBuilder和UI Debugger等插件)的使用教程。

    2024年02月04日
    浏览(72)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包