Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Все статьи »

Обсудить на форуме Обсудить на форуме

Вариант реализации многоязычной поддержки в ASP.NET приложении

1. Введение

 

Хм… Активные участника форумов мании наверняка не могли хотя бы уголком сознания не отметить тот факт, что обсуждение реализации многоязычной поддержки приложений последнее время является одной из самых популярных тем дискуссий. Осознание этого факта в совокупности с реалиями текущей моей работы, которая на данный момент включает ведение двух проектов, требующих реализации многоязычности, и подвигло меня на написание этой статьи.

 

2. Обзор возможных путей решения и формулировка некоторых терминов

 

2.1 Локализация и базы данных

 

Итак, какие же у нас варианты? Ну, поскольку любое современное динамическое веб-приложение просто немыслимо без взаимосвязи с СУБД, наверное, наиболее очевидный вариант решения – хранить варианты многоязычного представления одной и той же информации в базе данных. Действительно, а где ещё хранить значительные по объёму и периодически требующие изменений массивы информации, такие, как статьи, информация форумов, объявления, различные новостные и аналитические обзоры. Это тем более логично, что для единичной сущности варианты её локализации для некоторого количества языков можно считать разновидностью динамики этой сущности. Назовём описанный вид данных целевым динамическим контентом приложения (понятно, что это не единственный тип изменяемой информации, но если оценивать скорость изменения информации, отображаемой пользователю, данный вид информации подвержен наиболее частым изменениям). Как должна изменится структура хранения сущностей в базе данных в этом случае? Рассмотрим это на примере. На рисунке 2.1 изображена таблица, реализующая сущность “Документ”, предназначение которой сводится к хранению статических HTML и текстовых документов, которые требуют локализации.

 

Рисунок 2.1

 

Здесь наличествует 2 пути: первый – изменить первичный ключ таблицы, сделав его композитным – состоящим из полей DocumentId и LanguageId – рисунок 2.2:

 

Рисунок 2.2

 

 

Надо заметить, что меняется не только структура хранения данных – меняются так же и типы данных полей, хранящих локализуемую информацию – поля Title и Body теперь являются наборами Unicode символов. Итак, при указанной схеме данных мы можем хранить каждую версию локализации документа в отдельной строке таблицы. Но как быть, если структура данных документа потребует изменения: например, для сущности “Документ” необходимо будет добавить атрибуты “Автор”, “Дата создания”, “Дата истечения актуальности”. В таком случае описанная схема начинает представлять собой хранилище большого количества числа избыточных данных, которое ко всему прочему ещё и порождает ряд трудностей с их синхронизацией и изменением. Поэтому если нет абсолютно точной уверенности в том, что заданная структура сущности меняться не будет, либо изначально не требуется определённая денормализация данных, при реализации локализации динамического контента лучше пойти по другому пути – выделить локализацию в отдельную сущность (рисунок 2.3):

 

Рисунок 2.3

 

Схема достаточно наглядна, и, пожалуй, единственный момент, требующий пояснения – это поле Id таблицы tblDocumentLocalizations. Данное поле является автоинкрементным и реализует уникальный индекс таблицы. Поле Id не является необходимым, я его использую для удобства редактирования данных таблицы. Помимо целевого динамического контента любое приложение содержит различную вспомогательную информацию, требующую локализации – элементы управления, содержащие текстовую информацию, некоторые статические участки текста и т.п. сегменты текстовой информации, которые можно назвать вспомогательным динамическим контентом. “Статические участки текста” и “вспомогательный динамический контент”? На первый взгляд противоречие, но вспомним, что мы говорим о реализации многоязычной поддержки, и в самой формулировке этой задачи наличествует отрицание какой либо статичности текстовой информации – ведь любой статический текст, имеющий несколько языковых представлений, уже никак не может считаться статическим. При создании веб – приложений средней и большой сложности общее количество элементов, реализующих вспомогательный динамический контент может быть достаточно велико, и подзадачи реализации, поддержки и управления локализацией этого типа контента при помощи базы данных может потребовать значительное количество усилий, а также вылиться в значительную дополнительную нагрузку на сервер баз данных. Реализация локализованного отображения вспомогательного динамического контента непосредственно в коде приложения также является достаточно рутинным и не гибким решением. Как же быть? А очень просто – оказывается, разработчики платформы .NET изначально позаботились о предоставлении механизмов решения этой проблемы.

 

2.2 Satellite assemblies, файлы ресурсов и System.Resources

 

 

