Локализация приложения. Локализация проектов на.NET с интерпретатором функций

Начну с того, что за много лет работы программистом я неоднократно сталкивался с задачей внедрения в проект локализации в том или ином виде, но обычно это были решения, работающие на основе подгружаемого словаря с парами ключ-значение. Такой подход вполне оправдан для небольших проектов, но имеет ряд существенных недостатков:
  1. Сложность внедрения в существующий проект.
  2. Отсутствие средств форматирования локализованных сообщений (за исключением стандартного string.Format).
  3. Невозможность встраивания культурно-зависимых функций. Например, типичную задачу, - подстановку нужной формы слова в зависимости от значения числа, - одними словарями значений не разрешить.

Проанализировав эти проблемы, я пришел к выводу о необходимости создания собственной библиотеки для локализации проектов, которая будет лишена перечисленных выше недостатков. В этой статье я расскажу о принципах ее работы с примерами кода на C#.

В сборку входят следующие проекты:

Основные принципы


Менеджер локализаций
Библиотека построена на базе плагинов и работает следующим образом: при запуске приложения создается менеджер локализаций (LocalizationManager ), которому указывается путь к каталогу, где он будет производить поиск доступных пакетов локализаций (LocalizationPackage ), каждый из которых отвечает за некоторую культуру (пакет русской локализации, английской и т.п.). После этого дается команда на поиск и загрузку дескрипторов всех пакетов, весь код инициализации выглядит примерно следующим образом:

// инициализация менеджера локализаций LocalizationManager = new LocalizationManager(); LocalizationManager.BasePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Localization"); LocalizationManager.Initialize(); try { LocalizationManager.DetectAllLocalizations(); } catch (LocalizationException ex) { MessageBox.Show(ex.Message, "Ошибка локализации", MessageBoxButtons.OK, MessageBoxIcon.Error); return; }
Если все прошло без ошибок, в менеджере появится список доступных локализаций в виде их кратких описаний (дескрипторов, LocalizationDescriptor ). Эти дескрипторы не содержат в себе какой-либо логики, а служат только лишь описанием того или иного пакета, который можно загрузить и начать применять в программе.

Список всех локализаций можно получить из менеджера:

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

LocalizationPackage package = manager.Load("ru");
После загрузки с локализацией можно работать - получать из нее строки, ресурсы и т.д., а если она более не нужна, ее можно выгрузить:

Manager.Unload("ru");
Важно! Можно загружать и выгружать неограниченное число локализаций, т.к. все они создаются в собственных доменах (AppDomain).

Пакет локализации
Каждая локализация представляет из себя набор файлов в отдельном каталоге, корневым для всех является тот, который был выбран при загрузке менеджера локализаций. В примере выше, это будет каталог \Localization , а непосредственно пакеты локализаций будут размещаться в каталогах \Localization\ru , \Localization\en и т.д…

Каждый стандартный пакет, обязательно должен содержать следующие файлы:

  • localization.info - xml файл с кратким описанием пакета, именно эти файлы изначально загружает менеджер локализаций.

    Пример для русской локализации:

    Русский ru
    Как видим, здесь всего два поля, возможно потом будут добавлены новые поля для идентификации того или иного пакета.

  • flag.png - изображение, символизирующее локализацию. В моих примерах это флаги государств размером 16x16 пикселей.
  • strings.xml - xml файл, содержащий в себе локализованные строки. При переопределении логики пакета можно создать свой собственный источник строк, например, бинарник или базу данных.
  • package.dll - исполняемый модуль пакета - небольшая библиотечка, в которой должен присутствовать класс, унаследованный от LocalizationPackage .

    Пример исполняемого кода для русской локализации:

    Using System; using Genesis.Localization; namespace Ru { public class Package: LocalizationPackage { protected override object Plural(int count, params object forms) { int m10 = count % 10; int m100 = count % 100; if (m10 == 1 && m100 != 11) { return forms; } else if (m10 >= 2 && m10 <= 4 && (m100 < 10 || m100 >= 20)) { return forms; } else { return forms; } } } }
    Ниже будет дано пояснение, что такое метод Plural.

Применение пакетов локализации
Итак, мы создали менеджер локализаций и загрузили в него пакет с переводом. Теперь его можно использовать в программе тремя способами:
  1. Получение конкретной строки по ее ключу (классический метод). В роли ключа может выступать строка или число типа Int32.

    Пример использования:
    LocalizationPackage package = manager.Load(culture); string strByName = package["name"]; string strByID = package;

  2. Получение форматированной строки с передачей аргументов. Это тот метод, ради которого и создавалась библиотека.

    Пример использования:
    LocalizationPackage package = manager.Load(culture); string formattedString = package["name", arg1, args2, ...];
    В качестве аргументов могут использоваться любые объекты. Подробнее об этом методе будет рассказано ниже.

  3. Получение пути к локализованному ресурсу. Для этого используется метод GetResourceFilePath(string filename) для получения пути к произвольному файлу в каталоге локализации или метод GetImage(string filename) для загрузки изображения оттуда же.

Интерпретатор строк

Сила библиотеки заключается в ее интерпретаторе строк. Что же он из себя представляет?
Если вкратце, то это набор инструкций, включаемых в локализованные строки, с помощью которых можно адаптировать перевод под ту или иную культуру.

Интерпретатор строк вызывается описанным выше методом получения строки с заданными аргументами (при обычном обращении по ключу возвращается локализованная строка в «чистом» виде) или специальным методом GetFormattedString(string format, params object args), который работает точно так же, но при этом в качестве первого аргумента передается произвольная строка формата.

Теперь подробнее об этих инструкциях. Всего их две:

  1. Включение аргумента в строку.

    Формат инструкции: %index%
    Результат: встраивание в строку аргумента под номером index

    Пример использования:
    package.GetFormattedString("%1% = %0%%%", 80, "КПД");
    Результат:
    КПД = 80%
    Обратите внимание, что символ % , будучи служебным, должен экранироваться другим таким же символом, как в этом примере.

  2. Включение функций

    Формат инструкции:
    %Func(arg1, arg2, ..., argN)%

    Аргументами могут быть числа , строки в двойных кавычках (сами кавычки экранируются, как и %, двойным повтором), аргументы строки по их номеру (%index) или вызовы других функций .

    Пример использования:
    package.GetFormattedString("Игрок %1% наносит по вам %Upper(Random(\"сильный\", \"сокрушительный\", \"мощный\"))% удар, отнимая %0% %Plural(%0, \"единицу\", \"единицы\", \"единиц\")% здоровья.", 55, "MegaDeath2000");
    Результат:
    Игрок MegaDeath2000 наносит по вам СОКРУШИТЕЛЬНЫЙ удар, отнимая 55 единиц здоровья.

Встроенные функции и интеграция

В классе LocalizationPackage встроено несколько «стандартных» функций, часть была использована в примере выше:
  • Plural(int, var1, var2, ..., varN) - встраивание формы слова в зависимости от числа, данный метод уникален для каждой культуры и должен быть переопределен. В частности, в русском языке есть три формы числа (например: «1 единица», «2 единицы», «8 единиц»).
  • Random(var1, var2, ..., varN) - выбор случайного значения среди заданных.
  • Upper(string) - приведение к верхнему регистру.
  • Lower(string) - приведение к нижнему регистру.
  • UpperF(string) - приведение к верхнему регистру только первой буквы («словечко» => «Словечко»).
  • LowerF(string) - приведение к нижнему регистру только первой буквы.
