Recently I ran into the problem of localizing my application and thought about solving it.
The first to come to mind is the most obvious and simplest way - a dictionary, but it was immediately rejected, since there is no way to check whether a string exists in the dictionary at the time of compilation.
A much more elegant solution is to create a class hierarchy like this:
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;}
}
Then you can simply serialize / deserialize xml'ku.
There is only one "but". It can take a long time to create such a class hierarchy, especially if the project is large. So why not generate it from an xml file? This is what we will do.
Let's get started
First, let's create a project for our generator and add the necessary packages to it.
dotnet new classlib -o LocalizationSourceGenerator -f netstandard2.0 dotnet add package Microsoft.CodeAnalysis.CSharp dotnet add package Microsoft.CodeAnalysis.Analyzers
Important! The target framework of the project must be netstandard2.0
Next, let's add the class of our generator
It must implement the ISourceGenerator interface and be marked with the Generator attribute
Next, let's add the ILocalizationGenerator interface and the XmlLocalizationGenerator class that implements it:
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());
}
}
There is little left to do. It is necessary to implement the class of the generator itself
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)
{
// ,
//
}
}
That's all! Now you just need to check our generator. To do this, create a console application project
dotnet new console -o Test
Add the template and localization file
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>
Let's edit the project file
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>
And the program code
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) - generator repository