Локализация сайта asp.net mvc

Локализация через ресурсы

Наиболее распространненый способ локализации сайта использование ресурсов. Сразу хочется отметить основные минусы этого подхода:

Но для небольших и простых проектов этот способ может подойти. В итоге создания файлов ресурсов получается что-то вроде:

локализация через ресурсы asp.net

 Этот подход к локалицаии сайтов мы рассматривать не будем в силу основных минусов данного способа. Рассмотрим альтернативный способ локализации сайтов с использованием базы данных. Преимущества рассматриваемого способа:

Данный подход состоит из 2-х этапов: локализация View и локализация Model. Иначе говоря локализация статичных текстов (в представлении) и локализация моделей из базы данных.

Виды локализации в базе данных

1. Дополнительная таблица для каждого объекта

Примерная схема базы данных выглядит следующим образом:

MYITEMS

MYITEMLOCALIZED

CUSTOMERS

CUSTOMERLOCALIZED

CUSTOMERS

LOCALIZED

ALTER TABLE [dbo].[LocalizedViews] WITH CHECK ADD CONSTRAINT [FK_LocalizedViews_Languages] FOREIGN KEY([LanguageId]) REFERENCES [dbo].Languages GO

ALTER TABLE [dbo].[LocalizedViews] CHECK CONSTRAINT [FK_LocalizedViews_Languages] GO

В этой таблице будет содержаться весь перевод статичных текстов сайта (кнопки, надписи, заголовки). Идентификатор ресурса состоит из 2-х частей (ViewPath и KeyName). Таком образом KeyName может повторяться, а по ViewPath можно разделить и сгруппировать различные разделы сайта. При этом эту информацию можно использовать как во View (cshtml), так и в Controller. В библиотеке используется IViewLocalizationRepository, что на мой взгляд немного усложняет код, правда делает его более универсальным и поддерживает разные базы данных. Но при использовании asp.net mvc + sql server можно не изобретать нечто подобное.

Основная идея локализации html состоит в том, чтобы унаследовать WebViewPage и сделать процесс локализации более удобным.

/// <summary>
/// Base page adding support for the new helpers in all views.
/// </summary>
public abstract class OxozleWebViewPage<TModel> : WebViewPage<TModel>
{
    private ViewLocalizer _viewLocalizer;

/// &lt;summary&gt;
/// Gets class used for the view localization
/// &lt;/summary&gt;
protected virtual ViewLocalizer ViewLocalizer
{
    get
    {
        if (_viewLocalizer == null)
        {
            _viewLocalizer = DependencyResolver.Current.GetService&lt;ViewLocalizer&gt;();
            if (_viewLocalizer == null)
            {
                var repos = DependencyResolver.Current.GetService&lt;IViewLocalizationRepository&gt;();
                if (repos == null)
                    throw new Exception("You must register a ViewLocalizer or an IViewLocalizationRepository in your container.");

                _viewLocalizer = new ViewLocalizer(repos);
            }
        }

        return _viewLocalizer;
    }
}




/// &lt;summary&gt;
/// GetText inspired localization
/// &lt;/summary&gt;
/// &lt;param name="key"&gt;&lt;/param&gt;
/// &lt;param name="formatterArguments"&gt;optional arguments if the string contains {} formatters&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public MvcHtmlString T(string siteArea, string key, params object[] formatterArguments)
{
    var translated = ViewLocalizer.Translate(siteArea, key);
    return
        MvcHtmlString.Create(formatterArguments.Length == 0
                                 ? translated
                                 : string.Format(translated, formatterArguments));
}

}

/// <summary> /// Required to be able to switch page in the Views\Web.Config, but isn’t extended with any new stuff. /// </summary> public abstract class OxozleWebViewPage : WebViewPage { }

Все методы сделаны на основе описанной выше библиотеки. Множество классов просто выпилино для упрощения. Если понядобятся подробности или исходный код - можно спросить в комментариях.

Аналогичный код добавляем в базовом классе контроллера. Также в базовом классе контроллера добавляем логику для определения языка пользователя.

