Локализация приложений под OS X

в 11:01, , рубрики: Cocoa, iOS, osx, xcode, Блог компании Mail.Ru Group, локализация

Локализация приложений под 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.

Локализация приложений под OS X

Примеры содержания файлов локализаций:

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:

Локализация приложений под OS X

Локализация приложений под OS X

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

3. Динамическая локализация

Сейчас применяется у нас в проекте. Это скорее модификация предыдущего варианта, суть заключается в том, что язык меняется динамически через настройки программы, и посылается нотификейшн, по которому мы обходим локализируемые интерфейсы, где вызывается свой аналог NSLocalizedStringlocalizedStringForKey на основе [NSBundle localizedStringForKey:value:table:], который в runtime берет из ресурсов приложения искомое значение строчки на выбранном в настройках программы языке. Из нюансов стоит отметить что

  • на практике для некоторых языков может не оказаться некоторых переводов и нужно взять перевод из дефолтного;
  • для языка, локализации которого нет в проекте, нужно делать проверку и возвращать значение языка по умолчанию;
  • для некоторых языков (например португальского) может отличаться обозначение языка в системе (pt) и среде Xcode (pt-BR), и поэтому нужно делать дополнительную проверку.

В целом, для каждого подхода есть своя область применения. Локализацию через IB, например, лучше применять для приложений, где поддерживается не очень много языков, большое число текстовых элементов, и для разных локализаций геометрия интерфейса отличается. Программную локализацию лучше делать для проектов, где наоборот много языков и длины строк для разных локализаций не очень различаются. В некоторых случаях можно заморочиться и сделать динамическую локализацию, тут плюс в том, что дополнительно можно элегантно переключать язык на лету, и пользователь будет радоваться. Не стоит забывать Auto Layout. Надеюсь кто-то найдет в этой информации что-то для себя полезное. Спасибо за внимание!

Автор: gralexey

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js