Satellite assemblies (если дословно, спутники-сборки), используются в .NET Framework для хранения скомпилированных локализованных ресурсов приложений (т.е. какой либо программный код в этих сборках отсутствует): строк, изображений, видео и т.п. двоичной информации, для работы же c ресурсами в .NET существует отдельное пространство имён System.Resources, из всех членов которого наиболее интересны в прикладном смысле следующие классы:

1. ResourceManager – используется для получения ресурсов текущей культуры из сборки – спутника.

2. ResourceSet – используется для доступа ко всем ресурсам конкретной культуры, экземпляр этого класса проходит по всем ресурсам конкретной культуры и сохраняет результаты в HashTable.

Давайте разберёмся, как можно использовать в своих приложениях satellite assemblies и извлекать из них данные (сразу скажу, что буду использовать для этого Visual Studio.NET, любителей же всё делать “ручками” отсылаю к описанию утилит al.exe и resgen.exe в MSDN, а также топику Creating Satellite Assemblies, расположенному там же). Итак, создадим новый проект LocalizingDemo, переименуем форму WebForm1.aspx в Default.aspx (а соответсвующий класс в Default), добавим на эту форму элемент управления Label, назвав её headerLabel, а затем посмотрим, что у нас видно на панели Solution Explorer (предварительно нажав кнопки его тулбара - Show All Files и Refresh) – рисунок 2.4:

 

 

Рисунок 2.4

 

Из рисунка видно, что добрая :) студия уже создала “умолчабельный” файл ресурсов в формате resx. ResX - это формат ресурсного файла, который позволяет хранить заданные сущности, такие как строки и объекты, в виде XML – тэгов, и соответственно, является XML файлом определённой схемы. Что бы в этом убедится, откроем его и выберем XML режим отображения.
Видно, что ResX файл состоит из двух частей – xsd схемы, задающих структуру данных файла, и непосредственно данных, представляющих набор заголовков ресурсного файла (<RESHEADER/>) и набор данных (<DATA/>), представляющих собой пары имя/значение, структурированные согласно указанной xsd – схеме:

 

         <data name = "Имя">
         

            <value> Значение </value>
         

         </data>
               

 

Ресурсный файл Default.aspx.resx является нейтральным ресурсным файлом, предназначенным для хранения ресурсов, не зависящих от локализации. Добавим в проект ресурсный файл с заданной культурой и регионом “Английский - США” (File -> Add New Item -> Assembly Resource File), и назовём его Default.en-US.resx (свойство Build Action должно быть установлено в Embedded Resource), затем скомпилируем приложение. Мы увидим, что структура директории bin нашего приложения изменилась (рисунок 2.5):

 

Рисунок 2.5

 

Появился подкаталог en-US, а в нём файл LocalizingDemo.resources.dll – та самая satellite assembly, которая скомпонована с основной сборкой приложения, и которая будет содержать в себе все файлы ресурсов, принадлежащие к данной культуре / региону. Сочетание en-US, которое мы использовали при именовании файла ресурсов для класса Default, и которое студия использовала для формирования подкаталога директории bin – это не что иное, как именование культуры согласно стандарту RFC 1766: <код языка> - <страна/код региона>, где <код языка> – это двухбуквенное обозначение в стандарте ISO 639-1 (международный стандарт двухбуквенного обозначения языка), выполненное символами в нижнем регистре, а <страна/код региона> - двухбуквенное обозначение в стандарте ISO 3166 (международный стандарт двухбуквенного обозначения языка / региона), выполненное символами в верхнем регистре. (Ещё раз хочу подчеркнуть, что студия значительно упрощает создание, размещение (помещение в соответствующую директорию) и компоновку сборок спутников, при работе через консоль для достижения описанного эффекта надо выполнить ряд довольно нудных операций.) Таким образом, добавленный нами ресурсный файл соответствует культуре – региону английский – США. Добавим в этот ресурсный файл строковое значение для отображения элемента headerLabel (рисунок 2.6):

Рисунок 2.6

 

А в событие Page_Load страницы добавим следующий код:

 

private void Page_Load(object sender, System.EventArgs e)
{
   //    Задаём культуру "en-US" для текущего потока запроса пользователя 
   Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");

   //    Задаём культуру "en-US", используемую ResourceManager  для поиска 
   // локализованных ресурсов
   Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture("en-US");

   //    Задаём кодировку контента ответа на запрос пользователя
   this.Response.ContentEncoding = Encoding.GetEncoding(Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage);         

   //    Создаём экземпляр класса ResourceManager для данного класса и данной сборки
   ResourceManager _resourceManager = new ResourceManager(this.GetType().BaseType.FullName,
                                                            this.GetType().BaseType.Assembly);

   //    Получаем локализованное значение из локализованной сборки ресурсов
   this.headerLabel.Text = _resourceManager.GetString("headerLabel");
}



            

 

Компилируем, запускаем и в итоге получаем приблизительно такой результат (рисунок 2.7):

 

Рисунок 2.7

 

Код обработчика события достаточно подробно прокомментирован, и останавливаться на нём не стоит, единственно, на что стоит обратить внимание, это на то, что метод ResourceManager.GetString имеет перегруженную версию, которая в качестве второго параметра принимает экземпляр CultureInfo, т.е. в принципе мы могли не менять Thread.CurrentThread.CurrentCulture и Thread.CurrentThread.CurrentUICulture, а просто написать: this.headerLabel.Text = _resourceManager.GetString("headerLabel", СultureInfo.CreateSpecificCulture("en-US")); но, поскольку обычно требуется однотипная локализация всех элементов страницы / пользовательского элемента управления, использование данной версии перегрузки метода неоптимально (по крайней мере, имхо :) ). Однако никому не нужна локализация под один язык / культуру, поэтому добавим в проект ресурсный файл Default.ru-RU.resx, для хранения в нём ресурсов русскоязычной локализации, и добавим параметр отображения headerLabel (Рисунок 2.8):

 

Рисунок 2.8

 

затем добавим на страницу два экземпляра HyperLink (я приведу код всей страницы, поскольку она невелика).

а код события Page_Load приведём к виду:

 

private void Page_Load(object sender, System.EventArgs e)
{

   if (this.Request.QueryString["Culture"] == null)
   {
      return;
   }
   
   string _sCulture = this.Request.QueryString["Culture"];
   
   //    Задаём культуру для текущего потока запроса пользователя 
   Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(_sCulture);
   
   //    Задаём культуру, используемую ResourceManager  для поиска 
   // локализованных ресурсов
   Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(_sCulture);

   //    Задаём кодировку контента ответа на запрос пользователя
   this.Response.ContentEncoding = Encoding.GetEncoding(Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage);         

   //    Создаём экземпляр класса ResourceManager для данного класса и данной сборки
   ResourceManager _resourceManager = new ResourceManager(this.GetType().BaseType.FullName,
                                                            this.GetType().BaseType.Assembly);

   //    Получаем локализованное значение из локализованной сборки ресурсов
   this.headerLabel.Text = _resourceManager.GetString("headerLabel");
}
            

 

Теперь в зависимости от нажатия на ту или иную ссылку, мы получаем для отображения элемента headerLabel строковое значение из той или иной локализованной сборки (Рисунок 2.9 ):

 

Рисунок 2.9

 

Итак, мы получили вполне рабочее приложение, демонстрирующее работу механизма локализации в .NET, и на этом вполне бы можно было закончить этот раздел, но мне бы хотелось осветить ещё один аспект специфики работы класса ResourceManager. Добавим в проект файл ресурсов Default.en.resx, и добавим в него тe же пару имя / значение, что и в Default.en-US.resx для элемента headerLabel, а из Default.en-US.resx удалим эту пару (или удалим этот файл вовсе). При перекомпиляции и запуске приложения мы обнаружим, что его поведение ничуть не изменилось. Идём дальше – в файл Default.aspx.resx добавим всё ту же пару имя/значение, а из Default.en.resx удалим её (либо удалим сам файл). Ещё раз компилируем и запускаем – приложение работает по прежнему неизменно. Это объясняется спецификой работы экземпляра класса ResourceManager – не найдя требуемого значения в сборке ресурсов заданной культуры / региона (в нашем случае это en-US), он пытается найти эти данные сначала в сборке ресурсов культуры без указания региона (en), а затем в сборке нейтральных ресурсов. Этот механизм весьма логичен, и позволяет рационально распределять всю локализуемую информацию по ресурсам таким образом, чтобы, например, для культур/регионов en-US (США) и en-GB (Великобритания) хранить общую информацию в сборке культуры bin /en/AssemblyName.resources.dll, а различия соответсвенно в сборках bin/en-US/AssemblyName.resources.dll и bin/en-GB/AssemblyName.resources.dll, ресурсы же независимые от локализации можно хранить в нейтральной сборке.

 

3. Реализация (одностраничного) приложения с многоязычной поддержкой.

 

