Код
#Руководства

Как добавить в приложение поддержку нескольких языков

Разбираемся, как переводить приложения и какие с этим могут возникнуть трудности.

 vlada_maestro / shutterstock

Локализация приложения — достаточно сложная задача. Чтобы перевести приложение, нужно:

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

К счастью, разработчикам приходится решать только часть этих задач.

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

Весь код в статье будет на C# и XAML, но принцип для любых приложений один.

Репозиторий с кодом на GitHub

Верстаем WPF-приложение

Сразу же сверстаем небольшое приложение для Windows:

<Window.Resources>
   <Style TargetType="Button">
       <Setter Property="Background" Value="#444"/>
       <Setter Property="Foreground" Value="#f6f6f6"/>
   </Style>
</Window.Resources>
<Grid>
   <Grid.RowDefinitions>
       <RowDefinition Height="50"/>
       <RowDefinition />
       <RowDefinition Height="50"/>
   </Grid.RowDefinitions>
   <Border Grid.Row="0">
       <TextBlock Text="Header" Name="HeaderText" HorizontalAlignment="Center" VerticalAlignment="Center"/>
   </Border>
   <Border Grid.Row="1" Padding="5" Background="#333">
       <StackPanel Orientation="Vertical" MaxWidth="500" VerticalAlignment="Center">
           <DockPanel LastChildFill="True">
               <TextBlock Text="Enter your name: " Name="NameTextBlock" Margin="5" VerticalAlignment="Center" FontWeight="DemiBold"/>
               <TextBox Name="NameTextBox" Margin="5" Padding="5" VerticalAlignment="Center" Background="#444" Foreground="#f6f6f6"/>
           </DockPanel>
           <Button Name="ConfirmButton" Content="Confirm" Margin="5" Click="ConfirmButton_Click" IsDefault="True" Padding="5"/>
       </StackPanel>
   </Border>
   <Border Padding="5" Grid.Row="2">
       <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Name="LocaleButtons">
          
       </StackPanel>
   </Border>
</Grid>

Выглядит оно так:

В коде нет кнопок на нижней панели — они появятся после загрузки данных из файла.

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

Добавляем языки

Языки для приложения хранятся в файле locales.json. Его можно легко редактировать, поэтому в любой момент можно добавить новый язык:

{
   "locales":
   [
       {
           "name": "Ру",
           "values": [
               {
                   "name": "HeaderText",
                   "value": "Заголовок"
               },
               {
                   "name": "NameTextBlock",
                   "value": "Введите ваше имя: "
               },
               {
                   "name": "ConfirmButton",
                   "value": "Подтвердить"
               },
               {
                   "name": "HelloCaption",
                   "value": "Приветствуем!"
               },
               {
                   "name": "HelloMessage",
                   "value": "Здравствуйте, "
               }
           ]
       },
       {
           "name": "En",
           "values": [
               {
                   "name": "HeaderText",
                   "value": "Header"
               },
               {
                   "name": "NameTextBlock",
                   "value": "Enter your name: "
               },
               {
                   "name": "ConfirmButton",
                   "value": "Confirm"
               },
               {
                   "name": "HelloCaption",
                   "value": "Hello!"
               },
               {
                   "name": "HelloMessage",
                   "value": "Hello, "
               }
           ]
       },
 
   ]
}

Здесь есть русские и английские надписи для разных элементов. Они хранятся в массивах values в следующем виде:

  • name — название элемента;
  • value — переведённый текст.

В коде каждый язык будет представлять объект класса Locale:

public class Locale
{
   //Название языка
   private string name;
 
   //Словарь с надписями на этом языке
   //Ключ - название элемента, который нужно перевести
   //Значение - текст
   private Dictionary<string, string> values;
 
   public Locale(string name, Dictionary<string, string> values)
   {
       this.name = name;
       this.values = values;
   }
 
   public string Name
   {
       get
       {
           return this.name;
       }
   }
 
   public Dictionary<string, string> Values
   {
       get
       {
           return this.values;
       }
   }
}

Теперь можно приступать к написанию логики приложения.

Функция перевода языка

При запуске приложение загружает файл locales.json, получает из него данные и создаёт объекты класса Locale. Далее будут создаваться кнопки, при нажатии на которые меняется язык.

public partial class MainWindow : Window
{
   //Список языков
   private List<Locale> locales;
   //Текущий язык
   private Locale currentLocale;
   //Путь к файлу с языками
   private string localesPath = "locales.json";
  
   public MainWindow()
   {
       InitializeComponent();
 
       //После инициализации приложения загружаем языки
       locales = new List<Locale>();
       GetLocales();
   }
 
   private void GetLocales()
   {
       //Получаем данные из файла
       string json = File.ReadAllText(localesPath);
 
       //Конвертируем данные в объект с динамическими полями
       dynamic data = JsonConvert.DeserializeObject(json);
 
       //Проходимся по языкам из файла
       foreach(var item in data.locales)
       {
           //Создаём словарь
           Dictionary<string, string> values = new Dictionary<string, string>();
 
           //Помещаем все значения из файла в словарь
           foreach(var val in item.values)
           {
               values.Add(val.name.ToString(), val.value.ToString());
           }
 
           //Добавляем в список новый язык
           locales.Add(new Locale(item.name.ToString(), values));
       }
 
       //Обновляем кнопки
       UpdateButtons();
   }
 
   public void UpdateButtons()
   {
       //Удаляем старые кнопки
       LocaleButtons.Children.Clear();
 
       //Создаём новую кнопку для каждого языка и добавляем её на панель для кнопок
       foreach(Locale locale in locales)
       {
           Button btn = new Button();
 
           btn.Content = locale.Name;
           btn.Click += LocaleButton_Click;
           btn.Width = 25;
           btn.Height = 25;
           btn.Margin = new Thickness(5.0);
 
           LocaleButtons.Children.Add(btn);
       }
   }
 
   private void ConfirmButton_Click(object sender, RoutedEventArgs e)
   {
       if(currentLocale == null)
       {
           MessageBox.Show($"Hello, {NameTextBox.Text}!", "Hello!", MessageBoxButton.OK);
       }
       else
       {
           MessageBox.Show($"{currentLocale.Values["HelloMessage"]}{NameTextBox.Text}", currentLocale.Values["HelloCaption"], MessageBoxButton.OK);
       }
      
   }
 
   private void LocaleButton_Click(object sender, RoutedEventArgs e)
   {
       //Получаем кнопку, которая была нажата
       Button btn = (Button)sender;
 
       //Ищем язык по названию
       foreach(Locale locale in locales)
       {
           if (btn.Content.ToString() == locale.Name)
           {
               //Меняем текущий язык
               currentLocale = locale;
               break;
           }
       }
 
       //Обновляем надписи в приложении
       UpdateLocale();
   }
 
   private void UpdateLocale()
   {
       //Проверяем все записи в словаре текущего языка
       foreach(string key in currentLocale.Values.Keys)
       {
           //Ищем элемент, имя которого совпадает с ключом из словаря
           object elem = FindName(key);
 
           //Пробуем определить, какого типа может быть элемент, и прописываем различную логику
           //Вы можете сделать этот код более универсальным, но в данном приложении всего два типа элементов - кнопки и текстовые блоки
           try
           {
               Button btn = (Button)elem;
 
               if (btn != null)
               {
                   btn.Content = currentLocale.Values[key];
               }
           }
           catch (Exception e) { }
 
           try
           {
               TextBlock text = (TextBlock)elem;
               if(text != null)
               {
                   text.Text = currentLocale.Values[key];
               }
           }
           catch (Exception e) { }
       }
   }
}

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

Какие могут возникнуть трудности

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

Если же говорить о русском, то при переводе могут возникнуть сложности, которые не решатся обычной заменой текстов. Например, в английском языке нет изменений по родам, поэтому фраза «%username% uploaded new post» подойдёт пользователям обоих полов. В русском же «Татьяна опубликовал новый пост» будет выглядеть странно.

Правильное, но более дорогое решение — добавить отдельный вариант для женского рода: «Татьяна опубликовала новый пост». Если же нет возможности прописать дополнительный код, то есть и другие варианты:

  • Указывать окончание в скобках, хотя это и не очень хорошо выглядит: «Игорь прокомментировал (а) ваш пост».
  • Подбирать обезличенные варианты: «Новый комментарий от пользователя Игорь под вашим постом». Получается длинно и коряво.

Какую из этих зол выбрать, решать вам.

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

MessageBox.Show($"{currentLocale.Values["HelloMessage"]}{NameTextBox.Text}", currentLocale.Values["HelloCaption"], MessageBoxButton.OK);

Она покажет диалоговое окно, в котором строгий порядок слов.

Здесь лучше использовать паттерны. То есть строки, в которых определено положение всех используемых значений:

"Hello, %username%!"

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

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

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

Заключение

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

Жизнь можно сделать лучше!
Освойте востребованную профессию, зарабатывайте больше и получайте от работы удовольствие.
Каталог возможностей
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована