Пример создания многоязыкового приложения (XML)

Введение

Вопрос разработки многоязычного web – приложения (сайта) или приложения с поддержкой интерфейса пользователя на разных языках, поднимается довольно часто. Если еще пару лет назад данная задача была не так актуальна, то сейчас подобная функциональность де – факто стала одним из требований заказчика при создании web – ориентированного приложения..

Формулировка задачи

Целью данной работы является реализация компонента – переключателя поддерживаемых языков в web – приложении. При решении задачи хотелось бы учесть следующие моменты:

* добавление нового языка или удаление старого должно производится просто и быстро даже без привлечения разработчика;
* web – приложение должно автоматически формировать список поддерживаемых языков;
* так же исправление языковых файлов должно быть просто и доступно заказчику без привлечения разработчиков;
* данная функциональность не должна «тормозить» работу приложения.

Варианты решения

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

* Использование базы данных.
Суть метода решения заключается в том, что содержимое страницы хранится в базе данных. И подгружается по мере требования в соответствии с языком, выбранным пользователем.
Подробное описание данного способа и пример реализации рассматривался на www.aspnetmania.com в статье Вариант реализации многоязычной поддержки в ASP.NET приложении.
На мой взгляд самым главным недостатком (или не удобством) данного решения является необходимость реализации механизмов по внесению изменений в содержимое страниц.
* Использование файлов – ресурсов web – приложения.
Суть метода решения заключается в том, что статическое содержимое страниц (заголовки, надписи и т.д. и т.п.) находится в файлах ресурсов (одном или нескольких), к которому и идет обращение по мере необходимости.
Подробное описание данного способа и пример реализации также рассматривался на www.aspnetmania.com в статье Вариант реализации многоязычной поддержки в ASP.NET приложении. Еще один пример можно найти в книге «Освой самостоятельно ASP.NET за 21 день» (глава «День 19-й. Отделение кода от содержимого.»)
Данный подход к решению требует после внесения изменений в ресурсный файл производить перекомпиляцию ресурсов, что не всегда удобно или понятно для заказчика.
* Подгрузка необходимых языковых файлов на этапе выполнения (формирования) страницы.
Является давним способом еще при работе на PHP и ASP, где все фразы и надписи содержатся в текстовом файле в виде констант, которые подгружаются в начале страницы (деректива include…), а потом уже используются в серверном коде страницы.
Основным недостатком данного метода является большой размер подгружаемых файлов, что можно решить разделением одного большого файла на ряд маленьких, соответсующих либо странице приложения, либо модулю.
Именно этот вариант решения и предлагается к рассмотрению в данной статье с необходимой модернизацией.

Реализация

Основные положения реализации

* Языковые файлы находятся в корневой папке languages web – приложения, для каждого из поддерживаемых языков в своей подпапке (например, для русского языка в директории russian)
* Языковой файл представляет собой обычный текстовый файл в формате xml:

* Название языковых файлов совпадает с названием модуля web – приложения (или страницы) (например, для главного, основного модуля приложения system.xml)
* список поддерживаемых языков находится в файле config.xml системы. Сам файл имеет следующий вид:

* Для хранения настроек системы и выбора пользователя будем использовать переменные приложения (Application) и сеанса (Session) соответственно.

N.B.
При содании указанных xml – файлов убедительная просьба ИСКЛЮЧИТЬ ВСЕ КОММЕНТАРИИ, иначе файл не будет корректно считываться!

старт
Создаем новое ASP.NET web – приложение с названием… ну пусть будет example!

маленькие удобства
Добавим в класс Global несколько удобных процедур для работы с переменными приложения и сеанса:
файл Global.aspx.cs

using System;using System.Xml;

namespace example {  /// <summary>  /// Summary description for Global.  /// </summary>  public class Global : System.Web.HttpApplication  {    /// <summary>    /// Required designer variable.    /// </summary>    private System.ComponentModel.IContainer components = null;

    public Global()    {      InitializeComponent();    }

    /// <summary>    ///  xml - файл, содержащий глобальные настройки приложения    /// </summary>    private static XmlDocument xmlConfig = null;

