iOS Localization: XLIFF

54ecd82bbca54bc3a6b9c731c731d78a.png

В интернете легко найти статьи по локализации 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


27fd85f0d45f469bb07cfce1b4751db9.png

После добавления нового языка. Будут сгенерированы файлы .strings для .storyboard или *.xib.


fbf1c28c5af84acc9ce97dffaaef06b0.png

Начнем с того, что откроем 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 и напишем «Кнопка авторизации».


d4d20188d69a42e4ad4fc29754d91c32.png

Теперь можно экспортировать из нашего проекта файл для локализации.


  • Проект
  • В строке меню, Editor
  • В выпадающем меню Export For Localization…

Export For Localization

В результате у нас появился файл ru.xliff. Откроем его в редакторе XLIFFy или воспользуемся бесплатным аналогом из Mac App Store. Если вы выбрали XLIFFy, то справа будут перечислены имена таблиц переводов. Это и стандартный файл переводов Localizable.strings, и таблицы с именами, как у файлов .storyboard или .xib, из которых они были получены. Также есть таблица для info.plist, в которой можно перевести название приложения для разных стран. Есть и таблица Auth.strings, с которой мы связали в коде перевод теста ошибки.


8ad9424794694dc194499594de2a6b42.png

После того, как у нас все переведено, импортируем в Xcode.


  • Проект
  • В строке меню, Editor
  • В выпадающем меню Import Localizations…

Import Localizations

Может появиться окно с предупреждениями, если некоторые строки остались без перевода. Особенно часто это встречается, из-за непереведенного info.plist. Во время импорта Xcode создает на основе таблиц переводов файлы *.strings, если их нет, и вставляет в них ключ, значение и комментарий. Лучше их не редактировать вручную, при неправильном форматировании может перестать работать export / import.


1ae2c555a6ed4aab8f542943b269828c.png

После иморта переводов самое время проверить, как отображается наше приложение на разных языках. Перезапускать его на симуляторе или на девайсе, конечно, нужно, но довольно долго. Куда быстрее это можно сделать в Interface Builder.
Откройте Main.storyboard, включите Assistant Editor и выберите в его выпадающем списке Preview. В этом режиме вы можете просмотреть, как будет выглядеть ваше приложение на разных девайсах и в разных локализациях.


b6d2e78835884d689d8f59db9c0bbee0.png

Меняем Development Language

В очень редких случаях может понадобиться поменять Development Language, например, на Russian, потому что весь ваш дизайн сперва создается с русским текстом.
Вам нужно будет закрыть Xcode, открыть файл проекта в текстовом редакторе .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


Дополнительная информация по локализации приложений:


© Habrahabr.ru