Сразу скажу, что здесь я буду говорить именно о локализации до уровня языка, а не региона, ибо подобные задачи мне пока решать не доводилось, но принципы построения приложения, локализованного до уровня региона идентичны и описанное решение можно с лёгкостью расширить до требуемого уровня локализации. Для начала хочется привести диаграмму структуры хранения данных локализуемого одностраничного портала (Рисунок 3.1):

 

Рисунок 3.1

 

Структура и назначение таблиц tblPages, tblModules, tblContainers и tblPageConfigurations схожи с теми, что я описывал в статье “Создание простого одностраничного портала” , tblModuleDefinitions и tblDocuments – уже влияние IBuySpy Portal (у меня всё-таки дошли до него руки :) ). Таблица tblDocuments и её потомок tblDocumentLocalizations единственные из этих таблиц, не относящихся к стуктурным данным самого портала, и показаны на диаграмме просто в качестве представителей информационной части сайта, в реальном случае это естественно могут быть также таблицы анонсов, форумов, и т.п. Как видно из диаграммы, каждая сущность, имеющая информацию для отображения, имеет дополнительную таблицу локализации, и все таблицы локализаций имеют отношения (:) !) многие к одному с таблицей tblLanguages. Описание основных полей таблицы tblLanguages приведено на рисунке 2.3., единственно, хочется пояснить, что поле Alias служит для хранения обозначения языка в стандарте ISO 639-1. Итак, первое, что хочется сделать – это при старте приложения получить информацию о всех языках, поддерживаемых приложением. Соответсвенно, в Global.asax.cs пишем:

 

protected void Application_Start(Object sender, EventArgs e)
{
   this.Application.Add("AvailableLanguages", this.localizationsData.GetLanguages());
}

 

LocalizationsData.GetLanguages() – это метод бизнес компонента, который возвращает в виде HashTable результат выполнения вот такой хранимой процедуры:

 

CREATE PROCEDURE dbo.spSelectLanguages
AS
BEGIN

   SELECT Alias         AS LanguageAlias,
          EnglishName   AS LanguageName
      FROM tblLanguages
         
END
            

 

Если приложение имеет относительно высокую скорость динамики, возможно, имеет смысл проделывать эту операцию не только при старте приложения, но и периодически в период выполнения. Далее, при каждом запросе пользователя мы должны определять, информацию на каком языке он хочет получить:

 

protected void Application_BeginRequest(Object sender, EventArgs e)
{
   string _sUserLanguage;
   bool   _bLanguageCookieExist = false;
   
   //    Проверка наличия cookie выбора языка пользователем
   if (this.Request.Cookies["Language"] == null)
   {
      Hashtable _languageHashtable = (Hashtable)this.Application["AvailableLanguages"];
      
      //    Проверка наличия языка по умолчанию броузера клиента среди 
      // поддерживаемых сайтом
      if (_languageHashtable[this.Request.UserLanguages[0].Substring(0,2)] != null)
      {
         _sUserLanguage = this.Request.UserLanguages[0].Substring(0,2);                     
      }
      else
      {
         //    Если язык по умолчанию отсутсвует, выбираем отображение на английском
         _sUserLanguage = "en";
      }
   }
   else
   {
      _sUserLanguage = this.Request.Cookies["Language"].Value;
      _bLanguageCookieExist = true;
   }
   
   //    Задаём культуру для текущего потока запроса пользователя 
   Thread.CurrentThread
         .CurrentCulture   = CultureInfo.CreateSpecificCulture(_sUserLanguage);

   //    Задаём культуру, используемую ResourceManager  для поиска 
   // локализованных ресурсов
   Thread.CurrentThread
         .CurrentUICulture = CultureInfo.CreateSpecificCulture(_sUserLanguage);

   //    Задаём кодировку контента ответа на запрос пользователя         
   this.Response.ContentEncoding = 
      Encoding.GetEncoding(Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage);

   
   if (!_bLanguageCookieExist)
   {
      //    Задаём значение cookie, хранящую выбор языка пользователем
      this.Response.Cookies["Language"].Value = 
         Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
      
      this.Response.Cookies["Language"].Expires = DateTime.Now.AddYears(5);
   }
}

 

Здесь в общем, всё достаточно просто, единственно, на что хочется обратить на то, что лучше не использовать выражения типа Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(this.Request.UserLanguages[0]); - какая - нибудь несознательная Opera в качестве this.Request.UserLanguages[0] может отдать строку вроде “en;q=0,1”, что гарантировано поднимет исключение – т.к. передаваемый CultureInfo.CreateSpecificCulture параметр должен быть строкой в стандарте RFC 1766. Имея приведённый выше код в Global.asax.cs, а также какой – нибудь элемент управления, устанавливающий по желанию пользователя cookie определения языка, мы получим систему централизованного управления и поддержки локализации пользователя многоязычного приложения. Соответственно, в любой момент выполнения приложения мы можем получать целевой динамический контент с сервера базы данных в соответствии с текущей языковой настройкой пользователя, и корректно его отобразить. Что же касается вспомогательного динамического контента, хранящегося в satellite assemblies, то согласитесь, что для каждого контрола каждой страницы или пользовательского элемента управления подобный код писать довольно утомительно, поэтому я использую слегка переработанный класс, помещённый Дмитрием в кодохранилище, который реализует локализацию страницы или пользовательского элемента управления целиком:

 

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Globalization;
using System.Threading;
using System.Resources;
using System.Collections.Specialized;


namespace Volortho.Configuration
{
	/// <summary>
	///   Инкапсуляция статических методов для локализации элементов управления 
	/// страницы / пользовательского элемента управления
	/// </summary>
	public class WebUILocalizer
	{
      #region Private static methods 
      
      /// <summary>
      /// Загружает коллекцию ключ/значение из файла ресурсов локализуемого элемента
      /// </summary>
      /// <PARAM name="mainControl"> 
      ///   Целевой объект локализации, обычно 
      /// наследник System.Web.UI.Page или System.Web.UI.UserControl
      /// </PARAM>
      /// <PARAM name="exceptions"> 
      ///   Набор неких дополнительных параметров, хранящихся  в 
      /// файле ресурсов  
      /// </PARAM>
      /// <returns>
      /// Коллекция ключ/значение из файла ресурсов локализуемого элемента
      ///</returns>
      private static NameValueCollection LoadLabels(Control mainControl, string[] sExceptions)
      {
         string _sBaseName = mainControl.GetType().BaseType.FullName;
         string _sTwoLetterISOLanguageName = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
         
         NameValueCollection _labelsCollection = 
            (NameValueCollection)HttpContext.Current.Cache[_sBaseName + "_" + _sTwoLetterISOLanguageName];

         if (_labelsCollection == null)  
         {
         
         ResourceManager _resourceManager = 
            new ResourceManager(_sBaseName,typeof(WebUILocalizer).Assembly); 
         
         _labelsCollection = new NameValueCollection(); 
         
         FillResourcesCollection(mainControl,  _resourceManager, _labelsCollection); 
         
         if(sExceptions != null && sExceptions.Length >  0)
         {
            foreach(string ex in sExceptions)
            {
               _labelsCollection.Add(ex, _resourceManager.GetString(ex));
            }
         }
            
         HttpContext.Current.Cache.Insert(_sBaseName + "_" + _sTwoLetterISOLanguageName,
            _labelsCollection, null, DateTime.MaxValue, TimeSpan.FromMinutes(2));
         }

         return _labelsCollection;
      }
     
      /// <summary>
      /// Заполняет коллекцию ключ/значение из файла ресурсов локализуемого элемента
      /// </summary>
      /// <PARAM name="control"> 
      ///   Целевой объект локализации, обычно 
      /// наследник System.Web.UI.Page или System.Web.UI.UserControl
      /// </PARAM>
      /// <PARAM name="resourceManager"> 
      /// Менеджер ресурсов текущей локализации 
      /// </PARAM>
      /// <PARAM name="labelsCollection">
      /// Коллекция ключ/значение из файла ресурсов локализуемого элемента
      /// </PARAM>
      private static void FillResourcesCollection
		(Control control, ResourceManager resourceManager, NameValueCollection labelsCollection)
      {
         foreach(Control _childControls in control.Controls)
         {
            FillResourcesCollection(_childControls, resourceManager, labelsCollection);
         }

         if (control is WebControl)
         {
            string _sKey = control.ID;
            string _sVal = resourceManager.GetString(_sKey);
            
            if(_sVal != null)
            {
               if(_sVal != "array")
               {
                  labelsCollection.Add(_sKey, _sVal);
               }
               else
               {
                  labelsCollection.Add(_sKey, _sVal);
                  int _iArrayItemCounter = 1;
                  
                  while(resourceManager.GetString(_sKey + "_" + _iArrayItemCounter.ToString()) != null)
                  {
                     labelsCollection.Add(_sKey + "_" + _iArrayItemCounter.ToString(), 
                                          resourceManager.GetString(_sKey + "_" + _iArrayItemCounter.ToString()));
                     _iArrayItemCounter++;
                  }
               }
            }
         }
      }

