Привет Хабрадруг!
Я, конечно, понимаю, что тема это уже заезжена до дыр, но попытаюсь описать мое видение локализации приложения и ее реализацию. И так, что мы хотим получить в итоге: Систему быстрой локализации .NET приложения, а в частности WebForms, на стадии пост разработки. По пунктам:
1. Разработка приложения: минимум внимания локализации.
2. Завершение разработки: минимум времени на локализацию.
И ещё надо, что бы это все было максимально удобно и просто.
Если коротко, то все будет выглядеть так:
У нас будет метод, что то на подобии gettext, в который мы будем передавать какой-то текст, или ключ + текст, а метод в свою очередь нам вернет локализованную строку или же то, что было в него передано. А также будет утилита, которая пропарсит *.cs, *.aspx, *.ascx файлы в поисках этого самого метода, получит оттуда ключи и текст, посмотрит или данные тексты и ключи уже имеются в ресурсах приложения. Потом мы воспользуемся google translate, что бы хоть худо бедно, да перевести новые тексты. И напоследок отредактируем переводы и сохраним это все назад в ресурсы. Готово.
Явно, что похожий, или может даже лучший механизм уже разработан, но полазив в интернете, увидел только мега тяжёлые и к тому-же платные решения.
Итак, приступим к реализации. Начнем со вспомогательного класса, a для server-side контролов c ExpressionBuilder-a:
Теперь, во время разработки, в местах, где нам понадобится локализация пишем:
Класс L специально не находиться ни в каком namespace, что бы не приходилось в *.ascx файлах делать его импорт.
Здесь есть, как видно из кода, не мало важный метод GenerateKey. Если мы передаем только текст, без ключа, то этот метод сгенерирует ключ и именно этот ключ мы будем искать в ресурсах. Ключ в ресурсах может содержать только символы [a-z0-9_]. И наша утилита будет использовать этот же метод для генерации ключей. Сейчас еще рассмотрим ExpressionBuilder и приведу более конкретный пример всей схемы. Для чего же нам нужен этот самый Builder?
Конструкция <%=%> в серверных контролах приведет к ошибке, а для <%# %> надо дополнительно вызывать DataBind метод, что не очень удобно. Нам нужен класс унаследованный от ExpressionBuilder.
В результате пишем:
Первая часть готова. Переходим к утилите. Это обыкновенное WinForms приложение.
Диаграмма Классов
Как видно из диаграммы, код довольно прямолинейный. Но главное — простой. Мы загружаем нужный нам Solution или проект, указываем в классе Settings путь к нашему файлу ресурсов. И ...«поехали»! Solution класс загрузит все файлы. CodeParser загрузит все строки и ключи в класс Translation, который в свою очередь, загрузит все переводы с ресурсов и покажет все это в GridView. Теперь можно переводить вручную или через google, можна сохранить оригинальные строки в текстовый файл или что то еще реализовать, к примеру, сохранение в xml.
Assembla Subversion(Регистрация не обязательна)
Утилита эта написана для себя нa скорую руку, по принципу: «лишь бы работало», поэтому попрошу гуру не судить строго. Если будет у кого то интерес, могу довести это дело до, хотя бы, альфа версии.
Что ещё хочется сделать:
CodeParser выдирая строки из файлов запоминает их позиции. А так как большинство локализированных строк идут у меня изначально без ключей, то на их генерацию уходит несколько лишних наносекунд, поэтому утилита должна после вставлять в файлы ключи к строкам.
Но и без этого все работает очень шустро, хотя буду рад выслушать любую критику или пожелания.
Спасибо за внимание.
Я, конечно, понимаю, что тема это уже заезжена до дыр, но попытаюсь описать мое видение локализации приложения и ее реализацию. И так, что мы хотим получить в итоге: Систему быстрой локализации .NET приложения, а в частности WebForms, на стадии пост разработки. По пунктам:
1. Разработка приложения: минимум внимания локализации.
2. Завершение разработки: минимум времени на локализацию.
И ещё надо, что бы это все было максимально удобно и просто.
Если коротко, то все будет выглядеть так:
У нас будет метод, что то на подобии gettext, в который мы будем передавать какой-то текст, или ключ + текст, а метод в свою очередь нам вернет локализованную строку или же то, что было в него передано. А также будет утилита, которая пропарсит *.cs, *.aspx, *.ascx файлы в поисках этого самого метода, получит оттуда ключи и текст, посмотрит или данные тексты и ключи уже имеются в ресурсах приложения. Потом мы воспользуемся google translate, что бы хоть худо бедно, да перевести новые тексты. И напоследок отредактируем переводы и сохраним это все назад в ресурсы. Готово.
Явно, что похожий, или может даже лучший механизм уже разработан, но полазив в интернете, увидел только мега тяжёлые и к тому-же платные решения.
Итак, приступим к реализации. Начнем со вспомогательного класса, a для server-side контролов c ExpressionBuilder-a:
public static class L
{
private static readonly Regex KeyRepairRegex = new Regex("[^\\w\\d]", RegexOptions.Compiled);
public static string Run(string value)
{
var key = GenerateKey(value);
var localized = Resources.ResourceManager.GetString(key);
return string.IsNullOrEmpty(localized) ? value : localized;
}
public static string Run(string key, string value)
{
var localized = Resources.ResourceManager.GetString(key);
return string.IsNullOrEmpty(localized) ? value : localized;
}
public static string Run(string key, string value, params object[] format)
{
var localized = Run(key, value);
if (format != null && format.Length > 0)
{
try
{
localized = string.Format(localized, format);
}
catch
{
}
}
return localized;
}
private static string GenerateKey(string value)
{
if (string.IsNullOrEmpty(value)) return value;
value = value.Length > 23 ? value.Substring(0, 23) + "__" : value;
value = KeyRepairRegex.Replace(value, "_");
if (char.IsUpper(value[0])) value = "_" + value;
value = value.ToLowerInvariant();
return value;
}
}
Теперь, во время разработки, в местах, где нам понадобится локализация пишем:
<%= L.Run("Hello, Mr.Everybody") %>
Класс L специально не находиться ни в каком namespace, что бы не приходилось в *.ascx файлах делать его импорт.
<%@ Import Namespace ="Example.Namespace" %>
Здесь есть, как видно из кода, не мало важный метод GenerateKey. Если мы передаем только текст, без ключа, то этот метод сгенерирует ключ и именно этот ключ мы будем искать в ресурсах. Ключ в ресурсах может содержать только символы [a-z0-9_]. И наша утилита будет использовать этот же метод для генерации ключей. Сейчас еще рассмотрим ExpressionBuilder и приведу более конкретный пример всей схемы. Для чего же нам нужен этот самый Builder?
Конструкция <%=%> в серверных контролах приведет к ошибке, а для <%# %> надо дополнительно вызывать DataBind метод, что не очень удобно. Нам нужен класс унаследованный от ExpressionBuilder.
public class LExpressionBuilder: ExpressionBuilder
{
public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
var thisType = new CodeTypeReferenceExpression(GetType());
var expression = new CodePrimitiveExpression(entry.Expression.Trim());
const string evaluationMethod = "Localize";
return new CodeMethodInvokeExpression(thisType, evaluationMethod, new CodeExpression[] { expression });
}
public static string Localize(string expression)
{
return L.Run(expression);
}
public override object EvaluateExpression(object target, BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
return Localize(entry.Expression);
}
public override bool SupportsEvaluate
{
get { return true; }
}
}
В результате пишем:
<input typ='submit' id='btnSubmit' runat='server' value="<%$ L:A people free to choose will always choose peace %>"/>
Первая часть готова. Переходим к утилите. Это обыкновенное WinForms приложение.
Диаграмма Классов
Как видно из диаграммы, код довольно прямолинейный. Но главное — простой. Мы загружаем нужный нам Solution или проект, указываем в классе Settings путь к нашему файлу ресурсов. И ...«поехали»! Solution класс загрузит все файлы. CodeParser загрузит все строки и ключи в класс Translation, который в свою очередь, загрузит все переводы с ресурсов и покажет все это в GridView. Теперь можно переводить вручную или через google, можна сохранить оригинальные строки в текстовый файл или что то еще реализовать, к примеру, сохранение в xml.
Assembla Subversion(Регистрация не обязательна)
Утилита эта написана для себя нa скорую руку, по принципу: «лишь бы работало», поэтому попрошу гуру не судить строго. Если будет у кого то интерес, могу довести это дело до, хотя бы, альфа версии.
Что ещё хочется сделать:
CodeParser выдирая строки из файлов запоминает их позиции. А так как большинство локализированных строк идут у меня изначально без ключей, то на их генерацию уходит несколько лишних наносекунд, поэтому утилита должна после вставлять в файлы ключи к строкам.
Но и без этого все работает очень шустро, хотя буду рад выслушать любую критику или пожелания.
Спасибо за внимание.
* Source code was highlighted with Source Code Highlighter.