Локалізація додатків під OS X



При розробці програми певне значення має його локалізація, оскільки це безпосередньо впливає на число користувачів і, відповідно, успішність продукту. Відома статистика по числу інтернет-користувачів для різних мов, і напрошується висновок про те, що, зробивши переклад для групи певних мов, можна значно розширити аудиторію користувачів своєї програми.

Наша команда працює над проектом ICQ і Агентом Mail.Ru (привіт Діма, Вова, Льоша) під OS X, і локалізація продукту на різних етапах розвитку здійснювалася по-різному, для кожного з підходів виявлялися свої переваги і недоліки. Деяким накопиченим досвідом я і хочу поділитися.

1. Локалізація через Interface BuilderНайпростіше локалізацію здійснити через стандартні засоби Interface Builder (IB). При цьому нові мови можна додати в настроюваннях проекту MyTestProject > Info > Localizations. Також тут вже існуючі файли інтерфейсу можна обробити автоматично (використовується команда виду
genstrings-o en.lproj *.m
).

При створенні нового файлу з інтерфейсом достатньо клікнути на кнопку Localize у вікні Utilities. Інтерфейс в цілому дружній і зрозумілий. Для отримання рядків з xib'а, якщо хочеться локалізувати не через IB, а вручну, має сенс використовувати консольну команду вигляду:

ibtool --export-strings-file SomeViewController.utf16-strings.tmp \
SomeViewController.xib && iconv-f utf-16-t utf-8 SomeViewController.utf16-strings.tmp \
> SomeViewController.strings && rm SomeViewController.utf16-strings.tmp

На виході буде щось на кшталт:

cat SomeViewController.strings 

/* Class = "NSTextFieldCell"; title = "Вам потрібно запросити авторизацію"; ObjectID = "4"; */
"4.title" = "Вам потрібно запросити авторизацію";

/* Class = "NSTextFieldCell"; title = "Вкажіть текст запиту"; ObjectID = "9"; */
"9.title" = "Вкажіть текст запиту";

/* Class = "NSTextFieldCell"; title = "До групи"; ObjectID = "15"; */
"15.title" = "До групи";

/* Class = "NSTextFieldCell"; title = "Скасування"; ObjectID = "23"; */
"23.title" = "Скасування";

Отриманий переклад можна коригувати вручну в будь-якому редакторі або застосувати яку-небудь автоматизацію, після чого новий файл рядків SomeViewController.strings можна повернути в xib так:

iconv-f utf-8-t utf-16 SomeViewController.strings > \
SomeViewController.utf16-strings.tmp &&
ibtool --strings-file SomeViewController.utf16-strings.tmp \
SomeViewController.xib --write SomeViewController-updated.xib && \
rm SomeViewController.utf16-strings.tmp

Перевагою тут є простота і наочність, візуальний контроль «верстки» для кожної мови. До недоліків можна віднести проблеми з мерджем в git, те, що при великому числі підтримуваних мов, зміни в локалізації потрібно робити через IB, що не завжди зручно. Як варіант, можна ще використовувати скрипти, такі, як наведений вище, тримати переклад в xib'ах і переводити все автоматом, але в нашому випадку практика показала, що краще не ускладнювати систему і не використовувати зайві надбудови.

2. Програмна локалізація через NSLocalizedStringТакий підхід дозволяє позбутися від недоліків попереднього і полягає в тому, що аутлетах елементів інтерфейсу програмно задаються заголовки функцією
NSLocalizedStrings
:

self.startButton.stringValue = NSLocalizedString(@"StartCaption", @"Start button in menu");
self.pauseButton.stringValue = NSLocalizedString(@"PauseCaption", @"Pause button in menu");

Тут перший аргумент функції — ключ, за яким здійснюється пошук відповідної локалізованої рядки у відповідному файлі Localizable.strings.



Приклади змісту файлів локалізацій:

Localizable.strings (Russian):
"StartCaption" = "Старт!";
"PauseCaption" = "Пауза!";

