ソースコードジェネレーターによるローカリゼーションの実装

最近、アプリケーションをローカライズするという問題に遭遇し、それを解決することを考えました。





最初に頭に浮かぶのは、最も明白で最も単純な方法である辞書ですが、コンパイル時に文字列が辞書に存在するかどうかを確認する方法がないため、すぐに拒否されました。





より洗練された解決策は、次のようなクラス階層を作成することです。





public class Locale 
{
	public string Name {get; set;}
  public UI UI {get; set;}
}
public class UI 
{
	public Buttons Buttons {get; set;}
	public Messages Messages {get; set;}
}
public class Buttons 
{
	public string CloseButton {get; set;}
  public string DeleteButton {get; set;}
}
public class Messages 
{
	public string ErrorMessage {get; set;}
}
      
      



次に、xml'kuを単純にシリアル化/逆シリアル化できます。





「しかし」は1つだけです。特にプロジェクトが大きい場合、このクラス階層の作成には長い時間がかかる可能性があります。では、xmlファイルから生成してみませんか?これが私たちがすることです。





始めましょう

まず、ジェネレーターのプロジェクトを作成し、それに必要なパッケージを追加しましょう。





dotnet new classlib -o LocalizationSourceGenerator -f netstandard2.0
dotnet add package Microsoft.CodeAnalysis.CSharp
dotnet add package Microsoft.CodeAnalysis.Analyzers
      
      



重要!プロジェクトのターゲットフレームワークはnetstandard2.0である必要があります





次に、ジェネレータのクラスを追加しましょう





ISourceGeneratorインターフェースを実装し、Generator属性でマークする必要があります





次に、ILocalizationGeneratorインターフェイスとそれを実装するXmlLocalizationGeneratorクラスを追加しましょう。





ILocalizationGenerator.cs
public interface ILocalizationGenerator
{
	string GenerateLocalization(string template);
}
      
      



XmlLocalizationGenerator.cs
public class XmlLocalizationGenerator : ILocalizationGenerator
{
	//  
	private List<string> classes = new List<string>();
  
  public string GenerateLocalization(string template)
  {
  	//  xml    
    XmlDocument document = new XmlDocument();
    document.LoadXml(template);
    var root = document.DocumentElement;
    //      
    string namespaceName = root.HasAttribute("namespace") ? 
    											 root.GetAttribute("namespace") : 
                           "Localization";
    GenClass(root); //  
    var sb = new StringBuilder();
   	sb.AppendLine($"namespace {namespaceName}\n{{");
		//     
	  foreach(var item in classes) 
	  {
			sb.AppendLine(item);
		}
    sb.Append('}');
  	return sb.ToString();
  }
  public void GenClass(XmlElement element)
  {
  	var sb = new StringBuilder();
    sb.Append($"public class {element.Name}");
    sb.AppendLine("{");
    //       
    foreach (XmlNode item in element.ChildNodes)
    {
    	//       
      //     -  -
    	if (item.ChildNodes.Count == 0 
      || (item.ChildNodes.Count == 1 
      && item.FirstChild.NodeType==XmlNodeType.Text))
      {
      	sb.AppendLine($"public string {item.Name} {{get; set;}}");
      }
      else
      {
      	//    
        //   
      	sb.AppendLine($"public {item.Name} {item.Name} {{get; set;}}");
        GenClass(item); 
      }
    }
    sb.AppendLine("}");
    classes.Add(sb.ToString());
  }
}
      
      



やるべきことはほとんど残っていません。ジェネレータ自体のクラスを実装する必要があります





LocalizationSourceGenerator.cs
[Generator]
public class LocalizationSourceGenerator : ISourceGenerator
{
	public void Execute(GeneratorExecutionContext context)
 	{
  	//      
  	var templateFile = context
    									 .AdditionalFiles
                       .FirstOrDefault(
                       		x => Path.GetExtension(x.Path) == ".xml")
                          ?.Path;
    if (!string.IsNullOrWhiteSpace(templateFile))
    {
    	ILocalizationGenerator generator = new XmlLocalizationGenerator();
      var s = generator.GenerateLocalization(File.ReadAllText(templateFile));
      //    ""
      //      
      context.AddSource("Localization",s );
    }
  }

  public void Initialize(GeneratorInitializationContext context)
  {
  	//       ,
    //   
  }
}
      
      



それで全部です!今、あなたは私たちのジェネレータをチェックする必要があります。これを行うには、コンソールアプリケーションプロジェクトを作成します





dotnet new console -o Test
      
      



テンプレートとローカリゼーションファイルを追加します





template.xml
<Locale namespace="Program.Localization">
	<UI>
		<Buttons>
			<SendButton/> 
		</Buttons> 
	</UI> 
	<Name/>
</Locale>
      
      



ru.xml
<Locale>
	<UI>
		<Buttons>
			<SendButton></SendButton>
		</Buttons>
	</UI>
	<Name></Name>
</Locale>
      
      



プロジェクトファイルを編集しましょう





Test.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>  
  </PropertyGroup>  
  <ItemGroup>
    <ProjectReference 
					ReferenceOutputAssembly="false"
					OutputItemType="Analyzer" 
					Include="----" />
		<!--      -->
    <AdditionalFiles Include="template.xml"/>  
  </ItemGroup>
</Project>
      
      



そしてプログラムコード





Program.cs
using System;
using System.IO; 
using System.Xml.Serialization;   
using Program.Localization; //  
namespace Program
{ 
	public class Program
	{
		public static void Main()
		{ 
      // Locale    
			var xs = new XmlSerializer(typeof(Locale));
			var locale = xs.Deserialize(File.OpenRead("ru.xml")) as Locale;
			Console.WriteLine(locale.Name);
			Console.WriteLine(locale.UI.Buttons.SendButton);
			
		}
		
	}
}
      
      



Dotnet-And-Happiness / LocalizationSourceGenerator(github.com) -ジェネレーターリポジトリ








All Articles