Разработка многоязыковых приложений на XAML (часть 1)

Рынок приложений очень широк. К примеру, Магазин Windows работает в 230 странах и доступен на 106 языках, но лишь около 7 % приложений локализованы более чем на один язык. Рассмотрим следующий момент.

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

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

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

Я разделил эту статью на две части.

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

Цель этих статей — обобщить информацию, необходимую для создания многоязыковых приложений на XAML. Большая часть рекомендаций и сведений о функциях применимы как к Windows 8, так и к 8.1, если в тексте не указано, что функциональность применима исключительно к Windows 8.1.

Выбор языка и региона

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

Многоязычные пользователи могут задать несколько языков в Панели управления или в новом приложении «Параметры ПК». Например:

Приложение «Параметры ПК», отображающее выбор языка

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

Редактор манифеста Visual Studio, демонстрирующий язык по умолчанию

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

Windows.Globalization.ApplicationLanguages.Languages;

Соответствующий язык, использующийся чаще всего (в зависимости от полноты локализации приложения для этого языка) — ApplicationLanguages.Languages[0]. Этот язык применяется также для ресурсов, предоставляемых Windows через API для локализации, например модулей форматирования даты и времени.

Несмотря на то что приложение «разговаривает» на одном языке, пользователь может вводить содержимое на другом языке, а также менять клавиатуры/IME с помощью сочетания клавиш Win + пробел. Например, он может использовать англоязычную версию Windows, но общаться с членами семьи в социальном приложении на японском языке. Запросить текущий язык и регион ввода можно следующим образом:

string currentInputLanguage = Windows.Globalization.Language.CurrentInputMethodLanguageTag;

Кроме языка, очень важно учитывать региональный формат даты, времени, чисел и валют. Например, в США дата в сокращенном виде обычно записывается как месяц/день/год, а в Великобритании — день/месяц/год. Так что даже если вы не локализовали приложение для британского английского, возможно, стоит учесть регион при форматировании даты, времени, чисел и валют.

Выбор шрифта и резервные шрифты

Выбор шрифта по умолчанию

При отображении текста с использованием любых элементов управления XAML, например TextBlock, RichTextBlock, TextBox или RichTextBox, шрифт для него можно задать с помощью свойства «семейство шрифтов». Значение свойства по умолчанию, если шрифт не задан в элементе управления или через стиль, — Segoe UI. Файл generic.xaml отвечает за стили по умолчанию для элементов управления и шаблонов. Раньше он входил в состав Windows SDK в виде отдельного файла; но в предварительной версии Windows 8.1 встроен прямо в инфраструктуру XAML. Файл generic.xaml содержит ресурс FontFamily, называющийся ContentControlThemeFontFamily. Его значение — Segoe UI, и он используется в качестве шрифта для названных стилей TextBlock, а также как шрифт по умолчанию для элементов управления пользовательского интерфейса.

Резервные шрифты

Шрифты, поставляемые с Windows, совместно охватывают большую часть символов, определенных в Unicode. Однако по отдельности каждый шрифт не поддерживает все символы, которые могут встретиться в содержимом приложения. Если фрагмент текста содержит символы, не определенные в выбранном шрифте, текстовая система XAML находит подходящий шрифт с поддержкой этих символов и использует его. Эта система по большей части работает автоматически, однако некоторые восточноазиатские языки имеют общие коды Unicode для символов, но немного различаются по отображению. Чтобы система резервных шрифтов понимала, каким шрифтом отображать символ, ей необходимо знать, какой язык используется.

В XAML язык интерфейса — это свойство, наследуемое по дереву пользовательского интерфейса. После того как это свойство установлено для элемента, оно применяется ко всем дочерним элементам, если они не задают значения языка отдельно. В разметке язык может быть задан с помощью атрибута xml:lang; например, в MainPage.xaml это выглядит так:

<Page
  x:Class="MyTestApp.MainPage"
  xmlns="https://schemas.microsoft.com/winfx/2006/XAML/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/XAML"
  xmlns:local="using:MyTestApp"
  xml:lang="en-us">
…
</Page>

В программном коде язык можно задать с помощью свойства Language. Ниже приведен код, который задает язык в качестве языка приложения из механизма сопоставления, описанного выше. Пример из app.xaml.cs:

protected override void OnLaunched(LaunchActivatedEventArgs e)
{

  if (rootFrame == null)
  {
    // Создаем Frame, который будет контекстом навигации и осуществит переход на первую страницу
    rootFrame = new Frame();

    //Задаем язык по умолчанию
    rootFrame.Language = Windows.Globalization.ApplicationLanguages.Languages[0];

    if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
    {
      //TODO: Загружаем статус из ранее приостановленного приложения
    }

    // Устанавливаем frame в текущем окне
    Window.Current.Content = rootFrame;
  }

}

Рекомендации. Задайте язык или через API, как указано выше, или в разметке через локализацию с помощью механизма x:Uid. В части 2 мы будем обсуждать x:Uid.

API шрифта для языка

Windows содержит API, позволяющие определить подходящие шрифты для различных целей отображения в различных языках. Объект LanguageFontGroup имеет набор свойств, предоставляющих подходящие шрифты для различных сценариев. Например, в таблице ниже показаны семейства шрифтов и толщина букв для нескольких языков.

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

LanguageFontGroup API можно использовать для запроса подходящих шрифтов в XAML. Следующий код, например, обновляет ресурс ContentControlThemeFontFamily значением из свойства UIText языка приложения. Это делается в app.xaml и поэтому выполняется до создания интерфейса. Обратите внимание — я делаю это в OnWindowCreated, поскольку при наличии нескольких окон каждое окно находится в собственном потоке, а ресурсы у каждого потока — свои.

using Windows.Globalization;
using Windows.Globalization.Fonts;

namespace MyTestApp
{
  sealed partial class App : Application
  {
    public App()
    {
      this.InitializeComponent();
    }

    /// <summary>
    /// Вызывается при создании нового окна, ресурсы управляются каждым окном отдельно
    /// </summary>
    /// <param name="args"></param>
    protected override void OnWindowCreated(WindowCreatedEventArgs args)
    {
      base.OnWindowCreated(args);

      string langTag = ApplicationLanguages.Languages[0];
      LanguageFontGroup lfg = new LanguageFontGroup(langTag);
      FontFamily UIText = new FontFamily(lfg.UITextFont.FontFamily);
      SetResource("ContentControlThemeFontFamily", UIText);
    }

    void SetResource(object key, object value)
    {
      if (this.Resources.ContainsKey(key))
        this.Resources[key] = value;
      else
        this.Resources.Add(key, value);
    }
  }
}

Справа налево и двунаправленные

Не все языки используют систему написания слева направо, как английский и другие европейские языки. Арабский и иврит используют направление справа налево, и для этого в макете необходимо отзеркаливать интерфейс. К примеру, приложение «Карты» выглядит так:

Слева направо
Справа налево

XAML позволяет задавать направление элементов интерфейса, наследуемых от FrameworkElement, с помощью свойства FlowDirection. Данное свойство наследуется по визуальному дереву и применяется к большинству элементов. Исключения — Image и иконка AppBarButton.

XAML не задает FlowDirection автоматически в зависимости от языка. Приложение должно самостоятельно сделать это в программном коде или через локализацию. Чтобы динамически назначать FlowDirection в зависимости от языка программы, используйте следующий код на MainPage.xaml или других страницах:

using Windows.UI.Xaml.Controls;
using Windows.ApplicationModel.Resources.Core;
using Windows.UI.Xaml;

public MainPage()
{
  // Задаем направление на основе текущего направления языка
  if (ResourceManager.Current.DefaultContext.QualifierValues["LayoutDirection"] == "RTL")
  {
    this.FlowDirection = FlowDirection.RightToLeft;
  }
  this.InitializeComponent();
}

Чтобы воспользоваться локализацией, примените x:Uid на элементе Page и задайте направление через файл ресурсов resw. Подробнее о x:Uid будет рассказано в части 2.

Двунаправленность и текст

Направление справа налево влияет на макет и презентацию текста. В языках с таким написанием текст читается справа налево. Внутри одного абзаца могут содержаться куски текста с написанием слева направо и справа налево. Алгоритм Unicode устанавливает правила отображения подобного текста.

Согласно спецификациям Unicode, каждому символу назначается направленность и сила. К примеру, латинские символы (A–Z) имеют сильное левое направление, арабские символы — сильное правое направление, европейские числа слабые, а знаки пунктуации и пробелы нейтральные.

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

Если двунаправленный текст содержит куски смешанного текста с направлением справа налево и слева направо, направление текста управляет относительным макетом каждого отрывка текста. В случае с направлением справа налево блоки текста располагаются справа налево. Возьмем, к примеру, следующий текст: «Ahmad and Hiba are hiking».  Когда эти имена переводятся на язык урду:

В памяти и на диске строка остается в таком виде:

Обзор памяти в Visual Studio, демонстрирующий строку. Обратите внимание, что символы языка урду расположены в обратном порядке

Для текстовых элементов управления XAML содержимое отдельных блоков текста располагается в зависимости от их направления, а FlowDirection используется для управления порядком блоков. Пример кода и разметки:

<Grid>
  <StackPanel Orientation="Vertical" Margin="50" HorizontalAlignment="Left">
    <TextBlock>LeftToRight:</TextBlock>
    <Border Background="#FF3E64AA">
      <TextBlock FlowDirection="LeftToRight" x:Name="tb1"/>
    </Border>
    <TextBlock Margin="0,10,0,0">RightToLeft:</TextBlock>
    <Border Background="#FF3E64AA">
      <TextBlock FlowDirection="RightToLeft" x:Name="tb2"/>
    </Border>
  </StackPanel>
  <Grid.Resources>
    <Style TargetType="TextBlock">
      <Setter Property="FontSize" Value="20"/>
    </Style>
  </Grid.Resources>
</Grid>

public MainPage()
{
  this.InitializeComponent();
  string text = "احمد and حبا are hiking.";
  tb1.Text = text;
  tb2.Text = text;
}

Будет отображен в виде:

Диаграмма, демонстрирующая отображение текста в режимах слева направо и справа налево

Элементы управления для отображения текста

Элементы управления для отображения текста выводят текст, используя FlowDirection для определения порядка чтения абзацев. Пользователи-билингвы часто сталкиваются с содержимым, которое расположено в другом направлении, чем в языке, для которого сделана локализация приложения. Например, в медиапроигрывателе название композиции может быть написано на арабском языке, и вам хочется отобразить его правильно. Хорошо, если направление у него одно, но если имеется смешанный двунаправленный текст, то пользователь может сильно удивиться.

Для решения этой проблемы у элементов управления TextBlock и RichTextBlock есть новое свойство TextReadingOrder, в качестве значения которого можно присвоить DetectFromContent. Когда свойство задано, оно выполняет итерацию по всему содержимому в поисках первого сильного символа, а затем использует его для общего направления, переопределяя направление потока. Данное свойство не влияет на горизонтальное выравнивание, которое определяется свойствами FlowDirection и HorizontalAlignment.

Например:

<Grid>
  <StackPanel Orientation="Vertical" Margin="50" HorizontalAlignment="Left">
      <TextBlock>Default reading order:</TextBlock>
    <Border Background="#FF3E64AA">
      <TextBlock x:Name="tb1"/>
    </Border>
    <TextBlock Margin="0,10,0,0">Detect from content:</TextBlock>
    <Border Background="#FF3E64AA">
      <TextBlock x:Name="tb2" TextReadingOrder="DetectFromContent"/>
    </Border>
  </StackPanel>
  <Grid.Resources>
    <Style TargetType="TextBlock">
      <Setter Property="FontSize" Value="20"/>
    </Style>
  </Grid.Resources>
</Grid>

Код выше отображает результаты в следующем виде:

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

Элементы управления вводом текста

Если список языков, добавленных пользователем в Настройки ПК/Контрольную панель, включает двунаправленный язык, то после выхода/входа в систему Windows пользователь может управлять направлением текста:

  • На клавиатуре: нажав сочетание клавиш Левый Ctrl + Shift для режима ввода слева направо и Правый Ctrl + Shift для режима справа налево.
  • На экранной клавиатуре: нажав кнопку Ctrl, а затем кнопку направления:

Владеющий арабским языком пользователь, живущий в США, может использовать приложение с английской локализацией для общения со своей семьей, но на арабском языке. Несмотря на то что интерфейс приложения может быть расположен слева направо, таким образом можно управлять языком ввода.

Элемент управления RichEditBox сохраняет направление каждого абзаца в соответствии с разметкой RTF. Вы можете запросить направление определенного диапазона с помощью:

using Windows.UI.Text;



FormatEffect fe = RichEditBox1.Document.GetRange(0, 1).ParagraphFormat.RightToLeft;
bool isRTL = (fe == FormatEffect.On);

SideBar: поэкспериментируйте с различными опциями двунаправленного текста, включив визуализацию управляющих символов Unicode через контекстное меню в Блокноте и вставив их в нужном месте.

Изображения

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

Вы можете повернуть изображение, задав FlowDirection=RightToLeft прямо в теге image, через программный код или локализацию. Воспользуйтесь загрузчиком ресурсов для выбора другого изображения, если текст направлен справа налево. Делается это с помощью квалификатора направления макета; к примеру, в нем содержатся два изображения: mylogo.jpg и mylogo.layoutdir-RTL.jpg, последнее используется для направления справа налево.

Текстовые глифы в кнопках

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

  • Кнопки наследуют стиль направления справа налево, что может вызвать конфликт с интервалами символов для получения корректного пересечения. Если вы используете AppBarButton, она не будет применять направление справа налево для области глифов; таким образом, эта проблема решается.
  • Глифы-иконки не отражаются автоматически, поэтому если вы используете стрелки направления, то должны переназначить привязку глифов для выбора другого направления, поскольку списки имеют направление справа налево для этого направления макета.

Подведем итоги

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

Сэм Спенсер (Sam Spencer), старший руководитель программ, Windows