Средство "Генератор ресурсов"
Автор: Абхишек Сюр (Abhishek Sur)
В моей последней работе мне понадобилось реализовать возможность поддержки нескольких языков. Корпорация Майкрософт предложила несколько приемов решения этой задачи, но я придумал собственную логику для достижения той же цели. Так как языки — это фактически строковые данные, которые мне нужно предоставить приложениям WPF, можно легко создать отдельные файлы ресурсов для данных, связанных с конкретным языком.
Теперь при загрузке программы можно легко добавить нужный язык в текущий объект CultureInfo. Я ввел этот прием в статье: Простейший способ реализовать многоязыковое приложение.
Позднее в моей статье Подключаемый ресурс для словаря ресурсов WPF я предложил возможный способ подключения — в ресурсах для внешних сборок.
Подводя итог, когда бы мне ни понадобилось использовать строку, мне будет нужно изменить в файле ресурсов строковый ресурс с соответствующим ключом. Нам нужно создать новые файлы ресурсов для каждого языка, а также нужно сопоставить каждому ключу соответствующие данные.
Поэтому вместо Text="My Custom text" я буду писать что-то вроде Text="{DynamicResource rKeyCustomText}", чтобы ключ rKeyCustomText был заменен соответствующим текстом во время выполнения проекта, таким образом, обеспечивая поддержку языка с помощью строковых ресурсов. Для этого нужно создать схему, подобную следующей:
<ResourceDictionary
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="rKeyCustomText" >This is my custom Text</system:String>
</ResourceDictionary>
Для французского языка нам понадобится написать то же самое следующим образом :
<ResourceDictionary xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="rKeyCustomText">C'est mon texte personnalisé</system:String>
</ResourceDictionary>
Поэтому проблема состоит в том, что после объявления ключа ресурса нужно мгновенно преобразовать соответствующую строку во всех файлах ресурсов. Эта работа мне кажется очень неинтересной и трудоемкой. Поэтому я задумался о создании средства, которое помогло бы в этом. Обсудим, как можно использовать это средство для автоматического построения файлов ресурсов.
.jpg)
Чтобы создать это приложение, я использовал следующие технологии:
- .NET (конечно же);
- WPF для пользовательского интерфейса;
- службы Bing для перевода;
- потоки для выполнения фоновой работы.
Поэтому пользовательский интерфейс выглядит просто и содержит только два текстовых поля, показывающих исходный и конечный файлы. Рассмотрим нужную последовательную шагов:
- Шаг 1. Создание файла для ресурсов, точно соответствующего указанной мной схеме. Можно вставить тег system:strings для любого языка.
- Шаг 2. Откройте консоль и выберите только что созданный файл.
- Шаг 3. Создание файла с помощью кнопки Destination (Назначение).
- Шаг 4. Выберите соответствующие исходный и конечный языки.
- Шаг 5. Нажмите кнопку Convert (Преобразовать), чтобы создать нужный файл.
После этого, как можно видеть, будет создан новый файл с той же схемой.
Реализация
Реализация очень проста. Для создания результата я использовал классы LINQ для XML. Взгляните на код:
public void CreateResourceDictionary(string targetfile, string destinationfile, DoWorkEventArgs e)
{
DateTime startTime = DateTime.Now;
e.Result = "";
XDocument document = XDocument.Load(targetfile);
oWorker.ReportProgress(2, "Initializing...");
foreach (XElement elem in document.Descendants("{clr-namespace:System;assembly=mscorlib}String"))
{
XAttribute attribute = elem.Attribute("{https://schemas.microsoft.com/winfx/2006/xaml}Key");
elememnts.Add(attribute.Value, elem.Value);
}
oWorker.ReportProgress(4, "Target File Read Successfully.");
XDocument targetDocument = null;
XNamespace xaml = "https://schemas.microsoft.com/winfx/2006/xaml/presentation";
XNamespace x = "https://schemas.microsoft.com/winfx/2006/xaml";
XNamespace system = "clr-namespace:System;assembly=mscorlib";
XElement root = new XElement(xaml + "ResourceDictionary",
new XAttribute("xmlns", "https://schemas.microsoft.com/winfx/2006/xaml/presentation"),
new XAttribute(XNamespace.Xmlns + "x", "https://schemas.microsoft.com/winfx/2006/xaml"),
new XAttribute(XNamespace.Xmlns + "system", "clr-namespace:System;assembly=mscorlib"));
oWorker.ReportProgress(5, "Creating Target File.");
int elements = elememnts.Count;
int i = 1;
foreach (string key in elememnts.Keys)
{
try
{
using (LiveSearchPortTypeClient client = new LiveSearchPortTypeClient())
{
string sCode = this.Currentsource.Code;
string tCode = this.CurrentTarget.Code;
SearchResponse response = client.Search(BuildRequest(elememnts[key], sCode, tCode));
if (response.Translation.Results.Count() > 0)
{
string item = response.Translation.Results[0].TranslatedTerm;
XElement element = new XElement(system + "String", item);
element.Add(new XAttribute(x + "Key", key));
root.Add(element);
int percentage = ((i * 90)/elements) + 5;
oWorker.ReportProgress(percentage, string.Format("string {0} is converted as {1}", elememnts[key], item));
//Cancel the WORKER
if (oWorker.CancellationPending)
{
e.Cancel = true;
break;
}
}
}
}
catch (Exception ex)
{
// Исключение при доступе к сети.
oWorker.ReportProgress(((i * 90) / elements) + 5, ex.Message);
}
i = i+1;
}
targetDocument = targetDocument ?? new XDocument();
targetDocument.Add(root);
targetDocument.Save(destinationfile);
oWorker.ReportProgress(100, "Target file created Successfully");
TimeSpan span = DateTime.Now - startTime;
e.Result = string.Concat(span.TotalSeconds, " seconds");
}
public static SearchRequest BuildRequest(string query, string sCode, string tCode)
{
SearchRequest request = new SearchRequest();
request.AppId = "3382CF24D27D0A095C7C4945EA17FDD8E2946C73";
request.Query = query;
request.Sources = new SourceType[] { SourceType.Translation };
request.Translation = new TranslationRequest();
request.Translation.SourceLanguage = sCode;
request.Translation.TargetLanguage = tCode;
request.Version = "2.2";
return request;
}
Можно видеть, что вызов BuildRequest фактически создает объект SearchRequest, используемый для вызова переводчика Bing. Переменные SourceLanguage и TargetLanguage позволят задать значения языка и региональных параметров.
Затем с помощью объекта LiveSearchPortTypeClient будет вызван запрос поиска. Вызов client.Search(Request) получает объект запроса и возвращает результат. Возможные результаты можно найти в коллекции response.Translation.Results.
Можно видеть, что созданная логика использует объекты XDocument для создания того же результата, который нам нужен для ресурса.
Я надеюсь, что это решение окажется полезным.