Локализация приложений под OS X
При разработке приложения определенное значение имеет его локализация, поскольку это напрямую влияет на число пользователей и, соотвественно, успешность продукта. Известна статистика по числу интернет-пользователей для различных языков, и напрашивается вывод о том, что, сделав перевод для группы некоторых определенных языков, можно значительно расширить аудиторию пользователей своей программы.
Наша команда трудится над проектом ICQ и Агентом Mail.Ru (привет Дима, Вова, Леша) под OS X, и локализация продукта на разных этапах развития осуществлялась по-разному, для каждого из подходов обнаруживались свои достоинства и недостатки. Некоторым накопленным опытом я и хочу поделиться.
Проще всего локализацию осуществить через стандартные средства 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«ах и переводить все автоматом, но в нашем случае практика показала, что лучше не усложнять систему и не использовать лишние надстройки. Такой подход позволяет избавиться от недостатков предыдущего и заключается в том, что аутлетам элементов интерфейса программно задаются заголовки функцией 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:
Тут нужно проставлять, как при изменении текста контрола автоматом изменять его геометрию, и выравнивать следующие за ним кнопки. Здорово.
Сейчас применяется у нас в проекте. Это скорее модификация предыдущего варианта, суть заключается в том, что язык меняется динамически через настройки программы, и посылается нотификейшн, по которому мы обходим локализируемые интерфейсы, где вызывается свой аналог NSLocalizedString — localizedStringForKey на основе [NSBundle localizedStringForKey: value: table:], который в runtime берет из ресурсов приложения искомое значение строчки на выбранном в настройках программы языке. Из нюансов стоит отметить чтона практике для некоторых языков может не оказаться некоторых переводов и нужно взять перевод из дефолтного; для языка, локализации которого нет в проекте, нужно делать проверку и возвращать значение языка по умолчанию; для некоторых языков (например португальского) может отличаться обозначение языка в системе (pt) и среде Xcode (pt-BR), и поэтому нужно делать дополнительную проверку. В целом, для каждого подхода есть своя область применения. Локализацию через IB, например, лучше применять для приложений, где поддерживается не очень много языков, большое число текстовых элементов, и для разных локализаций геометрия интерфейса отличается. Программную локализацию лучше делать для проектов, где наоборот много языков и длины строк для разных локализаций не очень различаются. В некоторых случаях можно заморочиться и сделать динамическую локализацию, тут плюс в том, что дополнительно можно элегантно переключать язык на лету, и пользователь будет радоваться. Не стоит забывать Auto Layout. Надеюсь кто-то найдет в этой информации что-то для себя полезное. Спасибо за внимание!