Implémentation de la localisation avec des générateurs de code source

Récemment, j'ai rencontré le problème de la localisation de mon application et j'ai pensé à le résoudre.





Le premier qui me vient à l'esprit est le moyen le plus évident et le plus simple - un dictionnaire, mais il a été immédiatement rejeté, car il n'y a aucun moyen de vérifier si une chaîne existe dans le dictionnaire au moment de la compilation.





Une solution beaucoup plus élégante consiste à créer une hiérarchie de classes comme celle-ci:





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



Ensuite, vous pouvez simplement sérialiser / désérialiser xml'ku.





Il n'y a qu'un seul «mais». La création de cette hiérarchie de classes peut prendre beaucoup de temps, surtout si le projet est volumineux. Alors pourquoi ne pas le générer à partir d'un fichier xml? C'est ce que nous allons faire.





Commençons

Commençons par créer un projet pour notre générateur et y ajouter les packages nécessaires.





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



Important! Le cadre cible du projet doit être netstandard2.0





Ensuite, ajoutons la classe de notre générateur





Il doit implémenter l'interface ISourceGenerator et être marqué avec l'attribut Generator





Ensuite, ajoutons l'interface ILocalizationGenerator et la classe XmlLocalizationGenerator qui l'implémente:





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



Il reste peu à faire. Il est nécessaire d'implémenter la classe du générateur lui-même





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)
  {
  	//       ,
    //   
  }
}
      
      



C'est tout! Il ne vous reste plus qu'à vérifier notre générateur. Pour ce faire, créez un projet d'application console





dotnet new console -o Test
      
      



Ajouter le modèle et le fichier de localisation





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>
      
      



Éditons le fichier du projet





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>
      
      



Et le code du programme





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) - référentiel de générateurs








All Articles