“Ручная” локализация

Категории: Разработка ПО
Тэги: , ,

Интеграция собственных продуктов с уже существующими системами и их окружением рождает иногда интересные решения. С задачей такого рода я столкнулся производя локализацию разрабатываемого приложения.

Вообще говоря, стояла задача по написанию утилиты для существующей системы, которая бы располагалась в той же файлово-каталожной структуре и максимально дружественно вписывалась в окружение. Существующая система, львиную долю которой составляет неуправляемый код, имеет специально выделенную папку, в которой содержатся ресурсы. Каждый конкретный набор ресурсов, характерный выбранной локали, помещён в папку с названием этой локали. Например, вся английская локаль лежит в папке en, русская в ru, немецкая в de и т.д. При этом локали могут меняться в момент выполнения основного приложения, информация о выбранном языке хранится в специальном ключе реестра. В этом нет ничего необычного, просто я сразу отвечаю на вопросы, о невозможности создании локализованной сборки заранее.

Далее, поскольку единственный путь, которым вызывается моя утилита, пункт меню в основной программе, логично было бы подгружать необходимый набор ресурсов в момент запуска. Плюс сборка всех модулей, в том числе и моего, выполняется скриптом, менять код которого у меня полномочий нет. Таким образом задача делится на две: во-первых, инструментировать код своей утилиты таким образом, чтобы он подгружал необходимый ресурс в момент запуска; во-вторых, необходимо создать инфраструктуру, позволяющую создавать наборы локализованных ресурсов с учетом принятой политики размещения и используемый build-скриптов.

Начнём с инструментации кода. Создадим класс, который будет нашим менеджером ресурсов. Далее создадим в нём функцию загрузки ресурсов из файла:

// Создаем корневое имя ресурса
string resourcesBase = "MyApplication";
 
// Создаем путь к папке с ресурсами
string location = Assembly.GetExecutingAssembly().Location;
location = Path.GetDirectoryName(location);
DirectoryInfo dInfo = Directory.GetParent(location);
 
// Читаем из реестра текущую локаль и добавляем ее в путь
resourcesPath += GetMainProgramLocale();
 
// Создаем сам ResourceManager
m_ResManager = ResourceManager.CreateFileBasedResourceManager(resourcesBase, dInfo.FullName + resourcesPath, null);

Для этого вопспользуемся CreateFileBasedResourceManager, которая подгрузит из папки указанный набор ресурсов и вернет ResourceManager.

В этом же классе создадим функцию, которая будет возвращать выбранный ресурс, например строку:

public string GetString(string strID)
{
    try
    {
        // В самом простом виде, например, так
        return m_ResManager.GetString(strID);
    }
    catch (MissingManifestResourceException e)
    {
        // Обработка исключительной ситуации
    }
    catch (InvalidOperationException e)
    {
        // Обработка исключительной ситуации
    }
    // и т.д.
}

Далее, для удобства билда, создадим bat-файл, который будет упаковывать .resx фалы в бинарные .resource, с помощью утилиты resgen и помещать их в нужный каталог.

call "%VS80COMNTOOLS%..\..\VC\bin\vcvars32.bat"
rem На вский случай вызываем vcvars для корректного вызова resgen
 
resgen MyAppl\Localization\en\MyAppl.en-US.resx MainProgram\loc\en\MyAppl.resources
resgen MyAppl\Localization\ru\MyAppl.ru-RU.resx MainProgram\loc\ru\MyAppl.resources

А запуск этого bat-файла поместим в Post Build Event.

Вот и все. Скрипт отвечающий за сборку менять не надо – у нас есть Post Build Event, а собственный менеджер ресурсов вернет нужный ресурс, подгружая необходимый набор в момент запуска приложения.