[.NET, C#] Реализация локализации при помощи Source code generators
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Недавно я столкнулся с проблемой локализации своего приложения и задумался над её решением.Первым на ум приходить самый очевидный и простой способ - словарь, но он был тут же отвергнут, так как никак не нельзя проверить существует ли строка в словаре на момент компиляции.Куда более изящное решение - создать иерархию классов типа этой:
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'ку.Только есть одно "но". На создание такой иерархии классов может уйти достаточно времени, особенно если проект большой. Так почему бы не генерировать ее из xml файла? Этим мы и займемся. ПриступимДля начала создадим проект нашего генератора и добавим в него необходимые пакеты
dotnet new classlib -o LocalizationSourceGenerator -f netstandard2.0
dotnet add package Microsoft.CodeAnalysis.CSharp
dotnet add package Microsoft.CodeAnalysis.Analyzers
Важно! Target framework проекта обязательно должен быть 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) - репозиторий генератора
===========
Источник:
habr.com
===========
Похожие новости:
- [.NET] A little life hack when you work with Azure Service Bus and ASP.NET Core
- [.NET, C#] SmartTraits или добавляем «множественное» наследование в C#
- [Информационная безопасность, Open source, Сетевые технологии, Mesh-сети] Криптографическое образование адреса IPv6 в Yggdrasil
- [Open source, Программирование микроконтроллеров, DIY или Сделай сам] Поговорим с мышами? Или Soft USB HOST на Esp32
- [.NET, C#, ООП, Промышленное программирование] Lazy Properties Are Good. That Is How You Are to Use Them
- [Open source, *nix] FOSS News №59 – дайджест материалов о свободном и открытом ПО за 1-7 марта 2021 года
- [Open source, Разработка под Android, Kotlin] Reaction — обработка результатов методов в Kotlin
- [.NET, C#] Реализуем кооперативную многозадачность на C#
- [Open source, Разработка игр, Графический дизайн, Дизайн игр, DIY или Сделай сам] О ходе создания русской народной игры «Колобок» в феврале
- [Open source, Java, OpenStreetMap, Геоинформационные сервисы] Как использовать GraphHopper для построения пешеходных маршрутов по собственным правилам
Теги для поиска: #_.net, #_c#, #_sourcegenerator, #_source, #_csharp, #_.net, #_c#
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:21
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Недавно я столкнулся с проблемой локализации своего приложения и задумался над её решением.Первым на ум приходить самый очевидный и простой способ - словарь, но он был тут же отвергнут, так как никак не нельзя проверить существует ли строка в словаре на момент компиляции.Куда более изящное решение - создать иерархию классов типа этой: 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;} } dotnet new classlib -o LocalizationSourceGenerator -f netstandard2.0
dotnet add package Microsoft.CodeAnalysis.CSharp dotnet add package Microsoft.CodeAnalysis.Analyzers public interface ILocalizationGenerator
{ string GenerateLocalization(string template); } 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()); } } [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
<Locale namespace="Program.Localization">
<UI> <Buttons> <SendButton/> </Buttons> </UI> <Name/> </Locale> <Locale>
<UI> <Buttons> <SendButton>Отправить</SendButton> </Buttons> </UI> <Name>Русский</Name> </Locale> <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> 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); } } } =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:21
Часовой пояс: UTC + 5