![](https://habrastorage.org/getpro/habr/post_images/f5c/a48/1e6/f5ca481e6b3697b55a638eb80f9b2314.png)
Являясь разработчиком приложений для Windows Phone, я давно понял, что рынок мобильных приложений глобален и для успешного продвижения приложения, оно должно быть качественно локализовано для максимального количества доступных языков. Локализация приложений для Windows Phone подробно описана в документации по разработке приложений. Однако процесс локализации заголовков и заголовков плиток приложений отличается от локализации самого приложения и часто вызывает затруднения, так как требует подготовки отдельного файла ресурсов для каждого языка.
Для решения этой проблемы я создавал несколько проектов ресурсов, которые после удачной сборки копировали результат в каталог приложения. Данное решение меня полностью устраивало, пока мои приложения поддерживали не более трех языков. После того, как появилась необходимость локализовать приложения на большее количество языков, данный подход стал обременительным.
Поиск показал, что многие ищут пути удобного решения данной проблемы. Но удобного способа для себя я не нашел, это привело меня к решению написать утилиту которая позволит создавать и управлять файлами ресурсов. Для меня самым удобным способом стало, описание заголовков в XML файле и автоматическая генерация необходимых файлов на его основании.
Для файла описания заголовков приложения бы выбран следующий формат:
<Localization>
<Application>
<Name />
<Description />
</Application>
<Languages>
<Default inactive="false">
<Title />
<TileTitle />
</Default>
<Albanian inactive="true">
<Title />
<TileTitle />
</Albanian>
…
</Languages>
</Localization>
Утилиту решил создать как Custom Tool для Visual Studio
![](https://habrastorage.org/getpro/habr/post_images/6f8/bb5/9c2/6f8bb59c2199566cfcbf09e2b56cd47d.png)
Для реализации объекта, который можно использовать как Custom Tool, необходимо реализовать интерфейс IVsSingleFileGenerator.
interface IVsSingleFileGenerator
{
int DefaultExtension(out string);
int Generate(string, string, string, IntPtr[], out uint, IVsGeneratorProgress);
}
Как видно из названия и описания интерфейса, с его помощью можно создать только один файл, чего для решения поставленной задачи не достаточно. Однако исследование показало, что во время генерации утилите доступен контекст Visual Studio, через который можно изменять проекты произвольным образом.
Для управления проектом, нам надо получить ссылку на объект ProjectItem, с которым связан XML файл, описывающий исходные данные
var dte = (EnvDTE.DTE)Package.GetGlobalService(typeof(EnvDTE.DTE));
var sourceControl = dte.SourceControl;
var projectItem = dte.Solution.FindProjectItem(sourceFilePath);
if (projectItem == null)
{
throw new ApplicationException("Unable to locate a ProjectItem associated to the file");
}
После этого, мы можем добавлять и удалять дочерние файлы из коллекции ProjectItems данного элемента.
private static void UpdateNestedProjectItems(ProjectItem projectItem, HashSet<string> generatedFiles)
{
foreach (ProjectItem item in projectItem.ProjectItems)
{
if (item.Properties != null)
{
var property = item.Properties.Item("FullPath");
if (property != null)
{
var value = property.Value as string;
if (value != null)
{
if (generatedFiles.Contains(value))
{
item.Properties.Item("BuildAction").Value = 2;
generatedFiles.Remove(value);
}
else
{
if(value.EndsWith("txt", StringComparison.CurrentCultureIgnoreCase))
{
item.Properties.Item("BuildAction").Value = 0;
}
else
{
item.Remove();
}
}
}
}
}
}
foreach (var file in generatedFiles)
{
var item = projectItem.ProjectItems.AddFromFile(file);
item.Properties.Item("BuildAction").Value = 2;
}
}
Вторая важная часть создаваемой утилиты это создание файлов ресурсов, для ее решения был создан и внедрен в ресурсы утилиты пустой dll файл. Во время работы данный файл копируется и переименовывается в соответствии с требованиями именования файлов ресурсов. После этого, в этот файл добавляются ресурсы с использованием WIN API функций: BeginUpdateResource, UpdateResource, EndUpdateResource.
internal static class APIHelper
{
public const int RT_STRING = 6;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr BeginUpdateResource(string pFileName, bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool UpdateResource(IntPtr hUpdate, int lpType, int lpName, ushort wLanguage, IntPtr lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
}
При создании строковых ресурсов есть маленькая хитрость, дело в том, что строки в ресурсах хранятся в виде блоков по 16 строк, что сделано с целью оптимизации. Для изменения строки необходимо перезаписывать весь блок строк, в который она входит. Для нашей задачи нет необходимости менять данные поэтому, мы можем просто записывать нужные нам данные, и не заботится о сохранности других строк блока.
Номер блока строк можно вычислить по следующему алгоритму:
N = STRID / 16 + 1
где N — номер блока строк, STRID – ID строки ресурсов (в документации по локализации принято для заголовка приложения 100, для заголовка плитки 200)
Готовую утилиту необходимо зарегистрировать в GAC, а так же добавить запись в реестр, для регистрации класса как Custom Tool, после чего его можно будет использовать в проектах.
Итог работы утилиты выглядит так
![image](https://habrastorage.org/getpro/habr/post_images/b98/eb6/f3b/b98eb6f3b6e86c6b3094c294e4542db1.png)
Для более удобного использования утилиты так же был создан шаблон элемента для Visual Studio и создан инсталлятор для автоматической регистрации утилиты.
Исходные коды доступны: http://wptitlelocalizationtool.codeplex.com/
Установочный пакет доступен: http://visualstudiogallery.msdn.microsoft.com/527c7d79-55f7-4ff2-9ab8-d6122bf6e75d