Если вам требуется добавить новые функции, сделать это можно двумя способами.
  1. В переопределенном классе пакета можно объявить новые функции и пометить их атрибутом , тогда они будут автоматически включены в интерпретатор для конкретной локализации. Встроенные функции определены именно этим способом, например так выглядят функции Plural и Random:

    Protected abstract object Plural(int count, params object forms); protected virtual object Random(params object variants) { if (variants.Length == 0) { return null; } else { return variants[_rnd.Next(variants.Length)]; } }
    Обратите внимание, что для функции допустимо задание списка ее псевдонимов (для краткой записи), например Plural может вызываться как через основное имя (Plural ), так и через псевдоним (P ), при этом регистр в названиях функций не имеет значения.

  2. Интеграция собственных функций, для этого используется метод InjectFormatterFunction , пример использования:

    Var package = LocalizationManager.Load("ru"); package.InjectFormatterFunction(new Func((a, b) => Math.Min(a, b)), "Min"); package.InjectFormatterFunction(new Func((a, b) => Math.Max(a, b)), "Max"); package.GetFormattedString("%min(%0, max(%1, %2))%", 10, 8, 5);
    Результат:
    8
    В качестве аргумента для InjectFormatterFunction может быть передан метод (MethodInfo) или делегат (в примере выше передаются делегаты).

Дополнительные возможности

Помимо основных функций, библиотека предоставляет еще два дополнения.
Отладочный режим
В Debug-версии библиотеки включена возможность не только получения локализованных строк описанными выше способами, но и их непосредственная запись:

Var package = LocalizationManager.Load("ru"); package["New Key"] = "Новое значение"; package.Save();
В этом случае будет создана новая локализованная строка с указанным ключом и значением (либо перезаписана уже имеющаяся), а сам пакет сохранен на диск. Также в режиме отладки при попытке чтения строки с отсутствующим ключом, будет возвращено пустое значение, но при этом будет создана новая запись. Это удобно на начальном этапе разработки - нам не нужно заботится о наполнении словаря - он сам будет пополняться пустыми значениями, которые мы потом заполним данными.

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

Маппинги
Это наш десерт. Назначение - быстрая локализация форм, контролов и других сложных объектов.
Данная функция используется в демонстрационном проекте LocalizationViewer .

Приведу отрывок описания главной формы:

Public partial class frmMain: Form { ... private System.Windows.Forms.ToolStripButton cmdExit; private System.Windows.Forms.ToolStripButton cmdSave; private System.Windows.Forms.ToolStripLabel lblSearch; ... ///

/// применяем локализацию /// private void Localize() { LocalizationMapper mapper = new LocalizationMapper(); mapper.Current = manager["ru"]; mapper.Localize(this); } ... }
LocalizationMapper , позволяет локализовать любой объект, переданный ему в функции Localize , используя атрибуты и на полях и свойствах локализуемого объекта (в данном случае - формы). Например, атрибут без параметров означает, что надо локализовать свойство по умолчанию (Text), при этом будет использован автоматический ключ вида ... Для поля Text кнопки cmdExit ключ будет таким:

LocalizationViewer.frmMain.cmdExit_Text

Заключение

Библиотека в скором времени будет опробована в одном из моих проектов, так что скорее всего будут кое-какие доработки, в основном направленные на расширение базового функционала пакетов. Следите за обновлениями на SourceForge и пишите свои комментарии и мысли по дальнейшему развитию библиотеки.

P.S.

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

Существует множество способом локализовать WPF-приложение, но сложно найти метод, позволяющий менять надписи элементов в автоматическом режиме без необходимости закрытия и повторного открытия формы или полного перезапуска приложения. В этой публикации я расскажу о способе локализации WPF приложения, который позволяет менять культуру приложения без перезапуска приложения и форм. Данное решение требует использования ResourceDictionary (XAML) для перевода интерфейса(UI); для локализации сообщений из кода можно использовать файлы ресурсов (RESX), которые удобно использовать в коде и для редактирования которых есть плагин с удобным редактором ().

Проект написан на Visaul Basic .NET, а также на C#. Надеюсь это облегчит читаемость кода тем, кто не привык к Visaul Basic .NET или к C#.

Для начала создаём новый проект WPF Application:

Не забываем указать нейтральную культуру для всего проекта

В папке Resources создаём файл Resource Dictionary (WPF) , называем его lang.xaml и добавляем к уже созданному елементу ResourceDictionary аттрибут, который позволит описывать значения с указанием типа:

Xmlns:v="clr-namespace:System;assembly=mscorlib"

Теперь добавим файл в ресурсы приложения:

  1. Открываем файл Application.xaml (App.xaml для C#);
  2. В Application.Resources добавляем элемент ResourceDictionary ;
  3. В элемент ResourceDictionary добавляем элемент (тут будем хранить все наши ResourceDictionary);
  4. В элемент ResourceDictionary.MergedDictionaries добавляем элемент ResourceDictionary с аттрибутом Source , который ссылается на файл lang.xaml .

Пример результата

Теперь нам нужно добавить локализированные данные для UI внутрь элемента ResourceDictionary в файле lang.xaml :

WPF Localization example

В данном случае мы поместили текстовое значение (String), доступное по ключу m_Title .

Пример данных для приложения

WPF Localization example Hello world! Language 20.15

Для других культур приложения дублируем в папке Resources файл lang.xaml и переименовываем в lang.ru-RU .xaml , где ru-RU является названием культуры (Culture name). После дублирования можно переводить значения. Желательно это делать после того, когда добавим все значения в файл ресурсов lang.xaml .

Переведённые файл ресурсов на русскую культуру (ru-RU)

Пример WPF локализации Привет мир! Язык 10.5

Теперь в xaml коде окна добавим элементы, а текст для них будем браться используя динамические ресурсы:


Как видно из картики выше, Visual Studio видит ранее нами созданые ресурсы.

Примечание по поводу элемента Slider : свойство Value является типа Double , поэтому можно использовать только ресурс такого же типа.

Первый запуск

Мы вынесли в ресурсы название окна, название меню для смены языка приложения, текст у Label и значение у Slider.

Теперь приступим к написанию кода.

Для начала в классе Application (App для C#) укажем какие культуры поддерживает наше приложение:

Visual Basic .NET

Class Application Private Shared m_Languages As New List(Of CultureInfo) Public Shared ReadOnly Property Languages As List(Of CultureInfo) Get Return m_Languages End Get End Property Public Sub New() m_Languages.Clear() m_Languages.Add(New CultureInfo("en-US")) "Нейтральная культура для этого проекта m_Languages.Add(New CultureInfo("ru-RU")) End Sub End Class

C#

Public partial class App: Application { private static List m_Languages = new List(); public static List Languages { get { return m_Languages; } } public App() { m_Languages.Clear(); m_Languages.Add(new CultureInfo("en-US")); //Нейтральная культура для этого проекта m_Languages.Add(new CultureInfo("ru-RU")); } }

На уровне приложения реализуем функционал позволяющий переключать культуру из любова окна без дублирующего кода.
Добавляем статическое свойство Language в класс Application (App для C#), которое будет возвращать текущую культуру, а меняя культуру заменит словарь ресурсов предыдущей культуры на новую и вызовет эвент позволяющий всем окнам выполнить дополнительные действия при смене культуры.

Visual Basic .NET

"Евент для оповещения всех окон приложения Public Shared Event LanguageChanged(sender As Object, e As EventArgs) Public Shared Property Language As CultureInfo Get Return System.Threading.Thread.CurrentThread.CurrentUICulture End Get Set(value As CultureInfo) If value Is Nothing Then Throw New ArgumentNullException("value") If value.Equals(System.Threading.Thread.CurrentThread.CurrentUICulture) Then Exit Property "1. Меняем язык приложения: System.Threading.Thread.CurrentThread.CurrentUICulture = value "2. Создаём ResourceDictionary для новой культуры Dim dict As New ResourceDictionary() Select Case value.Name Case "ru-RU" dict.Source = New Uri(String.Format("Resources/lang.{0}.xaml", value.Name), UriKind.Relative) Case Else dict.Source = New Uri("Resources/lang.xaml", UriKind.Relative) End Select "3. Находим старую ResourceDictionary и удаляем его и добавляем новую ResourceDictionary Dim oldDict As ResourceDictionary = (From d In My.Application.Resources.MergedDictionaries _ Where d.Source IsNot Nothing _ AndAlso d.Source.OriginalString.StartsWith("Resources/lang.") _ Select d).First If oldDict IsNot Nothing Then Dim ind As Integer = My.Application.Resources.MergedDictionaries.IndexOf(oldDict) My.Application.Resources.MergedDictionaries.Remove(oldDict) My.Application.Resources.MergedDictionaries.Insert(ind, dict) Else My.Application.Resources.MergedDictionaries.Add(dict) End If "4. Вызываем евент для оповещения всех окон. RaiseEvent LanguageChanged(Application.Current, New EventArgs) End Set End Property

C#

//Евент для оповещения всех окон приложения public static event EventHandler LanguageChanged; public static CultureInfo Language { get { return System.Threading.Thread.CurrentThread.CurrentUICulture; } set { if(value==null) throw new ArgumentNullException("value"); if(value==System.Threading.Thread.CurrentThread.CurrentUICulture) return; //1. Меняем язык приложения: System.Threading.Thread.CurrentThread.CurrentUICulture = value; //2. Создаём ResourceDictionary для новой культуры ResourceDictionary dict = new ResourceDictionary(); switch(value.Name){ case "ru-RU": dict.Source = new Uri(String.Format("Resources/lang.{0}.xaml", value.Name), UriKind.Relative); break; default: dict.Source = new Uri("Resources/lang.xaml", UriKind.Relative); break; } //3. Находим старую ResourceDictionary и удаляем его и добавляем новую ResourceDictionary ResourceDictionary oldDict = (from d in Application.Current.Resources.MergedDictionaries where d.Source != null && d.Source.OriginalString.StartsWith("Resources/lang.") select d).First(); if (oldDict != null) { int ind = Application.Current.Resources.MergedDictionaries.IndexOf(oldDict); Application.Current.Resources.MergedDictionaries.Remove(oldDict); Application.Current.Resources.MergedDictionaries.Insert(ind, dict); } else { Application.Current.Resources.MergedDictionaries.Add(dict); } //4. Вызываем евент для оповещения всех окон. LanguageChanged(Application.Current, new EventArgs()); } }

Ну что ж, осталось научить наше окно переключать культуру программы. При создании нового окна добавим в меню смены культуры все поддерживаемые приложением культуры, а также добавим обработчик эвента юApplication.LanguageChanged , который ранее создали. Также добавим обработчик нажатия по пунту смены культуры ChangeLanguageClick , который будет менять у приложения культуру и функцию LanguageChanged для обработки события Application.LanguageChanged :

Visual Basic .NET

Class MainWindow Public Sub New() InitializeComponent() "Добавляем обработчик события смены языка у приложения AddHandler Application.LanguageChanged, AddressOf LanguageChanged Dim currLang = Application.Language "Заполняем меню смены языка: menuLanguage.Items.Clear() For Each lang In Application.Languages Dim menuLang As New MenuItem() menuLang.Header = lang.DisplayName menuLang.Tag = lang menuLang.IsChecked = lang.Equals(currLang) AddHandler menuLang.Click, AddressOf ChangeLanguageClick menuLanguage.Items.Add(menuLang) Next End Sub Private Sub LanguageChanged(sender As Object, e As EventArgs) Dim currLang = Application.Language "Отмечаем нужный пункт смены языка как выбранный язык For Each i As MenuItem In menuLanguage.Items Dim ci As CultureInfo = TryCast(i.Tag, CultureInfo) i.IsChecked = ci IsNot Nothing AndAlso ci.Equals(currLang) Next End Sub Private Sub ChangeLanguageClick(sender As Object, e As RoutedEventArgs) Dim mi As MenuItem = TryCast(sender, MenuItem) If mi IsNot Nothing Then Dim lang As CultureInfo = TryCast(mi.Tag, CultureInfo) If lang IsNot Nothing Then Application.Language = lang End If End If End Sub End Class

C#

Namespace WPFLocalizationCSharp { ///

/// Interaction logic for MainWindow.xaml /// public partial class MainWindow: Window { public MainWindow() { InitializeComponent(); App.LanguageChanged += LanguageChanged; CultureInfo currLang = App.Language; //Заполняем меню смены языка: menuLanguage.Items.Clear(); foreach (var lang in App.Languages) { MenuItem menuLang = new MenuItem(); menuLang.Header = lang.DisplayName; menuLang.Tag = lang; menuLang.IsChecked = lang.Equals(currLang); menuLang.Click += ChangeLanguageClick; menuLanguage.Items.Add(menuLang); } } private void LanguageChanged(Object sender, EventArgs e) { CultureInfo currLang = App.Language; //Отмечаем нужный пункт смены языка как выбранный язык foreach (MenuItem i in menuLanguage.Items) { CultureInfo ci = i.Tag as CultureInfo; i.IsChecked = ci != null && ci.Equals(currLang); } } private void ChangeLanguageClick(Object sender, EventArgs e) { MenuItem mi = sender as MenuItem; if (mi != null) { CultureInfo lang = mi.Tag as CultureInfo; if (lang != null) { App.Language = lang; } } } } }

Приложение готово. Но для полного счастья настроим приложение так, что бы оно запоминало нами выбранyю культуру при запуске приложения.

Добавляем в проект настройку DefaultLanguage , указываем тип System.Globalization.CultureInfo (находится в библиотеке mscorlib ) и указываем значение по умолчанию нейтральную культуру проекта:

Так же в класс Application добавляем 2 дополнительных функции:

Visaul Basic .NET

Private Sub Application_LoadCompleted(sender As Object, e As NavigationEventArgs) Handles Me.LoadCompleted Language = My.Settings.DefaultLanguage End Sub Private Shared Sub OnLanguageChanged(sender As Object, e As EventArgs) Handles MyClass.LanguageChanged My.Settings.DefaultLanguage = Language My.Settings.Save() End Sub

C#

Private void Application_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e) { Language = WPFLocalizationCSharp.Properties.Settings.Default.DefaultLanguage; } private void App_LanguageChanged(Object sender, EventArgs e) { WPFLocalizationCSharp.Properties.Settings.Default.DefaultLanguage = Language; WPFLocalizationCSharp.Properties.Settings.Default.Save(); }

В App.xaml к элементу Application добавляем обработчик LoadCompleted эвента:

LoadCompleted="Application_LoadCompleted"

В конструктор класса App добавляем обработчик App.LanguageChanged эвента:

App.LanguageChanged += App_LanguageChanged;

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

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

В принципе, я хочу:

  • В исходном коде я хочу только один файл на язык
  • этот файл скомпилируется в основное приложение при компиляции - никакие спутниковые сборки или внешние файлы данных после создания приложения
  • Пользователь может выбрать язык, мне не нужно/нужно автоматическое обнаружение на основе операционной системы.
  • В основном это должны содержать строки и ints, но также и CultureInfo

В большинстве решений, которые я видел, есть один файл.resx для каждой формы и/или внешних спутниковых сборок.

Нужно ли мне сворачивать? Или уже есть что-то в рамках?

Net Framework 3.5 SP1, если это имеет значение.

Изменить: . По большей части Visual Studio уже предлагает поддержку того, что я хочу, но есть две проблемы. Когда я устанавливаю Form.Localizable в true, у меня есть эта хорошая поддержка дизайнера, но это создает один resx для каждой формы. Идея вручную переопределить его в InitializeComponent терпит неудачу, потому что это написанный конструктором код, который будет регулярно перезаписываться. Теоретически, я только хочу: а) переопределить создание ComponentResourceManager, чтобы указать его на мой глобальный resx, и b) изменить вызов ApplyResources на перегрузку, в которой в качестве третьего параметра принимает значение CultureInfo.

Кажется, что мне нужно добавить вызов функции моему конструктору, который вызывается после InitializeComponent() и переопределяет его поведение. Это кажется ужасно неэффективным, но Visual Studio прав, когда он предупреждает о касании InitializeComponent. На данный момент я действительно катал свою собственную локализацию WinForms Framework...

5 ответов

Я только что завершил проект С#.Net 3.5 с аналогичной проблемой. Мы писали плагин WinForms для существующего многоязычного приложения с 8 языками (включая английский).

Вот как мы это сделали:

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

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

Как только мы завершили большую часть работы и тестирования, мы ее локализовали.

    У каждой формы уже был файл.resx, но это было пусто. Мы установили свойство "Localizable" в true, файл.resx был заполнен такими вещами, как размеры кнопок и строки.

    Для каждого из других языков мы изменили свойство "Язык" на форме. Мы выбрали базовую версию каждого языка, например: "Испанский", а не "испанский (Чили)" и т.д., Чтобы он работал на каждый "испанский" диалект, я думаю.

    Затем мы проходили через каждый элемент управления, при необходимости переводили его текст и изменяли размер. Это создало.resx для каждого языка и комбинации форм.

Затем мы были оставлены для 8 языков 8.resx для каждой формы и 8.resx для общих строк. При компиляции в выходной папке содержалась.dll, которую мы создавали, а затем подпапку для каждого языка с.resources.dll в ней.

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

В общем, однажды мы обняли его, это было довольно легко и безболезненно.

Нам не нужно было писать какие-либо пользовательские настройки в загрузке формы

Связанные вопросы


Похожие вопросы

Кажется, что вы можете загрузить свое приложение Winforms и позволить вам начать переводить свои ярлыки и т.д. и видеть формы, пока вы это делаете. Множество других функций, таких как инкрементная трансляция и память переводов (если вы используете одни и те же термины снова и снова).

Выглядит довольно многообещающе (для Winforms) - не использовал его сам, тем не менее.

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

Например, реализация framework по умолчанию - создать файл.resx для каждого ресурса. В ASP.Net это означает каждый пользовательский/серверный элемент управления или страницу. Это не поддается простому обслуживанию, и если вы хотите переместить ресурсы в базу данных, вам необходимо реализовать своего собственного провайдера.

Мое знакомство с Winforms ограничено, но если вы используете Silverlight или WPF, тогда прочитайте работу Гая Смита-Ферье по этому вопросу: http://www.guysmithferrier.com/category/Internationalization.aspx . У него также есть некоторые инструменты, которые могут облегчить вашу жизнь: http://www.dotneti18n.com/Downloads.aspx .

Я работал с ним раньше и никогда не сталкивался с кем-либо еще с лучшей глубиной понимания предмета.

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

Локализация приложения

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

На некоторых платформах локализация осуществляется путем предоставления множества копий таких элементов пользовательского интерфейса, как таблицы строк и изображения. В WPF локализация не является столь же детальной. Здесь единицей локализации является XAML-файл (формально это скомпилированный BAML-pecypc, который встраивается в приложение). При желании поддерживать три различных языка, потребуется включить три BAML-pecypca. WPF будет выбирать из них подходящий на основании текущих настроек культуры на компьютере, на котором выполняется приложение. (Точнее - WPF будет использовать для принятия решения значение свойства CurrentUICulture в обслуживающем пользовательский интерфейс потоке.)

Разумеется, в таком процессе не было бы особого смысла, если бы нужно было создавать (и развертывать) универсальную сборку со всеми локализованными ресурсами. Это было бы не лучше создания отдельных версий приложения для каждого языка, поскольку приложение приходилось бы полностью компоновать заново при каждой необходимости добавить поддержку для новой культуры (или необходимости настроить текст в одном из существующих ресурсов). К счастью, в.NET эта проблема решается с помощью подчиненных сборок (satellite assemblies) , которые работают с приложением, но хранятся в отдельных подпапках.

При создании локализованного WPF-приложения каждый локализованный BAML-pecypc помещается в отдельную подчиненную сборку. Для того чтобы приложение могло использовать эту сборку, она размещается в подпапке под основной папкой приложения вроде подпапки fr-FR, предназначенной для французского языка. После этого приложение может связываться с этой подчиненной сборкой автоматически путем использования технологии зондирования (probing) , которая является частью.NET Framework, начиная с версии 1.0.

Самая большая трудность в локализации приложения заключается в рабочем процессе - другими словами в том, как извлечь XAML-файлы из проекта, сделать их локализованными, скомпилировать в подчиненные сборки и затем вернуть обратно в приложение. Это самая запутанная часть "истории" локализации в WPF, поскольку никаких средств (включая Visual Studio), обладающих поддержкой локализации на этапе проектирования, пока не существует. Вероятнее всего такие средства появятся в будущем, а пока WPF все равно позволяет выполнять все, что необходимо для локализации приложения, просто с применением чуть большего количества усилий.

Создание локализуемых пользовательских интерфейсов

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

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

    Устанавливайте для свойства Window.SizeToContent значение Width, Height или WidthAndHeight, так чтобы размер окна мог увеличиваться по мере необходимости. (Опять-таки, это является обязательным не всегда; все зависит от структуры окна, но в некоторых случаях это очень полезно.)

    Используйте для просмотра текста большого объема элемент ScrollViewer .

    При желании локализовать приложение на язык, имеющий значительно отличающийся набор символов, понадобится использовать другой шрифт. Сделать это можно путем локализации в пользовательском интерфейсе свойства FontFamily или применения сложного шрифта вроде Global User Interface, Global Sans Serif или Global Serif, каждый из которых поддерживает все языки.

    Может также потребоваться обдумать, каким образом компоновка будет работать при раскладке "справа налево" (вместо стандартной английской раскладки "слева направо"). Например, в арабском и иврите используется раскладка "справа налево". Этим поведением можно управлять посредством установки на каждой странице или в каждом окне приложения свойства FlowDirection. Более подробную информацию о раскладках "справа налево" можно найти в справке Visual Studio, в разделе Bidirectional Features (Средства двунаправленности).

Локализация - сложная тема. WPF предлагает работоспособное, но еще недостаточно зрелое решение. После того, как вы познакомитесь с основами, стоит заглянуть в документ Microsoft, посвященный локализации WPF, который доступен по адресу http://wpflocalization.codeplex.com вместе с кодом примеров. Можно ожидать, что в будущем поддержку локализации будет улучшена в инструментах проектирования, таких как Visual Studio и Expression Blend.

Подготовка приложения для локализации

Следующий шаг связан с включением поддержки локализации для проекта. Для этого потребуется внести только одно изменение, а именно - добавить в файл.csproj проекта где-нибудь в первом разделе Следующий элемент:

en-US

Это укажет компилятору, что языком (культурой) по умолчанию для приложения должен быть английский (США) (очевидно, что при необходимости можно выбрать другой язык). После внесения этой корректировки процесс компоновки изменится. При следующей компиляции приложения будет создана подпапка по имени en-US. Внутри этой папки будет находиться подчиненная сборка с таким же именем, как и у приложения, и расширением.resources.dll (например, LocalizableApplication.resources.dll).

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

Формально приложение локализуется не для какого-то конкретного языка, а для культуры, в которой учитываются региональные отличия. Культуры обозначаются с помощью двух разделенных дефисом идентификаторов. Первый указывает язык, а второй - страну. Следовательно, fr-CA означает французский язык, на котором разговаривают в Канаде, a fr-FR - французский, на котором общаются во Франции. Полный список имен культур и их идентификаторов можно найти в справке Visual Studio, в разделе, посвященном классу System.Globalization.Culturelnfo .

Это предполагает детальную локализацию, что может оказаться чрезмерным. К счастью, WPF позволяет локализовать приложение и на основе только языка. Например, при желании определить параметры так, чтобы они применялись для любого франкоговорящего региона, для культуры можно указать только идентификатор fr. Такой подход будет работать, если только не окажется доступной более специфическая культура, в точности соответствующая текущему компьютеру.

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

Когда среда CLR начинает зондировать папки на предмет наличия подчиненной сборки, она следует нескольким простым правилам очередности:

    Сначала она проверяет самый специфический из всех доступных каталог. Это означает, что CLR ищет подчиненную сборку, предназначенную для текущего языка и региона (вроде fr-FR).

    Если CLR не удается обнаружить такой каталог, она начинает искать подчиненную сборку, предназначенную для текущего языка (такого как fr).

    Если ей не удается найти и такой каталог, тогда генерируется исключение IOException.