При разработке приложения определенное значение имеет его локализация, поскольку это напрямую влияет на число пользователей и, соотвественно, успешность продукта. Известна статистика по числу интернет-пользователей для различных языков, и напрашивается вывод о том, что, сделав перевод для группы некоторых определенных языков, можно значительно расширить аудиторию пользователей своей программы.
Наша команда трудится над проектом 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. Надеюсь кто-то найдет в этой информации что-то для себя полезное. Спасибо за внимание!
Автор: gralexey