    /// <summary>    /// процедура загрузки файла настроек приложения (запускается при запуске приложения)    /// </summary>    private void loadGlobalConfig()    {      try      {        // загружаем xml - файл настроек - config.xml        xmlConfig = new XmlDocument();        xmlConfig.Load(Server.MapPath("config.xml"));        setApplication("ApplicationConfig", xmlConfig);      }      catch      {        xmlConfig = null;      }    }

    /// <summary>    ///  функция получения переменной приложения по ее названию    /// </summary>    /// <PARAM name="name">название переменной приложения</PARAM>    /// <PARAM name="defValue">значение по - умолчанию</PARAM>    /// <returns>значение переменной приложения</returns>    public static object getApplication(string name, object defValue)    {            object result = null;      try      {        result = System.Web.HttpContext.Current.Application[name];      }      catch      {      }      if (result == null)      {        result = defValue;        setApplication(name, defValue);      }      return (result);    }

    /// <summary>    /// функция задания переменной приложения    /// </summary>    /// <PARAM name="name">название переменной</PARAM>    /// <PARAM name="newValue">новое значение</PARAM>    public static void setApplication(string name, object newValue)    {            try      {        System.Web.HttpContext.Current.Application[name] = newValue;      }      catch      {      }    }

    /// <summary>    ///  функция получения переменной сеанса по ее названию    /// </summary>    /// <PARAM name="name">название переменной сеанса</PARAM>    /// <PARAM name="defValue">значение по - умолчанию</PARAM>    /// <returns>значение переменной сеанса</returns>    public static object getSession(string name, object defValue)    {            object result = null;      try      {        result = System.Web.HttpContext.Current.Session[name];      }      catch      {      }      if (result == null)      {        result = defValue;        setSession(name, result);      }      return (result);    }

    /// <summary>    /// функция задания переменной сеанса    /// </summary>    /// <PARAM name="name">название переменной</PARAM>    /// <PARAM name="newValue">новое значение</PARAM>    public static void setSession(string name, object newValue)    {      try      {        System.Web.HttpContext.Current.Session[name] = newValue;      }      catch      {      }      return;    }

    protected void Application_Start(Object sender, EventArgs e)    {      // загрузка файла настроек      loadGlobalConfig();    }

    protected void Session_Start(Object sender, EventArgs e)    {

    }

    protected void Application_BeginRequest(Object sender, EventArgs e)    {

    }

    protected void Application_EndRequest(Object sender, EventArgs e)    {

    }

    protected void Application_AuthenticateRequest(Object sender, EventArgs e)    {

    }

    protected void Application_Error(Object sender, EventArgs e)    {

    }

    protected void Session_End(Object sender, EventArgs e)    {

    }

    protected void Application_End(Object sender, EventArgs e)    {

    }

    #region Web Form Designer generated code    /// <summary>    /// Required method for Designer support - do not modify    /// the contents of this method with the code editor.    /// </summary>    private void InitializeComponent()    {          this.components = new System.ComponentModel.Container();    }    #endregion  }}


переключатель языков
Создадим в нашем проекте пользовательский компонент userlanguages.
файл userlanguages.ascx:

файл userlanguages.ascx.cs:

using System;using System.Data;using System.Web;using System.Web.UI.WebControls;using System.Web.UI.HtmlControls;using System.Xml;

namespace example{

  /// <summary>  ///    компонент - переключаитель используемого языка интерфейса  /// </summary>  public class userlanguages : System.Web.UI.UserControl  {

    /// <summary>    /// константа - название команды для смены языка    /// </summary>    protected const string c_sUlChangeLanguage = "ChangeLanguage";

    /// <summary>    ///  функция построения таблицы со списком поддерживаемых языков    /// </summary>    protected void BuildTableLanguages()    {      // результат построения таблички      bool result = false;      // определяем выбранный язык по переменной сеанса      string selectedLanguage = (string)Global.getSession("UserLanguage", null);      // получаем файл настроек (считываем из переменной приложения)      XmlDocument xmlConfig = (XmlDocument)Global.getApplication("ApplicationConfig", null);      // если файл настроек инициализирован      if (xmlConfig != null)      {

        // считываем секцию настроек поддерживаемых языков        XmlNodeList xmlNodeList = xmlConfig.GetElementsByTagName("languages");        if (          (xmlNodeList != null)          &&          (xmlNodeList.Count == 1)          )        {          XmlNode xmlNode = xmlNodeList.Item(0);          XmlNode xmlLang = null;

          // пустая ли табличка или нет          bool tblClear = tblLanguages.Rows.Count == 0; // строка со списком языков          TableRow rowLang;  // пустая ли строка таблицы bool rowClear;          // ячейка с определенным языком

          TableCell cellLang; // пустая ли ячейка          bool cellClear;  // картинка - ссылка ImageButton imgLang;          string attrName = "";          if (xmlNode.HasChildNodes)            {            if (tblClear)            {              // если таблица пуста, то создаем ее содержимое              rowLang = new TableRow();            }            else            {              // берем существующий компонент              rowLang = tblLanguages.Rows[0];            }

            for (int i = 0; i < xmlNode.ChildNodes.Count; i++)            {              xmlLang = xmlNode.ChildNodes[i];              if (                (xmlLang != null)                &&                (xmlLang.Attributes.Count > 0)              )              {

                attrName = xmlLang.Attributes.GetNamedItem("name").Value;                rowClear = (rowLang.Cells.Count <= i);                if (rowClear)                {                  // если строка пустая, то создаем новую ячейку                  cellLang = new TableCell();                }                else                {                  // берем существующий компонент                  cellLang = rowLang.Cells[i];                }                cellClear = (cellLang.Controls.Count == 0);                if (cellClear)                {                  imgLang = new ImageButton();                }                else                {                  imgLang = (ImageButton)cellLang.Controls[0];                }                imgLang.Attributes.Clear();                imgLang.ImageAlign = ImageAlign.Middle;                imgLang.ImageUrl= "images/" + xmlLang.Attributes.GetNamedItem("image").Value;                imgLang.AlternateText = xmlLang.Attributes.GetNamedItem("description").Value;                if (                  (selectedLanguage == null)                  &&                  (xmlLang.Attributes.GetNamedItem("default") != null)                  &&                  (Boolean.Parse(xmlLang.Attributes.GetNamedItem("default").Value))                )                {                  selectedLanguage = attrName;                }                // выбран нужный язык                if (attrName.CompareTo(selectedLanguage) == 0)                {                  cellLang.BorderWidth = 1;                  cellLang.Enabled = false;                  imgLang.CommandName = imgLang.CommandArgument = "";                  imgLang.Command -= new CommandEventHandler(ImageButton_Command);                  // запоминаем папку используемого языка                  Global.setSession("UserLanguageFolder", xmlLang.Attributes.GetNamedItem("folder").Value);                }                else                {                  cellLang.BorderWidth = 0;                  cellLang.Enabled = true;                  imgLang.CommandName = c_sUlChangeLanguage;                  imgLang.CommandArgument = attrName;                  imgLang.Command += new CommandEventHandler(ImageButton_Command);                }

                imgLang.BorderWidth = cellLang.BorderWidth;

                if (cellClear)                {                  // если ячейка была пуста, то добавляем в нее компонент - картинку                  cellLang.Controls.Add(imgLang);                }                if (rowClear)                {                  // если строка была пуста, то добавляем в нее компонент - ячейку                  rowLang.Cells.Add(cellLang);                }              }            }            if (tblClear)            {              // если таблица была пуста, то добавляем в нее компонент - строку              tblLanguages.Rows.Add(rowLang);            }            tblLanguages.CellSpacing = 3;            tblLanguages.CellPadding = 0;            result = true;          }        }      }      tblLanguages.Visible = result;    }

    /// <summary>    /// таблица для отображения списка языков    /// </summary>    protected System.Web.UI.WebControls.Table tblLanguages;

    /// <summary>    /// обработчик события выбора языка интерфейса    /// </summary>    /// <PARAM name="sender"></PARAM>    /// <PARAM name="e"></PARAM>    void ImageButton_Command(object sender, CommandEventArgs e)     {      //  если команда - смены языка      if (e.CommandName == c_sUlChangeLanguage)      {        Global.setSession("UserLanguage", e.CommandArgument);        BuildTableLanguages();      }    }

    /// <summary>    ///  загрузка компонента    /// </summary>    /// <PARAM name="sender"></PARAM>    /// <PARAM name="e"></PARAM>    private void Page_Load(object sender, System.EventArgs e)    {      BuildTableLanguages();    }

    #region Web Form Designer generated code

    /// <summary>    ///  Инициализация компонента    /// </summary>    /// <PARAM name="e"></PARAM>    override protected void OnInit(EventArgs e)    {      // (автогенерация VS.NET)      InitializeComponent();      base.OnInit(e);      }

    /// <summary>    /// Инициализация используемых компонентов    /// </summary>    private void InitializeComponent()    {      // (автогенерация VS.NET)      this.Load += new System.EventHandler(this.Page_Load);    }    #endregion  }}


форматируем форму, т.е. формируем
Беремся за web – форму приложения WebForm1 (благо она создается всегда автоматически).
файл WebForm1.aspx:

файл WebForm1.aspx.cs:

using System;using System.Collections;using System.ComponentModel;using System.Data;using System.Drawing;using System.Web;using System.Web.SessionState;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.HtmlControls;using System.Xml;

namespace example{  /// <summary>  /// Summary description for WebForm1.  /// </summary>  public class WebForm1 : System.Web.UI.Page  {

    /// <summary>    /// название модуля    /// </summary>    protected string moduleName = "system";

    /// <summary>    ///  компонент - переключатель языков    /// </summary>    protected userlanguages usLang;

    /// <summary>    /// строки для отображения результатов загрузки ресурсов    /// </summary>    protected System.Web.UI.WebControls.Label title;    protected System.Web.UI.WebControls.Label author;    protected System.Web.UI.WebControls.Label hello;

    /// <summary>    ///  Коллекция строк - ресурсов    /// </summary>    protected System.Collections.SortedList slResource = new SortedList();

    /// <summary>    /// загрузка языкового файла    /// </summary>    public virtual void LoadResources()    {      // инициализация коллекции строк      slResource.Clear();

      // загрузка языкового файла      XmlDocument xmlLanguages = new XmlDocument();      try      {        string xmlPath = "languages/" + Global.getSession("UserLanguageFolder", "russian") + "/" + this.moduleName + ".xml";        xmlLanguages.Load(Server.MapPath(xmlPath));      }      catch      {        xmlLanguages = null;      }      if (xmlLanguages != null)      {        // получение секции с ресурсами         // в     качестве названия секции берется    "строковое"      представление класса - страницы.        //

      в данном случае ASP.WebForm1_aspx        XmlNodeList xmlSections =  xmlLanguages.GetElementsByTagName(this.ToString());        if      (          (xmlSections ! = null) && (xmlSections.Count == 1) )        { //  загрузка содержимого секции          XmlNode xmlSection= xmlSections[0];          XmlNode xmlItem = null;            for (int i = 0; i < xmlSection.ChildNodes.Count; i++)          {            xmlItem = xmlSection.ChildNodes[i];            // xmlItem.LocalName - название тега            // xmlItem.InnerText - его содержимое            // добавляем в коллекцию ресурсов            slResource.Add(xmlItem.LocalName, xmlItem.InnerText);          }        }      }    }

    /// <summary>    /// событие, соответствующее формированию готовой страницы    /// определяем его, чтобы перегружать ресурсы после смены языка    /// </summary>    /// <PARAM name="e"></PARAM>    protected override void OnPreRender(EventArgs e)    {      //  загрузка ресурсов      this.LoadResources();      this.Page_Load(this, e);      base.OnPreRender(e);    }

    private void Page_Load(object sender, System.EventArgs e)    {      // Put user code to initialize the page here      title.Text = (string)this.slResource["title"];      author.Text = (string)this.slResource["author"];      hello.Text = (string)this.slResource["hello"];

    }

    #region Web Form Designer generated code    override protected void OnInit(EventArgs e)    {      //      // CODEGEN: This call is required by the ASP.NET Web Form Designer.      //      InitializeComponent();      base.OnInit(e);    }

    /// <summary>    /// Required method for Designer support - do not modify    /// the contents of this method with the code editor.    /// </summary>    private void InitializeComponent()    {          this.Load += new System.EventHandler(this.Page_Load);    }    #endregion  }}



долгожданные результаты

Если приложение заработало с первого раза, тогда картинки можно не смотреть icon wink Пример создания многоязыкового приложения (XML)

Предложения модернизации

Так как ничего совершенного не бывает, то предложу следующие направления модернизации:

* На основании формы – примера сделать шаблон страницы, которую использовать в проектах.
* Изменить принцип разбиения на языковые файлы (например, только название страницы – для маленьких проектов).
* Т.к. событие OnPreRender переопределено только для того, чтобы результаты изменения языка интерфейса сразу сказывались при загрузке страницы (т.е. событие OnPageLoad вызывается снова); то, думаю, есть смысл искать другие варианты решения данной подзадачки.
* На результирующей картинке видно, что в файл можно добавлять «специализированные строки», обработка которых может быть реализована в базовом классе (причем, обработка на этапе загрузке ресурсов), что позволяет строить более одушевленные конструкции (например, строку «#user_name#» заменить на имя пользователя, полученное либо из базы данных, либо из пользовательских переменных).
* и т.д. и т.п.

Поделиться в соц. сетях

mailru Пример создания многоязыкового приложения (XML)
facebook Пример создания многоязыкового приложения (XML)
odnoklassniki Пример создания многоязыкового приложения (XML)
livejournal Пример создания многоязыкового приложения (XML)
googlebuzz Пример создания многоязыкового приложения (XML)

Также рекомендуем:

  1. Доступна первая бета-версия браузера Opera 11 Вчера для персональных компьютеров стала доступна бета-версия браузера Opera 11.. Основное отличие Opera 11 от предшествующих версий — появление принципиально новой системы вкладок с функцией группировки. Ранее вкладки с открытыми веб-страницами были расположены друг за другом, а теперь пользователям предоставлена возможность группировать вкладки по веб-сайтам или темам. Возможность группировки появляется, если мышью разместить одну вкладку [...]...
  2. Социальный поиск Google становится глобальным В последнее время в компании Google четко прослеживается тенденция к глобализации собственных поисковых сервисов.. Еще совсем недавно по всему миру был запущен новый поисковый алгоритм Panda, а уже сейчас появилось официальное сообщение о массовом внедрении социального поиска (Social search). Отныне на всех локальных версиях поиска Google в поисковую выдачу будут включаться социальные результаты – записи [...]...
  3. Работа с SQLite Введение SQLite – это реляционная база данных, запросы к которой можно осуществлять при помощи языка запросов SQL. База данных не поддерживает все особенности SQL и уступает в функциональности другим развитым СУБД, но вполне подходит для хранения и извлечения информации.. Отличие SQLite от MySQL и аналогичных СУБД Классические СУБД, такие как MySQL (а так же MS [...]...
  4. Отказ в обслуживании в Microsoft Internet Explorer Программа: Microsoft Internet Explorer 6.x Опасность: Средняя Наличие эксплоита: Да . Описание: Уязвимость позволяет удаленному пользователю вызвать отказ в обслуживании. Уязвимость существует в библиотеке mshtml.dll при обработке тега OBJECT. Удаленный пользователь может с помощью специально сформированной Web страницы вызвать отказ в обслуживании браузера. Примеры: http://lcamtuf.coredump.cx/iedie2-1.htmlhttp://lcamtuf.coredump.cx/iedie2-2.htmlhttp://lcamtuf.coredump.cx/iedie2-3.htmlhttp://lcamtuf.coredump.cx/iedie2-4.html URL производителя: www.microsoft.com Решение: Способов устранения уязвимости не существует в [...]...
  5. Вышли релизы PHP 5.1.2 и 4.4.2 В новом релизе PHP 5.1.2 исправлено более 80 ошибок, в том числе устранена проблема безопасности класса «Format string» в расширении mysqli. . Из новшеств может отметить: Помещение в состав поставки PHP расширений «hash» и XMLWriter. В интерфейс к библиотеке GD добавлена возможность генерации PNG в сжатом виде. SQLite библиотека обновлена с версии 2.8.17 до 3.2.8. [...]...
  6. Создание сайтов с возможностью печати PDF на примере PDF::AP Хотелось бы рассмотреть преимущества формата PDF (Portable Document Format), разработанного компанией Sun Microsystems, а также где и почему стоит использовать формат PDF при создании сайтов.Пожалуй, вам несколько раз встречалась необходимость печати документов прямо из Интернета. Это могут быть счета, квитанции, данные для печати на шаблоне.Возможно, вам также приходилось встречаться с особенностями печати подобных документов, оформленных [...]...
  7. API-спецификация баз данных языка Python, версия 2.0 Интерфейс модуля Доступ к базе данных реализуется с помощью объектов соединения (connection objects). Модуль должен предоставлять для них следующий конструктор:. connect(параметры…)Конструктор для создания соединения с базой данных. Возвращает Объект соединения. Имеет ряд параметров, которые зависят от базы данных. [1] Должны быть определены следующие глобальные переменные модуля: apilevelСтроковая константа, обозначающая поддерживаемый уровень DB API. В настоящее [...]...
  8. Переплетающиеся кольца Не так давно один из посетителей сайта спросил меня, как создать переплетающиеся кольца наподобие эмблемы Олимпийских Игр. Его просьба была реализована в Corel DRAW8, однако я не мог остановиться на достигнутом, поэтому здесь я познакомлю вас с вариантом того же урока для Corel DRAW9. Различие между уроками в том, что здесь были использованы Artistic Media [...]...
  9. 3D шар с текстурой Для начала нарисуйте круг, затем при помощи Ctrl+D сделайте его дубликат. Нижний круг залейте желаемой текстурой, а тот, что сверху, сделайте черным. Расположите оба круга точно один под другим (в этом вам поможет Arrange в верхнем меню). Важно, чтобы черный круг находился сверху. Сделайте контур обеих фигур невидимым.. Возьмите инструмент Interactive Transparency и в верхнем [...]...
  10. Правила хорошего поведения при обмене ссылками Обмен ссылками – один из самых распространенных способов привлечения посетителей на новый сайт.. Некоторые менеджеры сайта, самостоятельно занимающиеся раскруткой и продвижением сайтов, игнорируют данный способ раскрутки, мотивируя это тем, что подобный вид продвижения не принесет на сайт большое количество посетителей, и затраты времени на обмен ссылками с сайтами не принесут ожидаемой качественной и количественной отдачи, [...]...
  11. Преимущества локальной оптимизации Займитесь локальной оптимизацией. Зачастую оптимизации локального поиска не уделяется достаточно внимания, а между тем это отличный способ привлечения на ваш сайт местного трафика. Включив локальную информации в свои тэги и страницы, вы можете потенциальным клиентам найти специальную информацию по району и сфере деятельности, какие их интересует.. Что такое оптимизация локального поиска? Оптимизация локального поиска – [...]...
  12. Владимир Липка: веб-дизайн Все очень просто. Хороший веб-дизайн – это когда сайт не вызывает вопросов. Если на сайте невозможно отыскать нужную информацию, не ясна цель создания сайта, а внешний вид раздражает – это плохой веб-дизайн.. Дизайн – не искусство Задача искусства не давать ответы, его задача, скорее, в обратном – ставить вопросы. В отличие от искусства – дизайн [...]...
  13. Софт для вебмастера Как вы знаете, для создания вебстраниц, все вебмастера пользуются программами. А вот какими лучше всего пользоваться мы сейчас и разберем.. 1. Html-редакторы Macromedia HomeSite – весьма популярный html-редактор. Первое что бросается в глаза – это подсветка. Подсветка кода у него просто ужасная, т.к. в ней используются яркие цвета зеленого, синего, красного. Радует то, что цвета [...]...
  14. Продвижение сайта статьями Продвижение сайта статьями – это эффективная раскрутка web-ресурса в Интернете. Сейчас, когда Интернет бурно растет и развивается, важной целью для всех web-мастеров является раскрутка сайта. Среди различных видов раскрутки сайтов, особо выделяется продвижение сайта статьями. Этот метод приобрел такую популярность, потому что является лучшим способом раскрутки сайта в Интернете. . Суть продвижение сайта статьями является [...]...
  15. Текст из воды Step 1Создайте документ и напишите любой текст. Растеризуйте слой.. Используя кисть, добавьте несколько капель и подтеков к тексту. Объедините оба слоя Layer > Flatten Image. Step 2Идем в палитру каналов и создаем маску канала путем перетаскивания любого слоя Red, Green или Blue на иконку New Channel внизу палитры каналов. Называем его water. Далее инвертируем канал [...]...

Комментарии запрещены.