В интернете легко найти статьи по локализации iOS, где описываются все основные этапы. Проблема в том, что чаще нам на глаза попадается вариант ручного заполнения файла *.strings. Это довольно муторный подход и даже небольшая автоматизация в этом нам бы пригодилась. Ещё в iOS 8 Apple добавила возможность частичной автоматизации перевода приложения посредством экспорта и импорта локализованных строк через XLIFF-документ.
XLIFF (XML Localization Interchange File Format) — это обыкновенный XML, соотвествующий стандарту для обмена локализованными данными.
Я посчитал, что этот способ незаслуженно обходят стороной или упоминают его вскользь. А ведь он позволяет достать все строки для перевода из исходников (m, swift) и ресурсов (.storyboard, .xib) и объединить их в один файл *.xliff. А после может вставить перевод из него в проект. Остается лишь не забывать использовать NSLocalizedString.
NSLocalizedString
Разметка XLIFF-документа легко ложится на NSLocalizedString, который по умолчанию используется для работы с локализованными строками. Если мы пишем на Swift, то это функция:
public func NSLocalizedString(key: String, tableName: String? = default, bundle: NSBundle = default, value: String = default, comment: String) -> String
Если же мы еще пишем на Objective-C, то нужно использовать си макросы:
#define NSLocalizedString(key, comment)
#define NSLocalizedStringFromTable(key, tbl, comment)
#define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment)
#define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment)
Они имеют одинаковые аргументы и идентичную функциональность.
- key Ключ, по которому лежит переведенная строка.
- tableName Таблица, в которой находится ключ. Соответствует имени файла с расширением tableName.strings. Не обязательный параметр. По умолчанию используется Localizable.strings.
- bundle Бандл, в котором находится таблица с ключами и переводами. Не обязательный параметр. По умолчанию используется NSBundle.mainBundle().
- comment Комментарий для переводчика. Обязательно пишите его! Он поможет вам в будущем сориентироваться в коде.
- value Значение, возвращаемое, если локализованная строка для ключа не была найдена в таблице.
Аргументы NSLocalizedString соответствуют содержимому XLIFF-файла:
- Таблицы с ключами, на основе которых Xcode создаст файлы типа *.strings. Имена для них берутся на этапе создания xliff-файла из параметра tableName.
- Оригинальный текст из параметра key.
- Текст для перевода, который нужно заполнить.
- Комментарий для переводчика из параметра comment.
Дo iOS 8
В старые времена нам приходилось на несколько часов становиться секретаршами, чтобы пробежаться и сделать несколько монотонных вещей:
- Вставить NSLocalizedString, если не сделали это сразу.
- Придумать тег NSLocalizedString("TITLE", comment: "Заголовок первого экрана") и написать комментарий, если успеваем.
- Скопировать этот тег в файл Localizable.strings.
- Вставить перевод для тега. "TITLE" = "My App".
- Скопировать новую строчку в отдельный документ (например, Google Docs), чтобы переводчику было удобнее перевести.
К этому алгоритму добавлялись условия, когда тег уже существует и нужно использовать его или придумать новый, когда переводы разбиты на разные файлы или бандлы. В этой последовательности рутинного копипаста было легко допустить ошибку или "уснуть". К тому же у нас есть .storyboard или .xib файлы, и в них приходится делать IBOutlet, чтобы перевести весь текст в них из кода.
После iOS 8
С использованием XLIFF наш воркфлоу немного изменился.
- В коде, когда добавляем текст для UI, сразу пишем его в NSLocalizedString ("My App", comment: "Заголовок первого экрана"). Если язык разработки приложения Английский и вы его не меняли.
- Когда настало время, чтобы перевести приложение, экспортируем XLIFF-документ. В итоге у нас получаются файлы, соответсвующие поддерживаемым языкам. Например: ru.xliff, de.xliff.
- После того как переводы заполнены, импортируем их.
В результате Xcode сам создаст все необходимые файлы типа *.strings на основе xliff-файла.
А как же .storyboard и .xib ?
Строки из них Xcode также экспортирует в таблицы с именами как у их исходных файлов.
XLIFFy
Но остается одна проблема. Чем открыть файлы xliff? Когда Apple представила такую возможность, редакторов для этих файлов почти не было, а те что были, имели неудобный интерфейс. Сейчас Mac App Store полон ими на любой вкус. Но в то время я не нашел для себя подходящую программу и решил написать сам. XLIFFy
Пример
У нас есть демо-приложение с окном авторизации, в которое мы должны будем добавить русскую локализацию. По умолчанию Xcode создает проект с английским языком разработки. Это подразумевает, что весь ваш текст в UI будет на нем.
Добавим русский язык в проект.
- Проект
- Настроки проекта
- Вкладка Info
+
внизу списка Localization
После добавления нового языка. Будут сгенерированы файлы .strings для .storyboard или *.xib.
Начнем с того, что откроем ViewController.swift
и взглянем на метод signInAction(:_)
.
class ViewController: UIViewController {
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var signInButton: UIButton!
@IBAction func signInAction(sender: AnyObject) {
if usernameTextField.text == "user" && passwordTextField.text == "pass" {
// success
} else {
// fail
let alert = UIAlertController(
title: "Error",
message: "Username or Password is not correct",
preferredStyle: .Alert
)
let okAction = UIAlertAction(
title: "OK",
style: .Cancel,
handler: nil
)
alert.addAction(okAction)
presentViewController(alert, animated: true, completion: nil)
}
}
}
У нас есть UIAlertController, который должен показать пользователю описание ошибки, если он ввел неправильный логин или пароль.
Переведем заголовок.
let alert = UIAlertController(
title: NSLocalizedString("Error", comment: ""),
message: "Username or Password is not correct",
preferredStyle: .Alert
)
Часто в проекте повторяется одна строка, например “Error”, и было бы хорошо, чтобы и перевод для нее был один. В таком случае нам везде, где используется эта строка нужно вызывать метод с ней в качестве аргумента.
NSLocalizedString("Error", comment: "")
В результате перевод этой строки будет лежать в единственном экземпляре в файле Localizable.strings. Этот файл используется по умолчанию если не указывается имя другого.
Добавим сообщение об ошибке.
let message = NSLocalizedString(
"Username or Password is not correct",
tableName: "Auth",
comment: "Сообщение о неверном логине или пароле"
)
let alert = UIAlertController(
title: NSLocalizedString("Error", comment: ""),
message: message,
preferredStyle: .Alert
)
Сейчас мы уже добавили tableName, чтобы все строки, относящиеся к сценарию авторизации, лежали в отдельном файле Auth.strings, и комментарий, чтобы переводчику было понятнее, к какому контексту относится текст для перевода.
У нас еще много строк для перевода в Main.storyboard. Но мы не будем ничего с ними делать, кроме добавления комментариев. Чтобы добавить комментарий к элементу Interface Builder'а, выберем кнопку "Sign In" и в Identity Inspector найдем блок Document с разделом Notes и напишем "Кнопка авторизации".
Теперь можно экспортировать из нашего проекта файл для локализации.
- Проект
- В строке меню, Editor
- В выпадающем меню Export For Localization...
В результате у нас появился файл ru.xliff. Откроем его в редакторе XLIFFy или воспользуемся бесплатным аналогом из Mac App Store. Если вы выбрали XLIFFy, то справа будут перечислены имена таблиц переводов. Это и стандартный файл переводов Localizable.strings, и таблицы с именами, как у файлов .storyboard или .xib, из которых они были получены. Также есть таблица для info.plist, в которой можно перевести название приложения для разных стран. Есть и таблица Auth.strings, с которой мы связали в коде перевод теста ошибки.
После того, как у нас все переведено, импортируем в Xcode.
- Проект
- В строке меню, Editor
- В выпадающем меню Import Localizations...
Может появиться окно с предупреждениями, если некоторые строки остались без перевода. Особенно часто это встречается, из-за непереведенного info.plist. Во время импорта Xcode создает на основе таблиц переводов файлы *.strings, если их нет, и вставляет в них ключ, значение и комментарий. Лучше их не редактировать вручную, при неправильном форматировании может перестать работать export / import.
После иморта переводов самое время проверить, как отображается наше приложение на разных языках. Перезапускать его на симуляторе или на девайсе, конечно, нужно, но довольно долго. Куда быстрее это можно сделать в Interface Builder.
Откройте Main.storyboard, включите Assistant Editor и выберите в его выпадающем списке Preview. В этом режиме вы можете просмотреть, как будет выглядеть ваше приложение на разных девайсах и в разных локализациях.
Меняем Development Language
В очень редких случаях может понадобиться поменять Development Language, например, на Russian, потому что весь ваш дизайн сперва создается с русским текстом.
Вам нужно будет закрыть Xcode, открыть файл проекта в текстовом редакторе <project_name>.xcodeproj/project.pbxproj, найти пару строчек
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
и заменить их на
developmentRegion = Russian;
hasScannedForEncodings = 0;
knownRegions = (
ru,
Base,
);
Подробнее вы можете прочитать тут
Итого
Использование файлов xliff имеет как свои плюсы, так и минусы:
Плюсы:
- Больше не нужно заниматься монотонным копипастом.
- Удобный перевод .storyboard и .xib.
- Весь менеджмент файлов *.strings берет на себя Xcode.
- Вся работа по локализации сводится к использованию NSLocalizedString.
Минусы:
- Ключом выступает не абстрактная строка, а текст на Development Language. Если меняется оригинальный текст, то его приходится заново переводить.
- Для повторяющихся строк из .storyboard и .xib не получится добавить одного перевода для всех. Это сделано, потому что строки связаны с разными элементами UI, и один вариант перевода может оказаться слишком большим или неподходящим для контекста использования во втором случае.
- Нет обработки числительных и единиц измерения. Для этого нужно создавать специальный файл *.stringsdict. Handling Noun Plurals and Units of Measurement