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

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

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

Пример создания многоязыкового приложения (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
  }
}
        

долгожданные результаты
Если приложение заработало с первого раза, тогда картинки можно не смотреть ;-)

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

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

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

Исходный код примера можно скачать здесь.


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


Автор: MEPhIst aka Mif
Прочитано: 4374
Рейтинг:
Оценить: 1 2 3 4 5

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

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

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