Средство "Генератор ресурсов"

Автор: Абхишек Сюр (Abhishek Sur)

В моей последней работе мне понадобилось реализовать возможность поддержки нескольких языков. Корпорация Майкрософт предложила несколько приемов решения этой задачи, но я придумал собственную логику для достижения той же цели. Так как языки — это фактически строковые данные, которые мне нужно предоставить приложениям WPF, можно легко создать отдельные файлы ресурсов для данных, связанных с конкретным языком.

Теперь при загрузке программы можно легко добавить нужный язык в текущий объект CultureInfo. Я ввел этот прием в статье: Простейший способ реализовать многоязыковое приложение.

Позднее в моей статье Подключаемый ресурс для словаря ресурсов WPF я предложил возможный способ подключения — в ресурсах для внешних сборок.

Подводя итог, когда бы мне ни понадобилось использовать строку, мне будет нужно изменить в файле ресурсов строковый ресурс с соответствующим ключом. Нам нужно создать новые файлы ресурсов для каждого языка, а также нужно сопоставить каждому ключу соответствующие данные.

Поэтому вместо Text="My Custom text" я буду писать что-то вроде Text="{DynamicResource rKeyCustomText}", чтобы ключ rKeyCustomText был заменен соответствующим текстом во время выполнения проекта, таким образом, обеспечивая поддержку языка с помощью строковых ресурсов. Для этого нужно создать схему, подобную следующей:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"      xmlns:x="http://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>

Поэтому проблема состоит в том, что после объявления ключа ресурса нужно мгновенно преобразовать соответствующую строку во всех файлах ресурсов. Эта работа мне кажется очень неинтересной и трудоемкой. Поэтому я задумался о создании средства, которое помогло бы в этом. Обсудим, как можно использовать это средство для автоматического построения файлов ресурсов.

Чтобы создать это приложение, я использовал следующие технологии:

  1. .NET (конечно же);
  2. WPF для пользовательского интерфейса;
  3. службы Bing для перевода;
  4. потоки для выполнения фоновой работы.

Поэтому пользовательский интерфейс выглядит просто и содержит только два текстовых поля, показывающих исходный и конечный файлы. Рассмотрим нужную последовательную шагов:

  • Шаг 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("{http://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 = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
            XNamespace x = "http://schemas.microsoft.com/winfx/2006/xaml";
            XNamespace system = "clr-namespace:System;assembly=mscorlib";
            XElement root = new XElement(xaml + "ResourceDictionary",
                                        new XAttribute("xmlns", "http://schemas.microsoft.com/winfx/2006/xaml/presentation"),
                                        new XAttribute(XNamespace.Xmlns + "x", "http://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 для создания того же результата, который нам нужен для ресурса.

Я надеюсь, что это решение окажется полезным.