      /// <summary>
      ///   Задаёт соответствующим элементам управления локализованные значения свойств,
      /// извлекая их из коллекции labelsCollection  
      /// </summary>
      /// <PARAM name="сontrol"> 
      ///   Целевой объект локализации
      /// <PARAM name="labelsCollection">
      /// Коллекция ключ/значение из файла ресурсов локализуемого элемента
      /// </PARAM>
      private static void SetLabels(Control control, NameValueCollection labelsCollection)
      {
         foreach(Control _childControl in control.Controls)
         {
            SetLabels(_childControl, labelsCollection);
         }
         
         if(control is WebControl)
         {
            string _sKey = control.ID;
            string _sVal = labelsCollection[_sKey];
            
            if(_sVal != null)
               switch(control.GetType().Name)
               {
                  case "Label":
                     ((Label)control).Text = _sVal;
                     break;

                  case "CheckBox":
                     ((CheckBox)control).Text = _sVal;
                     break;

                  case "TextBox":
                     ((TextBox)control).Text = _sVal;
                     break;

                  case "LinkButton":
                     ((LinkButton)control).Text = _sVal;
                     break;

                  case "Button":
                     ((Button)control).Text = _sVal;
                     break;

                  case "HyperLink":
                     ((HyperLink)control).Text = _sVal;
                     break;

                  case "DropDownList":

                  case "RadioButtonList":

                  case "CheckBoxList":

                  case "ListBox":
                     ListControl _listControl = (ListControl)control;
                     int _iItemCounter = 0;

                     foreach(ListItem _listItem in _listControl.Items)
                     {
                        _listItem.Text = labelsCollection[_sKey + "_" + (_iItemCounter + 1).ToString()];
                        _iItemCounter++;
                     }
                     break;

                  case "RequiredFieldValidator":
                  case "RegularExpressionValidator":
                  case "CusomValidator":
                  case "CompareValidator":
                     ((BaseValidator)control).ErrorMessage = _sVal;
                     break;
               }
         }
      }

      #endregion Private static methods 
      
      
      #region Public static methods 

      /// <summary>
      ///   Локализует элемент управления в соответствии с текущей культурой
      /// </summary>
      /// <PARAM name="mainControl"> 
      ///   Целевой объект локализации, обычно 
      /// наследник System.Web.UI.Page или System.Web.UI.UserControl
      /// </PARAM>
      /// <PARAM name="exceptions"> 
      ///   Набор неких дополнительных параметров, хранящихся  в 
      /// файле ресурсов  
      public static void Localize(Control mainControl, string[] sExceptions)
      {
         if (Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName == "en")
         {
            return;
         }

         WebUILocalizer.SetLabels(mainControl, 
                                  WebUILocalizer.LoadLabels(mainControl, sExceptions));
      }

      /// <summary>
      ///   Локализует элемент управления в соответствии с текущей культурой
      /// </summary>
      /// <PARAM name="mainControl"> 
      ///   Целевой объект локализации, обычно 
      /// наследник System.Web.UI.Page или System.Web.UI.UserControl
      /// </PARAM>
      public static void Localize(Control mainControl)
      {
         if (Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName == "en")
         {
            return;
         }
         
         WebUILocalizer.SetLabels(mainControl, 
                                  WebUILocalizer.LoadLabels(mainControl, null));
      }

      #endregion Public static methods       
   }
}


 

Для его использования достаточно поместить такой код в класс локализуемого объекта:

 

private void Page_Load(object sender, System.EventArgs e)
{
   if (!this.IsPostBack)      
   {
      WebUILocalizer.Localize(this);            
   }
}

 

 

4. Заключение.

 

Ну вот пожалуй, и всё. Насколько я помню, кто - то из участников форума недавно продекларировал намерение написать статью со схожей тематикой. Пусть он не обижается на меня за невольное опережение: даже если предположить что то, что мы собираемся отразить в наших статьях лишь немного отличается по подходу к решению, смыслу или видению ситуации (а исходя из общих соображений различий должно быть более чем немного), это уже даст предполагаемому читателю больше информации и пищи для размышлений, нежели одна статья, написанная одним человеком.


Может пригодится:


Автор: Озорнин Артем
Прочитано: 6243
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру