Photo by Brett Zeck on Unsplash

Поддержка нескольких языков в приложении Windows Forms

User Rating: 0 / 5

Photo by Brett Zeck on Unsplash | Использованное фото к статье от Brett Zeck на Unsplash

Всем привет.

В сегодняшней статье я расскажу про один из возможных вариантов поддержки нескольких языков в приложении Windows Forms, а в качестве языка программирования для демонстрационного приложения, которое мы разработаем в рамках статьи, будет использован язык C#. Также отмечу, что примерно аналогичную методику я использовал при разработке приложения AINSideBar, где была поддержана локализация на русский, английский, немецкий и китайский языки.

Итак, что мы разберём в рамках этой статьи:

  • разработаем простое приложение для Windows Forms, которое будет приветствовать пользователя по имени, введённому в текстовом поле на главной форме. Приложение будет поддерживать локализацию на следующие языки, в качестве примера: русский, белорусский, немецкий, английский, китайский, испанский и португальский. Аналогичным образом при необходимости вы сможете поддержать в своих программах любое требуемое вам количество других языков;
  • разработанное приложение будет использовать в качестве ссылки отдельную библиотеку классов, содержащую ресурсы, представляющие собой строки, переведённые на указанные языки. Эти строки - различные сообщения, которые программа будет использовать в своей работе, причём они не относятся к элементам главной формы приложения. Сами же элементы на главной форме приложения мы переведём на разные языки, используя другой способ;
  • приложение будет хранить настройку "последний использованный в программе язык" и считывать её при загрузке, сразу переводя интерфейс программы на этот язык;
  • программа сможет переключаться между поддерживаемыми в ней языками без необходимости перезапуска приложения;
  • посмотрим, как можно использовать свойство Tag для элемента ToolStripMenuItem, чтобы хранить метаинформацию о языке, связанном с этим пунктом меню.

Для понимания материала статьи и описанных в ней шагов предполагается, что читатель:

  • уже знаком с языком C# и имеет некоторый опыт написания программ на C#, умеет создавать приложения Windows Forms в среде разработки Microsoft Visual Studio
  • понимает, где располагается "Панель элементов" в среде разработки при создании приложения Windows Forms, а также как размещать на форме элементы управления, настраивать их свойства и создавать методы-обработчики для событий.  

Если по перечисленным выше пунктам оказывается, что вы пока только начинаете знакомство с языком C# и пока не писали программ для Windows Forms, то перед этой статьей я бы порекомендовал для начала прочитать другие статьи сайта, в которых более детально описываются шаги по созданию нового проекта для Windows Forms, расположению на форме новых элементов, изменению их свойств и созданию методов-обработчиков для событий. В этих статьях также объясняется, как работать с некоторыми основными элементами управления (ButtonTextBox). Ниже привожу ссылки на эти статьи:

Также если вы не планируете самостоятельно проходить по шагам статьи и создавать "с нуля" описанное в статье приложение, а хотите просто изучить уже готовый код, в конце статьи вы найдете ссылку на архив с полностью готовым примером.

Посмотрим на внешний вид приложения, которое у нас получится.

Сама программа будет содержать текстовое поле для ввода имени и всего две кнопки - для приветствия пользователя по имени и выхода из программы:

По нажатию на кнопку приветствия будет выводиться простое сообщение:

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

Так будет выглядеть форма при переключении на немецкий язык и после ввода имени в текстовом поле:

По нажатию на кнопку приветствия будет выведено сообщение уже на немецком языке:

Как видите, приложение довольно простое, однако в ходе его разработки мы поймем основные шаги и различные нюансы, связанные с локализацией приложений Windows Forms.

Итак, начнём с разработки нового проекта в среде разработки.

1. Создание нового проекта и настройка главной формы

Создадим в среде разработки Microsoft Visual Studio новый проект с типом "Приложение Windows Forms (.NET Framework)" и назовём его MultiLangSupportExample. Местоположение проекта выбирайте на свой вкус, я предпочитаю оставлять настройки по умолчанию.

После того, как проект был создан, переименовываем имя файла для главной формы со стандартного имени Form1.cs на FrmMain.cs, соглашаясь во всплывающем диалоге на переименование всех связанных ссылок.

Теперь установим следующие свойства для главной формы FrmMain:

  • FormBorderstyle: FixedSingle
  • Text: [Allineed.Ru] Пример приложения на C# с поддержкой разных языков
  • Size: 629; 228
  • StartPosition: CenterScreen
  • Name: FrmMain (уже должно быть таким)
  • Localizable: True
  • MaximizeBox: False
  • MinimizeBox: False

Значение CenterScreen для свойства StartPosition позволяет отображать форму по центру экрана при запуске приложения. Значения False для MaximizeBox и MinimizeBox позволяют скрыть кнопки максимизации и минимизации окна главной формы. Одним из наиболее важных свойств главной формы в контексте этой статьи является свойство Localizable, которое мы устанавливаем в True, что позволит хранить отдельную локализацию для каждого из поддерживаемых языков для всех элементов формы.

Теперь перетащим из панели элементов на главную форму элемент ToolStripContainer и установим для него следующие свойства:

  • Dock: Fill
  • Name: ToolStripContainerMain

Значение Fill для свойства Dock позволяет элементу полностью заполнить пространство главной формы.

Далее перетащим из панели элементов на форму одну метку Label, одно текстовое поле TextBox и две кнопки Button.

Установим им следующие свойства.

Метка (Label):

  • Text: Введите Ваше имя:
  • Location: 12; 20
  • Name: LabelName

Текстовое поле (TextBox):

  • Location: 15; 45
  • Size: 586; 20
  • Name: TextBoxName

1-я кнопка (Button):

  • Text: &Приветствие
  • Location: 188; 108
  • Size: 115; 23
  • Name: ButtonSayHello

2-я кнопка (Button):

  • Text: &Выход
  • Location: 309; 108
  • Size: 115; 23
  • Name: ButtonExit

Следующим шагом перетащим из панели элементов элемент MenuStrip, причём при перетаскивании помещаем не в центр главной формы, а внутрь верхней панели только что размещённого элемента ToolStripContainer, таким образом в "шапке" формы у нас появится меню.

Теперь введём название &Файл для верхнего элемента меню и названия для вложенных в него элементов меню - &О программе, Язык и &Выход. В итоге должно получится заполненное меню, как показано на рисунке ниже (только напротив пункта меню "Язык" в вашем случае не будет чёрной стрелки - она появится чуть позже, при добавлении вложенных подпунктов меню):

 

Далее нужно выделить кликом мыши пункт меню Язык, и в появившейся справа от него области последовательно набрать названия для вложенных подпунктов меню: Русский, Белорусский, Немецкий, Английский, Китайский, Испанский, Португальский.

Должна получиться следующая картина:

 

Теперь осталось настроить значения для свойства Name для каждого добавленного пункта меню, а также пунктам меню, отвечающим за языки, мы дополнительно проставим значения для свойства Tag (далее разберём, для чего это нужно).

Последовательно кликаем левой кнопкой мыши на каждом пункте меню, проставляя для него в окне свойств следующие значения для свойства Name (а также для подпунктов меню Язык значения для свойства Tag):

  • для пункта меню &Файл
    • Name: FileToolStripMenuItem
  • для пункта меню &О программе
    • Name: FileAboutToolStripMenuItem
  • для пункта меню Язык:
    • Name: FileLanguageToolStripMenuItem
  • для пункта меню Русский
    • Name: FileLanguageRUToolStripMenuItem
    • Tag: ru
  • для пункта меню Белорусский
    • Name: FileLanguageBEToolStripMenuItem
    • Tag: be
  • для пункта меню Немецкий
    • Name: FileLanguageDEToolStripMenuItem
    • Tag: de
  • для пункта меню Английский
    • Name: FileLanguageENToolStripMenuItem
    • Tag: en
  • для пункта меню Китайский
    • Name: FileLanguageZHToolStripMenuItem
    • Tag: zh
  • для пункта меню Испанский
    • Name: FileLanguageESToolStripMenuItem
    • Tag: es
  • для пункта меню Португальский
    • Name: FileLanguagePTToolStripMenuItem
    • Tag: pt
  • для пункта меню &Выход
    • Name: FileExitToolStripMenuItem

На этом расположение всех элементов формы и пунктов меню готово.

Теперь мы добавим в наше решение MultiLangSupportExample дополнительный проект с типом "Библиотека классов (.NET Framework)", который будет содержать необходимые ресурсы (строки) для вывода различных сообщений в программе на соответствующем языке.

2. Добавление к решению нового проекта (MultiLangSupportResources) для хранения локализованных строк

В дереве элементов окна "Обозреватель решений" (обычно располагается сверху-справа) кликнем правой кнопкой мыши по корневому элементу Решение "MultiLangSupportExample" и в контекстном меню выберем Добавить → Создать проект...

В открывшемся окне с выбором шаблона для нового проекта выбираем "Библиотека классов (.NET Framework)" и нажимаем кнопку Далее. На следующем шаге в качестве имени проекта набираем MultiLangSupportResources и жмём кнопку Создать.

В результате к решению будет добавлен новый проект с именем MultiLangSupportResources, который по умолчанию будет содержать один пустой класс с именем Class1.cs. Этот класс мы сразу удаляем, поскольку в нашем случае он не потребуется: в созданном проекте в рамках статьи мы планируем хранить исключительно ресурсы для локализации программы на разные языки.

Создадим в новом проекте каталог с именем Resources, для этого кликаем по проекту правой кнопкой мыши и выбираем Добавить → Создать папку и задаём ей имя Resources. Теперь в эту папку добавим первый файл ресурсов. Для этого кликаем по ней правой кнопкой мыши и выбираем Добавить → Создать элемент. В открывшемся окне добавления нового элемента выбираем тип элемента - Файл ресурсов, а в качестве имени задаём ему MultiLangSupportExampleI18N.resx. Этот файл ресурсов будет являться для нас "эталонным": он будет хранить локализацию строк в стандартном языке нашей программы, используемом по умолчанию, и в этот файл ресурсов мы сейчас добавим несколько строк с их значениями, а затем скопируем этот файл столько раз, сколько всего языков мы хотим поддержать в нашей программе.

Дважды кликаем левой кнопкой мыши по файлу ресурсов и вбиваем в появившейся таблице следующие значения в столбцах Имя и Значение:

Имя Значение
AboutText Программа на C# от авторов сайта Allineed.Ru, демонстрирующая поддержку нескольких языков в приложении Windows Forms
AboutTitle О программе
AreYouSureYouWantToExit Вы уверены, что хотите выйти из приложения?
ExitAppTitle Выход из приложения
Greeting Привет,

 

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

После этого нажмём комбинацию клавиш Ctrl+S, чтобы сохранить все наши изменения, выполненные с файлом ресурсов.

Следующий важный шаг - необходимо "расклонировать" этот файл ресурсов, копируя его и вставляя в каталог Resources и задавая каждый раз новое имя, в котором будет содержаться код поддерживаемого языка. Подробнее про перечень кодов языков можно прочитать на Википедии. В нашей программе всего 7 языков, и их коды по сути мы уже с вами указывали ранее в свойстве Tag для пунктов меню. Итак, при клонировании исходного (эталонного) файла ресурсов MultiLangSupportExampleI18N.resx должны получиться следующие 6 дополнительных файлов:

  • MultiLangSupportExampleI18N.be.resx - для поддержки перевода сообщений на белорусский язык
  • MultiLangSupportExampleI18N.de.resx - для поддержки перевода сообщений на немецкий язык
  • MultiLangSupportExampleI18N.en.resx - для поддержки перевода сообщений на английский язык
  • MultiLangSupportExampleI18N.zh.resx - для поддержки перевода сообщений на китайский язык
  • MultiLangSupportExampleI18N.es.resx - для поддержки перевода сообщений на испанский язык
  • MultiLangSupportExampleI18N.pt.resx - для поддержки перевода сообщений на португальский язык

Обратите внимание на коды языков в имени каждого файла, которые идут перед расширением .resx и основной частью имени файла MultiLangSupportExampleI18N, отделяясь от него точкой.

Warning icon by Icons8ВНИМАНИЕ
Важный момент: следите за корректностью имён файлов, они должны в точности соответствовать тому, как перечислены в списке выше. Если сделать ошибку в имени одного из файлов, то перевод текста на этот язык не будет работать в рассматриваемой нами программе.

Если сейчас открыть любой из этих новых файлов, мы увидим таблицу с нашими знакомыми "эталонными" строками, которые мы задали чуть выше по тексту. Теперь нам нужно для каждого из этих файлов, в его соответствующей таблице, в столбце Значение, набрать соответствующий перевод на нужный нам язык.

Ниже приводятся переводы для каждого из созданных файлов ресурсов, которые нужно будет задать в его таблице и сохранить изменения:

1) MultiLangSupportExampleI18N.be.resx (поддержка белорусского языка):

Имя Значение
AboutText Праграма на C# ад аўтараў сайта Allineed.Ru, якая дэманструе падтрымку некалькіх моў у дадатку Windows Forms
AboutTitle Аб праграме
AreYouSureYouWantToExit Вы ўпэўненыя, што хочаце выйсці з прыкладання?
ExitAppTitle Выхад з прыкладання
Greeting Прывітанне,

 

2) MultiLangSupportExampleI18N.de.resx (поддержка немецкого языка):

Имя Значение
AboutText Ein C#-Programm von den Autoren der Website Allineed.Ru, das die Unterstützung mehrerer Sprachen in einer Windows Forms-Anwendung demonstriert
AboutTitle Über das Programm
AreYouSureYouWantToExit Möchten Sie die Anwendung wirklich beenden?
ExitAppTitle Beenden Sie die Anwendung
Greeting Hallo

 

3) MultiLangSupportExampleI18N.en.resx (поддержка английского языка):

Имя Значение
AboutText A C# program from the authors of the site Allineed.Ru, demonstrating support for multiple languages in a Windows Forms application
AboutTitle About the program
AreYouSureYouWantToExit Are you sure you want to exit the application?
ExitAppTitle Exit the application
Greeting Hello

 

4) MultiLangSupportExampleI18N.zh.resx (поддержка китайского языка):

Имя Значение
AboutText 來自 Allineed.Ru 網站作者的 C# 程序,演示了 Windows 窗體應用程式中對多種語言的支持
AboutTitle 關於該計劃
AreYouSureYouWantToExit 您確定要退出應用程式嗎?
ExitAppTitle 退出應用程式
Greeting 你好

 

5) MultiLangSupportExampleI18N.es.resx (поддержка испанского языка):

Имя Значение
AboutText Un programa C# de los autores del sitio Allineed.Ru, que demuestra soporte para múltiples idiomas en una aplicación Windows Forms
AboutTitle Sobre el programa
AreYouSureYouWantToExit ¿Estás seguro de que quieres salir de la aplicación?
ExitAppTitle Salir de la aplicación
Greeting Hola,

 

6) MultiLangSupportExampleI18N.pt.resx (поддержка португальского языка):

Имя Значение
AboutText Um programa C# dos autores do site Allineed.Ru, demonstrando suporte para múltiplas linguagens em uma aplicação Windows Forms
AboutTitle Sobre o programa
AreYouSureYouWantToExit Tem certeza de que deseja sair do aplicativo?
ExitAppTitle Saia do aplicativo
Greeting Olá,

 

Info icon by Icons8НА ЗАМЕТКУ
Не могу ручаться за корректность всех переведённых сообщений на разные языки. Сообщения приведены в статье лишь для примера, а я использовал для цели перевода для статьи один из доступных сервисов онлайн-переводчика.

Теперь у нас готовы файлы ресурсов, локализованные на каждый поддерживаемый в программе язык. Осталось подключить наш новый проект MultiLangSupportResources к основному проекту в виде ссылки.

3. Подключение ссылки на проект MultiLangSupportResources к основному проекту MultiLangSupportExample

Вернёмся к нашему первому проекту MultiLangSupportExample и раскроем его в окне "Обозреватель решений", найдя узел "Ссылки". Кликаем правой кнопкой мыши по этому узлу и выбираем пункт меню "Добавить ссылку..."

В открывшемся окне "Менеджер ссылок", в левой его части, находим раздел "Проекты" и в нём ставим галочку напротив проекта MultiLangSupportResources. Как результат - новый проект, содержащий все файлы ресурсов, будет подключен к основному проекту MultiLangSupportExample в качестве ссылки.

Также в главном меню среды разработки выберем Сборка → Пересобрать решение. Это делаем для того, чтобы убедиться на данном этапе, что никаких ошибок в решении у нас нет, а связи между проектами применились, как ожидается.

4. Добавление в программу настройки для используемого языка

Мы хотим сделать так, чтобы программа при её завершении запоминала в настройках последний используемый язык, который был выставлен перед выходом из программы. И при повторном запуске отображала бы все сообщения на главной форме и при работе программы на этом языке, т.е. загружала указанный в настройках язык.

Для этого в основном проекте MultiLangSupportExample добавим такую настройку, раскрыв в окне "Обозреватель решений" узел Properties, расположенный под проектом MultiLangSupportExample. В нём нас интересует файл Settings.settings. Дважды кликнем по этому файлу для открытия окна настроек программы. Мы увидим таблицу со столбцами Имя, Тип, Область, Значение. Нужно добавить в таблицу одну строку, введя в столбце Имя название для нашей будущей настройки с выбранным языком: LastUsedLanguage. Тип данных для настройки оставляем string, в столбце Область также оставляем значение по умолчанию - Пользователь. Таким образом мы добавили новую настройку с именем LastUsedLanguage, тип данных которой будет string (строка), и она будет пользовательской, т.е. программа будет сохранять её в рамках текущей сессии работы пользователя в операционной системе (т.е. не для всех пользователей системы). Если бы мы хотели сделать настройку глобальной (т.е. для всего приложения), то мы бы выбрали в столбце Область соответствующее значение - Приложение.

5. Программирование главной формы

5.1 Добавляем в код главной формы служебные методы для поддержки логики работы с разными языками

На данном этапе у нас готов дизайн формы, подготовленные файлы ресурсов с локализацией на целевые языки, а также единственная настройка LastUsedLanguage для нашего приложения. Пришло время запрограммировать поведение главной формы приложения и её элементов.

Для начала перейдем в редактор кода для главной формы и укажем в самом верху те пространства имён, которые нам будут нужны для работы:

using System;
using System.Globalization;
using System.Resources;
using System.Threading;
using System.Windows.Forms;
using MultiLangSupportExample.Properties;
using MultiLangSupportResources.Resources;

Обратите внимание на два последних пространства имён: первое относится к свойствам приложения и позволит нам загружать/сохранять настройки программы, а второе необходимо для доступа к ресурсов из второго проекта MultiLangSupportResources, который мы подключили к основному в качестве ссылки.

Добавим в код главной формы следующий метод SelectLanguageMenuItem:

        private void SelectLanguageMenuItem(string selectedCulture) {
            foreach (ToolStripMenuItem dropDownItem in FileLanguageToolStripMenuItem.DropDownItems) {
                if (dropDownItem.Tag is string str && str.Equals(selectedCulture)) {
                    dropDownItem.Checked = true;
                }
            }
        }

Он принимает единственный строковый параметр selectedCulture, в который будет передаваться код языка (например: "ru" для русского языка, "be" - для белорусского и так далее).

Дальше мы перебираем в цикле foreach все пункты меню под пунктом меню Язык и для каждого из них сверяем, равно ли значение его свойства Tag переданному в параметре selectedCulture значению. Если это так, то значит, нам нужно выбрать этот выпадающий пункт меню с названием языка, отметив его флажком, что мы и делаем с помощью установки dropDownItem.Checked = true.

Теперь подключим к форме специальный диспетчер ресурсов, представленный классом ResourceManager из системного пространства имён System.Resources. Этот диспетчер ресурсов поможет нам удобным образом переводить все расположенные на форме элементы (об этом чуть далее). Добавим закрытое (private) поле с именем resourceManager в тело класса главной формы, а также создадим новый экземпляр диспетчера ресурсов в конструкторе главное формы, после вызова метода InitializeComponent():

using System;
using System.Globalization;
using System.Resources;
using System.Threading;
using System.Windows.Forms;
using MultiLangSupportExample.Properties;
using MultiLangSupportResources.Resources;

namespace MultiLangSupportExample {
    public partial class FrmMain : Form {
        /// <summary>
        /// диспетчер ресурсов для главной формы
        /// </summary>
        private ResourceManager resourceManager;

        public FrmMain() {
            InitializeComponent();

            // инициализация диспетчера ресурсов для главной формы
            resourceManager = new ResourceManager(typeof(FrmMain));
        }

        // ... здесь идёт остальной код формы ...
}

Следующим шагом создадим следующий метод ReloadFormControlsTranslation в коде главной формы:

        /// <summary>
        /// Перезагрузка перевода всех элементов формы 
        /// в соответствии с выбранным языком приложения
        /// </summary>
        private void ReloadFormControlsTranslation() {
            this.Text = resourceManager.GetString("$this.Text");

            // перезагружаем перевод для метки
            LabelName.Text = resourceManager.GetString("LabelName.Text");
            
            // перезагружаем перевод для пунктов меню
            FileToolStripMenuItem.Text = resourceManager.GetString("FileToolStripMenuItem.Text");
            FileAboutToolStripMenuItem.Text = resourceManager.GetString("FileAboutToolStripMenuItem.Text");
            FileLanguageToolStripMenuItem.Text = resourceManager.GetString("FileLanguageToolStripMenuItem.Text");
            FileLanguageRUToolStripMenuItem.Text = resourceManager.GetString("FileLanguageRUToolStripMenuItem.Text");
            FileLanguageBEToolStripMenuItem.Text = resourceManager.GetString("FileLanguageBEToolStripMenuItem.Text");
            FileLanguageDEToolStripMenuItem.Text = resourceManager.GetString("FileLanguageDEToolStripMenuItem.Text");
            FileLanguageENToolStripMenuItem.Text = resourceManager.GetString("FileLanguageENToolStripMenuItem.Text");
            FileLanguageZHToolStripMenuItem.Text = resourceManager.GetString("FileLanguageZHToolStripMenuItem.Text");
            FileLanguageESToolStripMenuItem.Text = resourceManager.GetString("FileLanguageESToolStripMenuItem.Text");
            FileLanguagePTToolStripMenuItem.Text = resourceManager.GetString("FileLanguagePTToolStripMenuItem.Text");
            FileExitToolStripMenuItem.Text = resourceManager.GetString("FileExitToolStripMenuItem.Text");

            // перезагружаем перевод для кнопок
            ButtonSayHello.Text = resourceManager.GetString("ButtonSayHello.Text");
            ButtonExit.Text = resourceManager.GetString("ButtonExit.Text");
        }

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

Дальше добавим в код главной формы следующий метод с именем ChangeCurrentCulture:

        /// <summary>
        /// Изменяет текущий язык приложения на заданный в параметре <paramref name="selectedCulture"/>
        /// </summary>
        /// <param name="selectedCulture">язык приложения, на который нужно переключиться</param>
        /// <param name="saveSettings">флаг, задающий, следует ли сохранить настройки программы при переключении на новый язык</param>
        public void ChangeCurrentCulture(string selectedCulture, bool saveSettings) {
            CultureInfo selectedCultureInfo = CultureInfo.GetCultureInfo(selectedCulture);
            Thread.CurrentThread.CurrentUICulture = selectedCultureInfo;
            Thread.CurrentThread.CurrentCulture = selectedCultureInfo;
            if (saveSettings) {
                Settings.Default.LastUsedLanguage = selectedCulture;
                Settings.Default.Save();
            }
            SelectLanguageMenuItem(selectedCulture);
            ReloadFormControlsTranslation();
        }

Этот метод при помощи стандартных средств платформы .NET, а именно класса CultureInfo и его метода GetCultureInfo() позволяет нам по заданному коду языка (например "ru", "en", "be" и т. д.) получить экземпляр selectedCultureInfo, инициализированный соответствующим образом для выбранной культуры (т.е. языка). Этот экземпляр мы присваиваем свойствам CurrentUICulture и CurrentCulture текущего потока, получая его через Thread.CurrentThread. При необходимости - если был передан флаг saveSettings, равный true - мы сохраняем также и изменения в настройке LastUsedLanguage приложения. Также, поскольку мы изменяем в приложении язык, то мы должны отметить флажком соответствующий пункт меню для устанавливаемого языка - это делается через вызов SelectLanguageMenutItem. Наконец, нам нужно перевести все расположенные на главной форме элементы в соответствующие локализованные сообщения выбранного языка, и мы это делаем вызовом добавленного ранее метода ReloadFormControlsTranslation.

Теперь в код главной формы мы добавим следующий метод LoadLastUsedLanguage:

        /// <summary>
        /// Загрузить последний использованный язык из настроек приложения
        /// </summary>
        private void LoadLastUsedLanguage() {
            string lastLanguage = Settings.Default.LastUsedLanguage;
            if (!string.IsNullOrEmpty(lastLanguage)) {
                if (lastLanguage.StartsWith("ru")) {
                    ChangeCurrentCulture("ru", false);
                } else if (lastLanguage.StartsWith("be")) {
                    ChangeCurrentCulture("be", false);
                } else if (lastLanguage.StartsWith("de")) {
                    ChangeCurrentCulture("de", false);
                } else if (lastLanguage.StartsWith("en")) {
                    ChangeCurrentCulture("en", false);
                } else if (lastLanguage.StartsWith("zh")) {
                    ChangeCurrentCulture("zh", false);
                } else if (lastLanguage.StartsWith("es")) {
                    ChangeCurrentCulture("es", false);
                } else if (lastLanguage.StartsWith("pt")) {
                    ChangeCurrentCulture("pt", false);
                } else {
                    // язык по умолчанию
                    ChangeCurrentCulture("ru", false);
                }
            } else {
                ChangeCurrentCulture("ru", false);
            }
        }

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

Tip icon by Icons8СОВЕТ
Вы можете заметить, что в данном примере используются одни и те же повторяющиеся строковые константы для языков, которые перечислены прямо в условиях if. Хорошей практикой является выделение подобных строк в отдельные константы, но в данном примере я этого не сделал, поскольку код довольно простой, и константы используются только в этом единственном методе LoadLastUsedLanguage.

Добавим ещё следующий метод SwitchLanguage в код главной формы, его мы будем вызывать при каждом клике на пункты меню с названиями языков:

        private void SwitchLanguage(object sender) {
            foreach (ToolStripMenuItem dropDownItem in FileLanguageToolStripMenuItem.DropDownItems) {
                dropDownItem.Checked = false;
            }

            if (sender is ToolStripMenuItem clickedMenuItem) {
                clickedMenuItem.Checked = true;
                ChangeCurrentCulture(clickedMenuItem.Tag.ToString(), true);
            }            
        }

Кратко поясню, что делает метод SwitchLanguage: он принимает единственный параметр sender с типом object. В этот метод будет передаваться экземпляр пункта меню с названием конкретного языка, т.е. по сути в переменной sender будет находиться экземпляр типа ToolStripMenuItem, представляющий пункт меню с языком, по которому пользователь кликнул мышью. В цикле foreach мы сначала сбрасываем флажки для всех пунктов меню с названиями языков. А затем устанавливаем флажок именно для того пункта меню, по которому был клик. И дополнительно переключаем наше приложение на выбранный язык, вызывая ранее добавленный метод ChangeCurrentCulture. И обратите внимание, что именно здесь нам пригождается свойство Tag, которое на этапе добавления пунктов меню на форму мы определили для каждого пункта меню, связанного с языком. Вторым аргументом вызова мы передаём true, поскольку здесь нужно будет не только переключить язык приложения на выбранный, но и сразу же сохранить его в настройках программы.

Ещё добавим метод ExitApp, который будет выводить стандартный диалог с кнопками "Да", "Нет", "Отмена" и спрашивать пользователя приложения о том, действительно ли он хочет выйти из программы. Если пользователь выберет "Да", программа завершится:

        /// <summary>
        /// Выход из приложения
        /// </summary>
        private void ExitApp() {
            DialogResult dlgResult = MessageBox.Show(
                    MultiLangSupportExampleI18N.AreYouSureYouWantToExit, 
                    MultiLangSupportExampleI18N.ExitAppTitle, 
                    MessageBoxButtons.YesNoCancel, 
                    MessageBoxIcon.Question
                );
            if (dlgResult == DialogResult.Yes) {
                Application.Exit();
            }            
        }

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

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

5.2 Добавляем обработчики событий для элементов управления главной формы

Добавим обработчики события для основных элементов управления на форме.

Начнём с обработки события Load главной формы, оно вызывается при загрузке формы. Единственное, что мы делаем здесь, - это вызываем ранее написанный метод LoadLastUsedLanguage:

        /// <summary>
        /// Загрузка главной формы
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FrmMain_Load(object sender, EventArgs e) {
            LoadLastUsedLanguage();
        }

Дальше добавим обработку события Click для кнопок ButtonExit ( "&Выход" ) и ButtonSayHello ( "&Приветствие" ):

        /// <summary>
        /// Нажатие на кнопку "Выход"
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ButtonExit_Click(object sender, EventArgs e) {
            ExitApp();
        }

        /// <summary>
        /// Нажатие на кнопку "Приветствие"
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ButtonSayHello_Click(object sender, EventArgs e) {
            MessageBox.Show(
                MultiLangSupportExampleI18N.Greeting + " " + TextBoxName.Text, 
                MultiLangSupportExampleI18N.Greeting + " " + TextBoxName.Text, 
                MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

По нажатию на кнопку "&Выход" вызываем ранее добавленный метод ExitApp(), а по нажатию на "&Приветствие" мы просто выводим приветственное сообщение пользователю, используя для этого строку-ресурс Greeting, которую мы добавляли на этапе создания проекта MultiLangSupportResources. Это позволит приветствовать пользователя на текущем языке приложения, что выбран через меню.

Дальше мы обработаем события нажатия (Click) на пункты меню "&О программе..." и "&Выход":

        /// <summary>
        /// Нажатие на пункт меню "Выход"
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FileExitToolStripMenuItem_Click(object sender, EventArgs e) {
            ExitApp();
        }

        /// <summary>
        /// Нажатие на пункт меню "О программе..."
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FileAboutToolStripMenuItem_Click(object sender, EventArgs e) {
            MessageBox.Show(
                MultiLangSupportExampleI18N.AboutText,
                MultiLangSupportExampleI18N.AboutTitle,
                MessageBoxButtons.OK,
                MessageBoxIcon.Information
            );
        }

Здесь всё очень просто: при нажатии на "&Выход" снова вызываем ранее созданный метод ExitApp(). А при нажатии на пункт меню "&О программе..." мы просто выводим сообщение пользователю с информацией о программе - снова на том языке, на котором сейчас работает наша программа. Это достигается за счёт строк-ресурсов AboutText и AboutTitle, которые мы завели ранее в отдельном проекте MultiLangSupportResources.

Теперь нам осталось лишь обработать клики по пунктам меню с названиями всех языков, все они будут вызывать ранее добавленный метод SwitchLanguage и передавать в него экземпляр sender, в котором будет храниться ссылка на тот пункт меню с названием языка, по которому мы кликнули:

        private void FileLanguageRUToolStripMenuItem_Click(object sender, EventArgs e) {
            SwitchLanguage(sender);
        }

        private void FileLanguageBEToolStripMenuItem_Click(object sender, EventArgs e) {
            SwitchLanguage(sender);
        }

        private void FileLanguageDEToolStripMenuItem_Click(object sender, EventArgs e) {
            SwitchLanguage(sender);
        }

        private void FileLanguageENToolStripMenuItem_Click(object sender, EventArgs e) {
            SwitchLanguage(sender);
        }

        private void FileLanguageZHToolStripMenuItem_Click(object sender, EventArgs e) {
            SwitchLanguage(sender);
        }

        private void FileLanguageESToolStripMenuItem_Click(object sender, EventArgs e) {
            SwitchLanguage(sender);
        }

        private void FileLanguagePTToolStripMenuItem_Click(object sender, EventArgs e) {
            SwitchLanguage(sender);
        }

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

Info icon by Icons8НА ЗАМЕТКУ
Можно заметить, что обработчики события Click для языковых пунктов меню выглядят однотипно: они все вызывают метод SwitchLanguage. В целом, можно было бы задать собственный метод с параметрами object sender и EventArgs e, и к нему привязать обработку события Click для каждого языкового пункта меню. Однако считаю, что это дело вкуса каждого разработчика, поэтому если хотите, можете оптимизировать эти однотипные обработчики на свой вкус.

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

Это довольно монотонная работа, требующая усидчивости и внимания, но она необходима, чтобы главная форма была полностью и корректно представлена на каждом из поддерживаемых в приложении языков. Посмотрим, как это делается.

6. Заключительный этап. Готовим переводы для каждого элемента управления формы на каждом из языков.

Помните, как в первой части статьи, на этапе установки свойств главной формы, мы установили для главной формы свойство Localizable равным True? Мы это сделали, чтобы указать, что наша главная форма поддерживает локализацию, т.е. работу с разными языками.

Но нам теперь ещё нужно выполнить непосредственно перевод для каждого элемента главной формы на нужный язык. Это достигается последовательным изменением свойства Language на главной форме на один из поддерживаемых нами языков, после чего нужно переводить каждый элемент управления на форме на выбранный язык - путём установки ему значения для свойства Text, которое содержит текст на заданном языке. Когда для очередного языка всем элементам формы установлена значение свойства Text в нужном целевом языке, производится сохранение всех выполненных изменений (Ctrl+S), и далее свойство Language меняется на следующий язык. Так повторяется до тех пор, пока мы не обеспечим переводом каждый элемент управления формы на каждом языке.

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

Language: Белорусский

Свойство Text для FrmMain: [Allineed.Ru] Прыклад дадатку на C# з падтрымкай розных моў
Свойство Text для ButtonExit: &Вынахад
Свойство Text для ButtonSayHello: &Прывітанне
Свойство Text для FileAboutToolStripMenuItem: &Аб праграме...
Свойство Text для FileExitToolStripMenuItem: &Вынахад
Свойство Text для FileLanguageBEToolStripMenuItem: Беларуская мова
Свойство Text для FileLanguageDEToolStripMenuItem: Нямецкая мова
Свойство Text для FileLanguageENToolStripMenuItem: Англійская мова
Свойство Text для FileLanguageESToolStripMenuItem: Іспанская мова
Свойство Text для FileLanguagePTToolStripMenuItem: Партугальская мова
Свойство Text для FileLanguageRUToolStripMenuItem: Руская мова
Свойство Text для FileLanguageToolStripMenuItem: Мова
Свойство Text для FileLanguageZHToolStripMenuItem: Кітайская мова
Свойство Text для FileToolStripMenuItem: &Файл
Свойство Text для LabelName: Увядзіце Ваша імя:

Language: Немецкий

Свойство Text для FrmMain: [Allineed.Ru] Beispielanwendung in C# mit Unterstützung für verschiedene Sprachen
Свойство Text для ButtonExit: &Ausfahrt
Свойство Text для ButtonSayHello: &Grüße
Свойство Text для FileAboutToolStripMenuItem: &Über das Programm...
Свойство Text для FileExitToolStripMenuItem: &Ausfahrt
Свойство Text для FileLanguageBEToolStripMenuItem: Weißrussische Sprache
Свойство Text для FileLanguageDEToolStripMenuItem: Deutsche Sprache
Свойство Text для FileLanguageENToolStripMenuItem: Englische Sprache
Свойство Text для FileLanguageESToolStripMenuItem: Spanisch
Свойство Text для FileLanguagePTToolStripMenuItem: Portugiesisch
Свойство Text для FileLanguageRUToolStripMenuItem: Russisch
Свойство Text для FileLanguageToolStripMenuItem: Sprache
Свойство Text для FileLanguageZHToolStripMenuItem: Chinesische Sprache
Свойство Text для FileToolStripMenuItem: &Datei
Свойство Text для LabelName: Gib deinen Namen ein:

Language: Английский

Свойство Text для FrmMain: [Allineed.Ru] Example C# application with support for different languages
Свойство Text для ButtonExit: &Exit
Свойство Text для ButtonSayHello: &Greeting
Свойство Text для FileAboutToolStripMenuItem: &About Application...
Свойство Text для FileExitToolStripMenuItem: &Exit
Свойство Text для FileLanguageBEToolStripMenuItem: Belarusian
Свойство Text для FileLanguageDEToolStripMenuItem: German
Свойство Text для FileLanguageENToolStripMenuItem: English
Свойство Text для FileLanguageESToolStripMenuItem: Spanish
Свойство Text для FileLanguagePTToolStripMenuItem: Portuguese
Свойство Text для FileLanguageRUToolStripMenuItem: Russian
Свойство Text для FileLanguageToolStripMenuItem: Language
Свойство Text для FileLanguageZHToolStripMenuItem: Chinese
Свойство Text для FileToolStripMenuItem: &File
Свойство Text для LabelName: Enter your name:

Language: Китайский

Свойство Text для FrmMain: [Allineed.Ru] 支援不同語言的 C# 範例應用程式
Свойство Text для ButtonExit: &出口
Свойство Text для ButtonSayHello: &問候
Свойство Text для FileAboutToolStripMenuItem: 關於該計劃...
Свойство Text для FileExitToolStripMenuItem: 出口
Свойство Text для FileLanguageBEToolStripMenuItem: 白俄羅斯語
Свойство Text для FileLanguageDEToolStripMenuItem: 德文
Свойство Text для FileLanguageENToolStripMenuItem: 英語
Свойство Text для FileLanguageESToolStripMenuItem: 西班牙語
Свойство Text для FileLanguagePTToolStripMenuItem: 葡萄牙語
Свойство Text для FileLanguageRUToolStripMenuItem: 俄文
Свойство Text для FileLanguageToolStripMenuItem: 語言
Свойство Text для FileLanguageZHToolStripMenuItem: 中國人
Свойство Text для FileToolStripMenuItem: 文件
Свойство Text для LabelName: 輸入你的名字:

Language: Испанский

Свойство Text для FrmMain: [Allineed.Ru] Aplicación de ejemplo en C# con soporte para diferentes lenguajes
Свойство Text для ButtonExit: &Salida
Свойство Text для ButtonSayHello: &Saludos
Свойство Text для FileAboutToolStripMenuItem: &Sobre el programa...
Свойство Text для FileExitToolStripMenuItem: &Salida
Свойство Text для FileLanguageBEToolStripMenuItem: Bielorruso
Свойство Text для FileLanguageDEToolStripMenuItem: Alemán
Свойство Text для FileLanguageENToolStripMenuItem: Inglés
Свойство Text для FileLanguageESToolStripMenuItem: Español
Свойство Text для FileLanguagePTToolStripMenuItem: Portugués
Свойство Text для FileLanguageRUToolStripMenuItem: Ruso
Свойство Text для FileLanguageToolStripMenuItem: Idioma
Свойство Text для FileLanguageZHToolStripMenuItem: Chino
Свойство Text для FileToolStripMenuItem: &Archivo
Свойство Text для LabelName: Introduzca su nombre:

Language: Португальский

Свойство Text для FrmMain: [Allineed.Ru] Exemplo de aplicação em C# com suporte para diferentes linguagens
Свойство Text для ButtonExit: &Saída
Свойство Text для ButtonSayHello: &Saudações
Свойство Text для FileAboutToolStripMenuItem: &Sobre o programa...
Свойство Text для FileExitToolStripMenuItem: &Saída
Свойство Text для FileLanguageBEToolStripMenuItem: Língua bielorrussa
Свойство Text для FileLanguageDEToolStripMenuItem: Alemão
Свойство Text для FileLanguageENToolStripMenuItem: língua Inglesa
Свойство Text для FileLanguageESToolStripMenuItem: Espanhol
Свойство Text для FileLanguagePTToolStripMenuItem: Português
Свойство Text для FileLanguageRUToolStripMenuItem: língua russa
Свойство Text для FileLanguageToolStripMenuItem: Linguagem
Свойство Text для FileLanguageZHToolStripMenuItem: Сhinês
Свойство Text для FileToolStripMenuItem: &Arquivo
Свойство Text для LabelName: Digite seu nome:

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

После перевода формы на каждый из поддерживаемых приложением языков, программа полностью готова. Можно запустить её и попробовать переключиться между языками. Также можно ввести имя в текстовое поле и нажать на кнопку приветствия. Вы должны увидеть приветственное сообщение на том языке, с которым работает программа. 

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

Ссылка на архив с готовым примером решения, разработанного в рамках статьи:

https://allineed.ru/our-products/download/4-allineed-ru-examples/26-csharp-i18-demo

Спасибо за внимание и удачи в написании программ на C#, которые используют поддержку разных языков. Если возникли вопросы или пожелания, напишите их в комментариях.

Яндекс.Метрика