- PVSM.RU - https://www.pvsm.ru -
Начну с того, что за много лет работы программистом я неоднократно сталкивался с задачей внедрения в проект локализации в том или ином виде, но обычно это были решения, работающие на основе подгружаемого словаря с парами ключ-значение. Такой подход вполне оправдан для небольших проектов, но имеет ряд существенных недостатков:
Проанализировав эти проблемы, я пришел к выводу о необходимости создания собственной библиотеки для локализации проектов, которая будет лишена перечисленных выше недостатков. В этой статье я расскажу о принципах ее работы с примерами кода на C#.
Ссылка на проект SourceForge: https://sourceforge.net/projects/open-genesis/?source=navbar [1]
В сборку входят следующие проекты:
Библиотека построена на базе плагинов и работает следующим образом: при запуске приложения создается менеджер локализаций (LocalizationManager), которому указывается путь к каталогу, где он будет производить поиск доступных пакетов локализаций (LocalizationPackage), каждый из которых отвечает за некоторую культуру (пакет русской локализации, английской и т.п.). После этого дается команда на поиск и загрузку дескрипторов всех пакетов, весь код инициализации выглядит примерно следующим образом:
// инициализация менеджера локализаций
LocalizationManager = new LocalizationManager();
LocalizationManager.BasePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Localization");
LocalizationManager.Initialize();
try
{
LocalizationManager.DetectAllLocalizations();
}
catch (LocalizationException ex)
{
MessageBox.Show(ex.Message, "Ошибка локализации", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
Если все прошло без ошибок, в менеджере появится список доступных локализаций в виде их кратких описаний (дескрипторов, LocalizationDescriptor). Эти дескрипторы не содержат в себе какой-либо логики, а служат только лишь описанием того или иного пакета, который можно загрузить и начать применять в программе.
Список всех локализаций можно получить из менеджера:
manager.Localizations
Например, мы захотели подключить русскую локализацию, для этого необходимо загрузить ее непосредственно в менеджер:
LocalizationPackage package = manager.Load("ru");
После загрузки с локализацией можно работать — получать из нее строки, ресурсы и т.д., а если она более не нужна, ее можно выгрузить:
manager.Unload("ru");
Важно! Можно загружать и выгружать неограниченное число локализаций, т.к. все они создаются в собственных доменах (AppDomain).
Каждая локализация представляет из себя набор файлов в отдельном каталоге, корневым для всех является тот, который был выбран при загрузке менеджера локализаций. В примере выше, это будет каталог [ProjectDir]Localization, а непосредственно пакеты локализаций будут размещаться в каталогах [ProjectDir]Localizationru, [ProjectDir]Localizationen и т.д…
Каждый стандартный пакет, обязательно должен содержать следующие файлы:
Пример для русской локализации:
<?xml version="1.0"?>
<LocalizationInfo xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>Русский</Name>
<Culture>ru</Culture>
</LocalizationInfo>
Как видим, здесь всего два поля, возможно потом будут добавлены новые поля для идентификации того или иного пакета.
Пример исполняемого кода для русской локализации:
using System;
using Genesis.Localization;
namespace Ru
{
public class Package : LocalizationPackage
{
protected override object Plural(int count, params object[] forms)
{
int m10 = count % 10;
int m100 = count % 100;
if (m10 == 1 && m100 != 11)
{
return forms[0];
}
else if (m10 >= 2 && m10 <= 4 && (m100 < 10 || m100 >= 20))
{
return forms[1];
}
else
{
return forms[2];
}
}
}
}
Ниже будет дано пояснение, что такое метод Plural.
Итак, мы создали менеджер локализаций и загрузили в него пакет с переводом. Теперь его можно использовать в программе тремя способами:
Пример использования:
LocalizationPackage package = manager.Load(culture);
string strByName = package["name"];
string strByID = package[150];
Пример использования:
LocalizationPackage package = manager.Load(culture);
string formattedString = package["name", arg1, args2, ...];
В качестве аргументов могут использоваться любые объекты. Подробнее об этом методе будет рассказано ниже.
Сила библиотеки заключается в ее интерпретаторе строк. Что же он из себя представляет?
Если вкратце, то это набор инструкций, включаемых в локализованные строки, с помощью которых можно адаптировать перевод под ту или иную культуру.
Интерпретатор строк вызывается описанным выше методом получения строки с заданными аргументами (при обычном обращении по ключу возвращается локализованная строка в «чистом» виде) или специальным методом GetFormattedString(string format, params object[] args), который работает точно так же, но при этом в качестве первого аргумента передается произвольная строка формата.
Теперь подробнее об этих инструкциях. Всего их две:
Формат инструкции:
%index%
Результат: встраивание в строку аргумента под номером index
Пример использования:
package.GetFormattedString("%1% = %0%%%", 80, "КПД");
Результат:
КПД = 80%
Обратите внимание, что символ %, будучи служебным, должен экранироваться другим таким же символом, как в этом примере.
Формат инструкции:
%Func(arg1, arg2, ..., argN)%
Аргументами могут быть числа, строки в двойных кавычках (сами кавычки экранируются, как и %, двойным повтором), аргументы строки по их номеру (%index) или вызовы других функций.
Пример использования:
package.GetFormattedString("Игрок %1% наносит по вам %Upper(Random("сильный", "сокрушительный", "мощный"))% удар, отнимая %0% %Plural(%0, "единицу", "единицы", "единиц")% здоровья.", 55, "MegaDeath2000");
Результат:
Игрок MegaDeath2000 наносит по вам СОКРУШИТЕЛЬНЫЙ удар, отнимая 55 единиц здоровья.
В классе LocalizationPackage встроено несколько «стандартных» функций, часть была использована в примере выше:
Если вам требуется добавить новые функции, сделать это можно двумя способами.
[Function("P")]
protected abstract object Plural(int count, params object[] forms);
[Function]
protected virtual object Random(params object[] variants)
{
if (variants.Length == 0)
{
return null;
}
else
{
return variants[_rnd.Next(variants.Length)];
}
}
Обратите внимание, что для функции допустимо задание списка ее псевдонимов (для краткой записи), например Plural может вызываться как через основное имя (Plural), так и через псевдоним (P), при этом регистр в названиях функций не имеет значения.
var package = LocalizationManager.Load("ru");
package.InjectFormatterFunction(new Func<int, int, int>((a, b) => Math.Min(a, b)), "Min");
package.InjectFormatterFunction(new Func<int, int, int>((a, b) => Math.Max(a, b)), "Max");
package.GetFormattedString("%min(%0, max(%1, %2))%", 10, 8, 5);
Результат:
8
В качестве аргумента для InjectFormatterFunction может быть передан метод (MethodInfo) или делегат (в примере выше передаются делегаты).
Помимо основных функций, библиотека предоставляет еще два дополнения.
В Debug-версии библиотеки включена возможность не только получения локализованных строк описанными выше способами, но и их непосредственная запись:
var package = LocalizationManager.Load("ru");
package["New Key"] = "Новое значение";
package.Save();
В этом случае будет создана новая локализованная строка с указанным ключом и значением (либо перезаписана уже имеющаяся), а сам пакет сохранен на диск. Также в режиме отладки при попытке чтения строки с отсутствующим ключом, будет возвращено пустое значение, но при этом будет создана новая запись. Это удобно на начальном этапе разработки — нам не нужно заботится о наполнении словаря — он сам будет пополняться пустыми значениями, которые мы потом заполним данными.
В релизе функции записи недоступны, что вполне логично — промышленная программа не должна уметь пополнять свой словарь локализации.
Это наш десерт. Назначение — быстрая локализация форм, контролов и других сложных объектов.
Данная функция используется в демонстрационном проекте LocalizationViewer.
Приведу отрывок описания главной формы:
[LocalizableClass("Text", "CAPTION")]
public partial class frmMain : Form
{
...
[LocalizableClass]
private System.Windows.Forms.ToolStripButton cmdExit;
[LocalizableClass]
private System.Windows.Forms.ToolStripButton cmdSave;
[LocalizableClass]
private System.Windows.Forms.ToolStripLabel lblSearch;
...
/// <summary>
/// применяем локализацию
/// </summary>
private void Localize()
{
LocalizationMapper mapper = new LocalizationMapper();
mapper.Current = manager["ru"];
mapper.Localize(this);
}
...
}
LocalizationMapper, позволяет локализовать любой объект, переданный ему в функции Localize, используя атрибуты [Localizable] и [LocalizableClass] на полях и свойствах локализуемого объекта (в данном случае — формы). Например, атрибут [LocalizableClass] без параметров означает, что надо локализовать свойство по умолчанию (Text), при этом будет использован автоматический ключ вида <class>.<subclass>.<field>. Для поля Text кнопки cmdExit ключ будет таким:
LocalizationViewer.frmMain.cmdExit_Text
Библиотека в скором времени будет опробована в одном из моих проектов, так что скорее всего будут кое-какие доработки, в основном направленные на расширение базового функционала пакетов. Следите за обновлениями на SourceForge и пишите свои комментарии и мысли по дальнейшему развитию библиотеки.
Возможно, вы скажете, что я изобретаю велосипед. Пусть так, но изобретать велосипеды — это мое хобби…
К тому же, это намного интереснее и полезнее в плане самосовершенствования в программировании.
По этой же причине здесь не будет ссылок на литературу и прочие источники информации — все было написано с нуля.
Автор: Doomer3D
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/net/41234
Ссылки в тексте:
[1] https://sourceforge.net/projects/open-genesis/?source=navbar: https://sourceforge.net/projects/open-genesis/?source=navbar
[2] Источник: http://habrahabr.ru/post/190556/
Нажмите здесь для печати.