|
上篇文章我们介绍了
VUE+.NET应用系统的国际化-多语言词条服务
系统国际化改造整体设计思路如下:
- 提供一个工具,识别前后端代码中的中文,形成多语言词条,按语言、界面、模块统一管理多有的多语言词条
- 提供一个翻译服务,批量翻译多语言词条
- 提供一个词条服务,支持后端代码在运行时根据用户登录的语言,动态获取对应的多语言文本
- 提供前端多语言JS生成服务,按界面动态生成对应的多语言JS文件,方便前端VUE文件使用。
- 提供代码替换工具,将VUE前端代码中的中文替换为$t("词条ID"),后端代码中的中文替换为TermService.Current.GetText("词条ID")
今天,我们在上篇文章的基础上,继续介绍基于Roslyn抽取词条、更新代码。
一、业务背景
先说一下业务背景,后端.NET代码中存在大量的中文提示和异常消息,甚至一些中文返回值文本。
这些中文文字都需要识别出来,抽取为多语言词条,同时将代码替换为调用多语言词条服务获取翻译后的文本。
例如:- private static void CheckMd5(string fileName, string md5Data)
- {
- string md5Str = MD5Service.GetMD5(fileName);
- if (!string.Equals(md5Str, md5Data, StringComparison.OrdinalIgnoreCase))
- {
- throw new CustomException(PackageExceptionConst.FileMd5CheckFailed, "服务包文件MD5校验失败:" + fileName);
- }
- }
复制代码 代码中需要将“服务包文件MD5校验失败”这个文本做多语言改造。
这里通过调用多语言词条服务I18NTermService,根据线程上下文中设置的语言,获取对应的翻译文本。例如以下代码:- var text=T.Core.I18N.Service.TermService.Current.GetTextFormatted("词条ID","默认文本"); <br><br>throw new CustomException(PackageExceptionConst.FileMd5CheckFailed, <strong>text</strong> + fileName);
复制代码 以上背景下,我们准备使用Roslyn技术对代码进行中文扫描,对扫描出来的文本,做词条抽取、代码替换。
二、使用Roslyn技术对代码进行中文扫描
首先,我们先定义好代码中多语言词条的扫描结果类TermScanResult- 1 [Serializable]
- 2 public class TermScanResult
- 3 {
- 4 public Guid Id { get; set; }
- 5 public string OriginalText { get; set; }
- 6
- 7 public string ChineseText { get; set; }
- 8
- 9 <strong>public string SlnName { get; set; }
- 10
- 11 public string ProjectName { get; set; }
- 12
- 13 public string ClassFile { get; set; }
- 14
- 15 public string MethodName { get; set; }
- 16
- 17 public string Code { get; set; }
- </strong>18
- 19 public I18NTerm I18NTerm { get; set; }
- 20
- 21 public string SlnPath { get; set; }
- 22
- 23 public string ClassPath { get; set; }
- 24 28 public string SubSystemCode { get; set; }
- 29
- 30 public override string ToString()
- 31 {
- 32 return Code;
- 33 }
- 34 }
复制代码 上述代码中SubSystemCode是一个业务管理维度。大家忽略即可。
我们会以sln解决方案为单位,扫描代码中的中文文字。
以下是具体的实现代码- public async Task<List<TermScanResult>> CheckSln(string slnPath, System.ComponentModel.BackgroundWorker backgroundWorker, SubSystemFile subSystemFiles, string subSystem)
- {
- var slnFile = new FileInfo(slnPath);
- var results = new List<TermScanResult>();
- MSBuildHelper.RegisterMSBuilder();
- var solution = await MSBuildWorkspace.Create().OpenSolutionAsync(slnPath);
- var subSystemInfo = subSystemFiles?.SubSystemSlnMappings.FirstOrDefault(w => w.SlnName.Select(s => s += ".sln").Contains(slnFile.Name.ToLower()));
- if (solution.Projects != null && solution.Projects.Count() > 0)
- {
- foreach (var project in solution.Projects.ToList())<br> {
- backgroundWorker.ReportProgress(10, $"扫描Project: {project.Name}");
- var documents = project.Documents.Where(x => x.Name.Contains(".cs"));<br>
- if (project.Name.ToLower().Contains("test"))
- {
- continue;
- }
- var codeReplace = new CodeReplace();
- foreach (var document in documents)
- {
- var tree = await document.GetSyntaxTreeAsync();
- var root = tree.GetCompilationUnitRoot();
- if (root.Members == null || root.Members.Count == 0) continue;
- //member
- var classDeclartions = root.DescendantNodes().Where(i => i is ClassDeclarationSyntax);
- foreach (var classDeclare in classDeclartions)
- {
- var programDeclaration = classDeclare as ClassDeclarationSyntax;
- if (programDeclaration == null) continue;
- foreach (var memberDeclarationSyntax in programDeclaration.Members)
- {
- foreach (var item in GetLiteralStringExpression(memberDeclarationSyntax))
- {
- var statementCode = item.Item1;
- foreach (var syntaxNode in item.Item3)
- {
- ExpressionSyntaxParser expressionSyntaxParser = new ExpressionSyntaxParser();
- var text = "";
- var expressionSyntax = expressionSyntaxParser
- .GetExpressionSyntaxVerifyRule(syntaxNode as ExpressionSyntax, statementCode);
- if (expressionSyntax != null)
- {
- // 排除
- if (expressionSyntaxParser.IsExcludeCaller(expressionSyntax, statementCode))
- {
- continue;
- }
- text = expressionSyntaxParser.GetExpressionSyntaxOriginalText(expressionSyntax, statementCode);
- if (expressionSyntax is Microsoft.CodeAnalysis.CSharp.Syntax.InterpolatedStringExpressionSyntax)
- {
- text = expressionSyntaxParser.GetExpressionSyntaxOriginalText(expressionSyntax, statementCode);
- if (expressionSyntax is Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax)
- {
- if (!expressionSyntax.IsKind(SyntaxKind.StringLiteralExpression))
- {
- continue;
- }
- text = expressionSyntax.NormalizeWhitespace().ToString();
- }
- }
- }
- if (CheckChinese(text) == false) continue;
- if (string.IsNullOrWhiteSpace(text)) continue;
- if (string.IsNullOrWhiteSpace(text.Replace(""", "").Trim())) continue;
- results.Add(new TermScanResult()
- {
- Id = Guid.NewGuid(),
- ClassPath = programDeclaration.SyntaxTree.FilePath,
- SlnPath = slnPath,
- OriginalText = text.Replace(""", "").Trim(),
- ChineseText = text,
- SlnName = slnFile.Name,
- ProjectName = project.Name,
- ClassFile = programDeclaration.Identifier.Text,
- MethodName = item.Item2,
- Code = statementCode,
- SubSystemCode = subSystem
- });
- }
- }
- }
- }
- }
- }
- }
- return results;
- }
复制代码 上述代码中,我们先使用MSBuilder编译,构建 sln解决方案- MSBuildHelper.RegisterMSBuilder();
- var solution = await MSBuildWorkspace.Create().OpenSolutionAsync(slnPath);<br><br>
复制代码 然后遍历solution下的各个Project中的class类- foreach (var project in solution.Projects.ToList())<br>
复制代码- var documents = project.Documents.Where(x => x.Name.Contains(".cs"));<br>
复制代码 然后遍历类中声明、成员、方法中的每行代码,通过正则表达式识别是否有中文字符- public static bool CheckChinese(string strZh)
- {
- Regex re = new Regex(@"[\u4e00-\u9fa5]+");
- if (re.IsMatch(strZh))
- {
- return true;
- }
- return false;
- }
复制代码 如果存在中文字符,作为扫描后的结果,识别为多语言词条- results.Add(new TermScanResult()
- {
- Id = Guid.NewGuid(),
- ClassPath = programDeclaration.SyntaxTree.FilePath,
- SlnPath = slnPath,
- OriginalText = text.Replace(""", "").Trim(),
- ChineseText = text,
- SlnName = slnFile.Name,
- ProjectName = project.Name,
- ClassFile = programDeclaration.Identifier.Text,
- MethodName = item.Item2,
- Code = statementCode, //管理维度
- SubSystemCode = subSystem //管理维度
复制代码 TermScanResult中没有对词条属性赋值。
public I18NTerm I18NTerm { get; set; }
下一篇文章的代码中,我们会通过多语言翻译服务,将翻译后的文本放到I18NTerm 属性中,作为多语言词条。
三、代码替换
代码替换这块逻辑中,我们设计了一个类SourceWeaver,对上一步的代码扫描结果,进行代码替换- <strong>CodeScanReplace这个方法中完成了代码的二次扫描和替换</strong>
复制代码- /// <summary>
- /// 源代码替换服务
- /// </summary>
- public class SourceWeaver
- {
- List<CommonTermDto> commonTerms = new List<CommonTermDto>();
- List<CommonTermDto> commSubTerms = new List<CommonTermDto>();
- public SourceWeaver()
- {
- commonTerms = JsonConvert.DeserializeObject<List<CommonTermDto>>(File.ReadAllText("comm_data.json"));
- commSubTerms = JsonConvert.DeserializeObject<List<CommonTermDto>>(File.ReadAllText("comm_sub_data.json"));
- }
- public async Task CodeScanReplace(Tuple<List<I18NTerm>, List<TermScanResult>> result, System.ComponentModel.BackgroundWorker backgroundWorker)
- {
- try
- {
- backgroundWorker.ReportProgress(0, "正在对代码进行替换.");
- var termScanResultGroupBy = result.Item2.GroupBy(g => g.SlnName);
- foreach (var termScanResult in termScanResultGroupBy)
- {
- var termScan = termScanResult.FirstOrDefault();
- MSBuildHelper.RegisterMSBuilder();
- var solution = await MSBuildWorkspace.Create().OpenSolutionAsync(termScan.SlnPath).ConfigureAwait(false);
- if (solution.Projects.Any())
- {
- foreach (var project in solution.Projects.ToList())
- {
- if (project.Name.ToLower().Contains("test"))
- {
- continue;
- }
- var projectTermScanResults = result.Item2.Where(f => f.ProjectName == project.Name);
- var documents = project.Documents.Where(x =>
- {
- return x.Name.Contains(".cs") && projectTermScanResults.Any(f => $"{f.ClassPath}" == x.FilePath);
- });
- foreach (var document in documents)
- {
- var tree = await document.GetSyntaxTreeAsync().ConfigureAwait(false);
- var root = tree.GetCompilationUnitRoot();
- if (root.Members.Count == 0) continue;
- var classDeclartions = root.DescendantNodes()
- .Where(i => i is ClassDeclarationSyntax);
- List<MemberDeclarationSyntax> syntaxNodes = new List<MemberDeclarationSyntax>();
- foreach (var classDeclare in classDeclartions)
- {
- if (!(classDeclare is ClassDeclarationSyntax programDeclaration)) continue;
- var className = programDeclaration.Identifier.Text;
- foreach (var method in programDeclaration.Members)
- {
- if (method is ConstructorDeclarationSyntax)
- {
- syntaxNodes.Add((ConstructorDeclarationSyntax)method);
- }
- else if (method is MethodDeclarationSyntax)
- {
- syntaxNodes.Add((MethodDeclarationSyntax)method);
- }
- else if (method is PropertyDeclarationSyntax)
- {
- syntaxNodes.Add(method);
- }
- else if (method is FieldDeclarationSyntax)
- {
- // 注:常量不支持
- syntaxNodes.Add(method);
- }
- }
- }
- var terms = termScanResult.Where(
- f => f.ProjectName == document.Project.Name && f.ClassPath == document.FilePath).ToList();
- backgroundWorker.ReportProgress(10, $"正在检查{document.FilePath}文件.");
- ReplaceNodesAndSave(root, syntaxNodes, terms, result, backgroundWorker, document.Name);
- }
- }
- }
- }
- }
- catch (Exception ex)
- {
- LogUtils.LogError(string.Format("异常类型:{0}\r\n异常消息:{1}\r\n异常信息:{2}\r\n",
- ex.GetType().Name, ex.Message, ex.StackTrace));
- backgroundWorker.ReportProgress(0, ex.Message);
- }
- }
- public async void ReplaceNodesAndSave(SyntaxNode classSyntaxNode, List<MemberDeclarationSyntax> syntaxNodes, IEnumerable<TermScanResult> terms, Tuple<List<I18NTerm>, List<TermScanResult>> result,
- System.ComponentModel.BackgroundWorker backgroundWorker, string className)
- {
- {//check pro是否存在词条
- if (AppConfig.Instance.IsCheckTermPro)
- {
- backgroundWorker.ReportProgress(15, $"词条验证中.");
- var termsCodes = terms.Select(f => f.I18NTerm.Code).ToList();
- var size = 100;
- var p = (result.Item2.Count() + size - 1) / size;
- using DBHelper dBHelper = new DBHelper();
- List<I18NTerm> items = new List<I18NTerm>();
- for (int i = 0; i < p; i++)
- {
- var list = termsCodes
- .Skip(i * size).Take(size);
- Thread.Sleep(10);
- var segmentItems = await dBHelper.GetTermsAsync(termsCodes).ConfigureAwait(false);
- items.AddRange(segmentItems);
- }
- List<TermScanResult> termScans = new List<TermScanResult>();
- foreach (var term in terms)
- {
- if (items.Any(f => f.Code == term.I18NTerm.Code))
- {
- termScans.Add(term);
- }
- else
- {
- backgroundWorker.ReportProgress(20, $"词条{term.OriginalText}未导入到词条库,该词条将忽略替换.");
- }
- }
- terms = termScans;
- }
- }
- var newclassDeclare = classSyntaxNode;
- newclassDeclare = classSyntaxNode.ReplaceNodes(syntaxNodes,
- (methodDeclaration, _) =>
- {
- MemberDeclarationSyntax newMemberDeclarationSyntax = methodDeclaration;
- var className = ((ClassDeclarationSyntax)newMemberDeclarationSyntax.Parent).Identifier.Text;
- List<StatementSyntax> statementSyntaxes = new List<StatementSyntax>();
- switch (newMemberDeclarationSyntax)
- {
- case ConstructorDeclarationSyntax:
- {
- var blockSyntax = (newMemberDeclarationSyntax as ConstructorDeclarationSyntax).NormalizeWhitespace().Body;
- if (blockSyntax == null)
- {
- break;
- }
- foreach (var statement in blockSyntax.Statements)
- {
- var nodeStatement = statement.DescendantNodes();
- statementSyntaxes.Add(new CodeReplace().ReplaceStatementNodes(statement,
- new ExpressionSyntaxParser().LiteralStringExpression(nodeStatement), terms, commonTerms, commSubTerms));
- }
- break;
- }
- case MethodDeclarationSyntax:
- {
- var blockSyntax = (methodDeclaration as MethodDeclarationSyntax).NormalizeWhitespace().Body;
- if (blockSyntax == null)
- {
- break;
- }
- foreach (var statement in blockSyntax.Statements)
- {
- var nodeStatement = statement.DescendantNodes();
- statementSyntaxes.Add(new CodeReplace().ReplaceStatementNodes(statement,
- new ExpressionSyntaxParser().LiteralStringExpression(nodeStatement), terms, commonTerms, commSubTerms));
- }
- break;
- }
- case PropertyDeclarationSyntax:
- {
- var propertyDeclarationSyntax = newMemberDeclarationSyntax as PropertyDeclarationSyntax;
- var nodeStatement = propertyDeclarationSyntax.DescendantNodes();
- return new CodeReplace().ReplacePropertyNodes(newMemberDeclarationSyntax as PropertyDeclarationSyntax,
- new ExpressionSyntaxParser().LiteralStringExpression(nodeStatement), terms, commonTerms, commSubTerms);
- }
- case FieldDeclarationSyntax:
- {
- var fieldDeclarationSyntax = newMemberDeclarationSyntax as FieldDeclarationSyntax;
- var nodeStatement = fieldDeclarationSyntax.DescendantNodes();
- return new CodeReplace().ReplaceFiledNodes(fieldDeclarationSyntax,
- new ExpressionSyntaxParser().LiteralStringExpression(nodeStatement), terms, commonTerms, commSubTerms);
- }
- }
- backgroundWorker.ReportProgress(50, $"解析并对类文件{className}中的方法做语句替换.");
- // 替换方法内部
- if (newMemberDeclarationSyntax is MethodDeclarationSyntax)
- {
- return new CodeReplace().ReplaceMethodDeclaration(newMemberDeclarationSyntax as MethodDeclarationSyntax, statementSyntaxes);
- }
- else if (newMemberDeclarationSyntax is ConstructorDeclarationSyntax)
- {
- return new CodeReplace().ReplaceConstructorDeclaration(newMemberDeclarationSyntax as ConstructorDeclarationSyntax, statementSyntaxes);
- }
- return newMemberDeclarationSyntax;
- });
- var sourceStr = newclassDeclare.NormalizeWhitespace().GetText().ToString();
- File.WriteAllText(newclassDeclare.SyntaxTree.FilePath, sourceStr);
- backgroundWorker.ReportProgress(100, $"完成{className}的替换.");
- }
- }
复制代码 关键的代码语义替换的实现代码:- public StatementSyntax ReplaceStatementNodes(StatementSyntax statement, List<ExpressionSyntax> expressionSyntaxes, IEnumerable<TermScanResult> terms
- , List<CommonTermDto> commonTerms, List<CommonTermDto> commSubTerms)
- {
- var statementSyntax = statement.ReplaceNodes(expressionSyntaxes, (syntaxNode, _) =>
- {
- var statementStr = statement.NormalizeWhitespace().ToString();
- var argumentLists = statement.DescendantNodes().
- OfType<InvocationExpressionSyntax>();
- ExpressionSyntaxParser expressionSyntaxParser = new ExpressionSyntaxParser();
- return expressionSyntaxParser.ExpressionSyntaxTermReplace(syntaxNode, statementStr, terms, commonTerms, commSubTerms);
- });
- return statementSyntax;
- }
复制代码 这里,我们抽象了一个ExpressionSyntaxParser 类,负责替换代码:- T.Core.I18N.Service.TermService.Current.GetTextFormatted
复制代码- public ExpressionSyntax ExpressionSyntaxTermReplace(ExpressionSyntax syntaxNode, string statementStr, IEnumerable<TermScanResult> terms
- , List<CommonTermDto> commonTerms, List<CommonTermDto> commSubTerms)
- {
- var expressionSyntax = GetExpressionSyntaxVerifyRule(syntaxNode, statementStr);
- var originalText = GetExpressionSyntaxOriginalText(expressionSyntax, statementStr);
- var I18Expr = "";
- var interpolationSyntaxes = syntaxNode.DescendantNodes().OfType<InterpolationSyntax>();
- var term = terms.FirstOrDefault(i => i.ChineseText == originalText);
- if (term == null)
- return syntaxNode;
- string termcode = term.I18NTerm.Code;
- if (syntaxNode is InterpolatedStringExpressionSyntax)
- {
- if (interpolationSyntaxes.Count() > 0)
- {
- var parms = "";
- foreach (var item in interpolationSyntaxes)
- {
- parms += $",{item.ToString().TrimStart('{').TrimEnd('}')}";
- }
- I18Expr = "$"{T.Core.I18N.Service.TermService.Current.GetTextFormatted("" + termcode + "", " + originalText + parms + ")}"";
- var token1 = SyntaxFactory.Token(default, SyntaxKind.StringLiteralToken, I18Expr, "", default);
- return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, token1);
- }
- else
- {
- var startToken = SyntaxFactory.Token(SyntaxKind.InterpolatedStringStartToken);
- if ((syntaxNode as InterpolatedStringExpressionSyntax).StringStartToken.Value == startToken.Value)
- {
- // 如果本身有"$"
- I18Expr = "$"{T.Core.I18N.Service.TermService.Current.GetText("" + termcode + ""," + originalText + ")}";
- }
- else
- {
- // 如果没有"$"
- I18Expr = "$"{T.Core.I18N.Service.TermService.Current.GetText("" + termcode + "",\\teld"" + originalText + "")}";
- I18Expr = I18Expr.Replace("\\teld", "$");
- }
- }
- }
- else
- {
- I18Expr = "$"{T.Core.I18N.Service.TermService.Current.GetText("" + termcode + ""," + originalText + ")}";
- }
- var token = SyntaxFactory.Token(default(SyntaxTriviaList), SyntaxKind.InterpolatedVerbatimStringStartToken, I18Expr, "$"", default(SyntaxTriviaList));
- var literalExpressionSyntax = SyntaxFactory.InterpolatedStringExpression(token);
- return literalExpressionSyntax;
- }
复制代码- T.Core.I18N.Service.TermService这个就是多语言词条服务类,这个类中提供了一个GetText的方法,通过词条编号,获取多语言文本。<br><br>代码完成替换后,打开VS,对工程引用多语言词条服务的Nuget包/dll,重新编译代码,手工校对替换后的代码即可。<br>以上是.NET应用系统的国际化-基于Roslyn抽取词条、更新代码的分享。<br><br><br>周国庆<br>2023/3/19<br><br><br><br>
复制代码 [code][/code]
来源:https://www.cnblogs.com/tianqing/archive/2023/03/19/17232474.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
|