public class ControllerBase : Controller
{
protected override void Initialize(RequestContext requestContext) { string language = null;

    if (OxoCookies.RequestCookies.Contains("LanguageId"))
    {
        language = OxoCookies.RequestCookies["LanguageId"].ToString();
    }
    //Сюда попадаем, если язык не определен
    else if (requestContext.HttpContext.Request.UserLanguages != null &amp;&amp;
             requestContext.HttpContext.Request.UserLanguages.Length &gt; 0)
    {
        language = requestContext.HttpContext.Request.UserLanguages[0];
    }

    if (language.IsEmpty())
    {       
        language = "ru";
    }

    var ci = LanguageService.GetLanguage(language).CreateCultureInfo();
    Thread.CurrentThread.CurrentCulture = ci;
    Thread.CurrentThread.CurrentUICulture = ci;
    Language = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
    OxoCookies.SetValue("LanguageId", Language, DateTime.Now.AddYears(1));

    base.Initialize(requestContext);
}

#region Translate

private ViewLocalizer _viewLocalizer;

/// &lt;summary&gt;
/// Gets class used for the view localization
/// &lt;/summary&gt;
protected virtual ViewLocalizer ViewLocalizer
{
    get
    {
        if (_viewLocalizer == null)
        {
            _viewLocalizer = DependencyResolver.Current.GetService&lt;ViewLocalizer&gt;();
            if (_viewLocalizer == null)
            {
                var repos = DependencyResolver.Current.GetService&lt;IViewLocalizationRepository&gt;();
                if (repos == null)
                    throw new Exception(
                        "You must register a ViewLocalizer or an IViewLocalizationRepository in your container.");

                _viewLocalizer = new ViewLocalizer(repos);
            }
        }

        return _viewLocalizer;
    }
}


protected string Translate(string siteArea, string key, params object[] formatterArguments)
{
    var translated = ViewLocalizer.Translate(siteArea, key);
    return
        formatterArguments.Length == 0
            ? translated
            : string.Format(translated, formatterArguments);
}

#endregion

}

Локализация моделей

Рассмотрим локализацию бизнес объектов на примере игр.

Таблицы SQL Server

CREATE TABLE [dbo].Games
GO

CREATE TABLE [dbo].Games_TX GO

ALTER TABLE [dbo].[Games_TX] WITH CHECK ADD CONSTRAINT [FK_Games_TX_Games] FOREIGN KEY([GameId]) REFERENCES [dbo].Games GO

ALTER TABLE [dbo].[Games_TX] CHECK CONSTRAINT [FK_Games_TX_Games] GO

ALTER TABLE [dbo].[Games_TX] WITH CHECK ADD CONSTRAINT [FK_Games_TX_Languages] FOREIGN KEY([LanguageId]) REFERENCES [dbo].Languages GO

ALTER TABLE [dbo].[Games_TX] CHECK CONSTRAINT [FK_Games_TX_Languages] GO

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

sql server локализация в базе данных

Получение DAL объектов игр.

/// <summary>
/// Все игры
/// </summary>
private static List<Game> _games = new List<Game>();
public static List<Game> Games
{
    get
    {
        if (!Config.CacheEnabled)
            _games.Clear();

    if (_games.Count == 0)
    {
        //IoC может быть любой
        IRepository&lt;Game&gt; gamesRepository = QRResolver.Current.GetInstance&lt;IRepository&lt;Game&gt;&gt;();
        //Паттерн Repository и соответствующие классы из Oxozle.Utilities.Data
        _games = gamesRepository.Get(null, null, "Games_TX").ToList();
    }

    return _games;
}

}

Этим кодом мы получим (и закешируем) все объекты игр и все переводы. Заранее знаем, что игр не очень много. В случае, если объектов в базе данных достаточно много следует выключить кеширование или использовать класс Cache.

Конструктор модели GameModel выглядит примерно следующим образом:

public GameModel(Game game,  string languageCode)
{
    Games_TX translate = game.Games_TX.FirstOrDefault(x => x.LanguageId == languageCode);

if (translate == null)
    translate = game.Games_TX.FirstOrDefault();

Complexity = game.Complexity;
Id = game.Id;
BackgroundFile = game.BookBackgroundFileName;

Name = translate.Name;
Description = translate.Description;
PlayersCount = translate.PlayersCount;

}

Таким образом можно локализовать 100% объектов в базе данных. При этом этот способ локализации позволяет добавить функцию локализации для объектов в базе. Для этого нужно написать небольшой скрипт автозаполнения _TX таблицы из основной. В общем случае для каждой бизнес модели потребуется своя структурированная таблица _TX, в которой будет хранится локализованная текстовая информация.

Ссылки

Комментарии

comments powered by Disqus