Localizable.strings (Engligh):
"StartCaption" = "Start!";
"PauseCaption" = "Pause!";

Варто відзначити, що, якщо ключ
Localizable.strings
відсутній, то
NSLocalizedStrings
повертає саме його значення. Є спокуса використовувати в якості ключа його значення, ми так робили деякий час — тут і красивий зрозумілий ключ, і не страшно забути проставити значення, але на ділі стало зрозуміло, що, по-перше, більше незручностей доставляє те, що значення ключа може знадобитися проставити відмінним від самого ключа в базовому мовою, і це виходить негарно, і, по-друге, все-таки зручніше контролювати коректність локалізації вилазить в графічний інтерфейс ключу. Другий аргумент
NSLocalizedStrings
— коментар, не грає особливої ролі, він використовується головним чином при генерації рядка файлів на основі існуючого коду для пояснення, до чого саме відноситься рядок. Отримати актуальний файл з рядками для перекладу можна за допомогою скрипта виду:

#!/bin/bash
export LANG=C LC_CTYPE=C LC_ALL=C
for file in `find . -name "*.m"`
do
genstrings-a $file
done
cat Localizable.strings | sort-u | sed '/\/\\*/d' > actual.strings

Поряд з
NSLocalizedString
можна відразу використовувати метод
[NSBundle localizedStringForKey:value:table:]
, який на ділі і смикається (для головного бандла і з nil в таблиці) — його має сенс застосовувати при роботі, наприклад, з бібліотеками, коли хочеться завантажувати рядки з кастомного .strings файлу (його ім'я тоді вказуємо у таблиці). Використовуваний мова визначається на основі значень ключа
AppleLanguages
NSUserDefaults
.

Якщо довжини перекладів для різних мов сильно відрізняються, і інтерфейс пливе, то поряд з програмним вирівнюванням на основі розміру контрола

NSSize myButtonSize = [self.myButton.stringValue sizeWithAttributes:[NSDictionary dictionaryWithObject:self.myButton.font forKey:NSFontAttributeName]];

можна, і це в цілому зручніше, використовувати Auto Layout:





Тут потрібно проставляти, як при зміні тексту контрола автоматом змінювати його геометрію, і вирівнювати наступні за ним кнопки. Здорово.

3. Динамічна локалізаціяЗараз застосовується у нас в проекті. Це скоріше модифікація попереднього варіанту, суть полягає в тому, що мова змінюється динамічно через налаштування програми, і надсилається нотификейшн, за яким ми обходимо локализируемые інтерфейси, де викликається свій аналог
NSLocalizedString
localizedStringForKey
на основі
[NSBundle localizedStringForKey:value:table:]
, який в runtime бере з ресурсів програми шукане значення рядка на обраному в налаштуваннях програми мовою. З нюансів варто відзначити, що
  • на практиці для деяких мов може не виявитися деяких перекладів і потрібно взяти переклад з дефолтного;
  • для мови, локалізації якого немає в проекті, потрібно робити перевірку і повертати значення мови за замовчуванням;
  • для деяких мов (наприклад португальської) може відрізнятися позначення мови в системі (pt) і середовищі Xcode (pt-BR), і тому потрібно робити додаткову перевірку.
В цілому, для кожного підходу є своя область застосування. Локалізацію через IB, наприклад, краще застосовувати для додатків, де підтримується не дуже багато мов, велика кількість текстових елементів, і для різних локалізацій геометрія інтерфейсу відрізняється. Програмну локалізацію краще робити для проектів, де навпаки багато мов і довжини рядків для різних локалізацій не дуже різняться. В деяких випадках можна заморочити і зробити динамічну локалізацію, тут плюс в тому, що додатково можна елегантно перемикати мову на льоту, і користувач буде радіти. Не варто забувати Auto Layout. Сподіваюся, що хтось знайде в цій інформації щось для себе корисне. Спасибі за увагу!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.