Как локализовать приложение с NSLocalisedString

Большой гайд по локализации. Как перевести текст, фото и значения. Обзор инструментов и автоматизаций.

Поможем в чате для iOS разработчиков

Это большой ультимативный гайд по локализации. Если вы только начинаете изучить локализацию - рекомендуем читать по порядку. Все инструменты в статье редакция выстрадала опытом и временем.

 Пародийный постер к фильму Перевозчик 3
Пародийный постер к фильму Перевозчик 3

Основы

Начнём с основ — добавим язык и переведём слова.

Добавление языка

Чтобы добавить новый язык, перейдите в Настройки проекта -> Info. Потом найдите секцию Localizations, нажмите на кнопку `+` и выберите новый язык.

 Добавление нового языка в настройках проекта
Добавление нового языка в настройках проекта

Локализация строки

Чтобы перевести строку, используем макрос NSLocalizedString. Он принимает 2 параметра — ключ и комментарий, а возвращает уже локализованную строку.

let localisedString = NSLocalizedString(
    "label text", // уникальный ключ, связан со строкой
    comment: "Пример комментария для ключа" // комментарий для переводчика, можно оставить пустым
)
Если строка не локализована, вернётся имя ключа

Теперь добавим перевод. Создайте файл Localizable.strings. Файл можно перевести на языки, которые поддерживает проект. Необязательно переводить на все языки. В инспекторе справа вы увидите, какие языки поддерживает файл. Чтобы перевести строки на новый язык, поставьте рядом с ним галочку.

 Здесь выбираем языки для локализации файла
Здесь выбираем языки для локализации файла

Локализация заполняется в формате "ключ" = "значение";. Перейдите в файл и добавьте строки:

/<i> Пример комментария для ключа </i>/
"label text" = "Localised Text";

Ключ локализован - теперь по ключу label text вернётся локализованное значение Localised Text. Заполните по аналогии другие языки.

Структура папок на диске будет отличаться. Xcode создаст папку с идентификатором локали en.lproj и файлы локализации будет помещать в неё. Так в папке может быть несколько файлов одной локализации.

 Как выглядят файлы локализации в папке
Как выглядят файлы локализации в папке

Передача параметра в строку

Возможность пригодится, если хотите поприветствовать пользователя. Например, написать Привет, Имя! или отобразить время Осталось X минут. В NSLocalizedString можно передавать параметры — строки или числа. Для этого нужны спецификаторы - Xcode заменит их на значения:

  1. %@ - для значений String;
  2. %d - для значений Int;
  3. %f - для значений Float;
  4. %ld - для значений Long;

Весь список спецификаторов находится на сайте Apple Developer.

Перейдём к примеру. Давайте создадим объект String с инициализатором format:

let parametrString = "Parametr Example" // параметр, который будем передавать 

let localisedString = String(
    format: NSLocalizedString(
        "label text", // ключ локализации 
        comment: "" // комментарий
    ), parametrString // переменная
)

Теперь локализуем ключ с параметром. Перейдём в Localizable.strings и добавим:

"label text" = "Localised Text with %@";

Мы использовали спецификатор для параметров с типом String - %@, он заменится значением. Теперь при выводе ключа label text получим label text with Parametr Example.

Порядок параметров

Если в строке находятся два спецификатора одинакового типа, то значения отобразятся в том порядке, в котором мы их передадим. Давайте создадим переменную localisedString, принимающую 3 параметра:

let parametrString = "Make Apple"
let secondParametrString = "great again"
let parametrInt = 941

let localisedString = String(
    format: NSLocalizedString("label text", comment: ""), 
    parametrString, secondParametrString, parametrInt
)

Локализуем ключ в strings-файле:

"label text" = "Lets %1$@ a true %2$@ at %3$d o’clock";
// %1$@ - для первого текстового значения и так далее. 
// %3$d - для первого числового значения.

Нумерацию параметров можно не указывать, тогда строка будет такая:

"label text" = "Lets %@ a true %@ at %d o’clock";

Теперь при выводе переменной localisedString мы получим текст: Lets Make Apple a true great again at 941 o'clock. Если изменить порядок элементов, то изменится их порядок при выводе. Например, если создадим localisedString так:

let parametrString = "Make Apple"
let secondParametrString = "great again"
let parametrInt = 941

let localisedString = String(
    format: NSLocalizedString("label text", comment: ""), 
    secondParametrString, parametrString, parametrInt // меняем parametrString и secondParametrString местами
)

При выводе получим Lets great again a true Make Apple at 941 o'clock

Локализация Info.plist

Info.plist — системный файл проекта, который содержит информацию о бандле, имени приложения, ключах разрешений и т. д. Мы можем локализовать имя приложения и ключи разрешений. Создаём файл InfoPlist.strings и в инспекторе выбираем поддерживаемые языки.

Файл должен называться InfoPlist.strings, иначе локализация не заработает

Чтобы локализовать название приложения, добавим в файл CFBundleName в формате "ключ" = "значение";:

"CFBundleName" = "App name";

Когда добавляете в Info.plist разрешения, например, для использования камеры, объясните, зачем оно нужно приложению. Локализуем это сообщение.

 Пример текста в запросе разрешения
Пример текста в запросе разрешения

Список ключей можно глянуть здесь. Вставляем ключ и локализуем:

/<i> Privacy - Camera Usage Description </i>/
"NSCameraUsageDescription" = "We use the camera to take pictures.";

Экспорт и импорт локализации

Экспорт и импорт локализации автоматизирует действия, добавляет ключи. Экспорт помогает передать файлы переводчику, не передавая весь проект целиком. Переводчик видит имя ключа и комментарии к нему.

Перейдём в Product. Тут мы видим кнопки Export Localizations... и Import Localizations....

 Расположение кнопок экспорта и импорта локализации
Расположение кнопок экспорта и импорта локализации

После экспорта создаются файлы xcloc — в них находится информация для переводчика:

 Сгенерированные xcloc каталога
Сгенерированные xcloc каталога
xcloc расшифровывается как Xcode Localization Catalog

Внутри xcloc находятся 3 папки и файл:

  1. Папка Localized Contents содержит локализуемые ресурсы, включая файл xliff. В нём находятся локализуемые строки.
  2. Папка Notes содержит дополнительную информацию для переводчиков: скриншоты, видео или текстовые файлы.
  3. Папка Source Contents хранит исходные strings-файлы и контекст для переводчиков: файлы интерфейса и другие ресурсы.
  4. Файл contents.json хранит метаданные о каталоге: регион разработки, язык, номер версии Xcode, а также номер версии каталога.
В Xcode 12 экспортировался только xliff. Теперь xliff только часть каталога xloc

Переведём приложение через экспортированный файл. У Xcode есть встроенная IDE для редактирования файла. Откройте xcloc-каталог.

 Встроенная в Xcode IDE для редактирования xcloc
Встроенная в Xcode IDE для редактирования xcloc

На сайдбаре увидите 2 файла — InfoPlist и Localizable. В первой колонке ключ, во второй переводим его, а в третьей находится комментарий. Сохраните файл после перевода. Чтобы импортировать локализацию, перейдите в Product -> Import Localizations.

 Импортирование xcloc каталога в проект
Импортирование xcloc каталога в проект

Здесь выбираем каталог и загружаем в проект. В файле Localizable.strings импортированного языка появятся переведённые ключи:

/<i> No comment provided by engineer. </i>/
"key a" = "Буква А";

/<i> No comment provided by engineer. </i>/
"key b" = "Буква Б";

/<i> No comment provided by engineer. </i>/
"key c" = "Буква С";

/<i> No comment provided by engineer. </i>/
"key d" = "Буква Д";

/<i> No comment provided by engineer. </i>/
"key e" = "Буква Е";

Встроенный переводчик удобно использовать для быстрой правки локализации.

Poedit

Это альтернативная IDE для редактирования xсloc-каталогов. Она покажет ошибки в переводе, отсутствующие строки и автоматически переведёт ключи на другой язык.

Poedit умеет читать только xliff-файлы, поэтому открываем xcloc-каталог правой кнопкой и переходим в содержимое пакета.

 Содержимое xcloc каталога
Содержимое xcloc каталога

Итак, нас интересует папка Localized Contents. Внутри будет xliff файл, откройте его через Poedit.

 Интерфейс Poedit
Интерфейс Poedit

Здесь содержатся все ключи списком. Выберите нужный. После этого внизу появится исходный ключ и поле для ввода перевода. Справа есть варианты перевода, ключ и комментарий. После перевода сохраните файл и импортируйте xсloc в проект.

Можно импортировать не только xсloc целиком, но и отдельно xliff-файлы

BartyCrouch

Это консольный инструмент и встраиваемый плагин. Он автоматизирует локализацию и генерацию ключей, обновляет strings-файлы, удаляет неиспользуемые ключи и сортирует ключи по алфавиту.

Установка

Как установить BartyCrouch:

  1. Откройте терминал и установите [Homebrew](https://brew.sh):

```

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

<ol class="ol"><li>Введите в терминал <mark>brew install bartycrouch</mark></li><li>Создайте дефолтный конфиг, для этого вставьте в терминал:</li></ol>

```

bartycrouch init

В папке появится скрытый файл .bartycrouch.toml.

 Стандартный файл-конфигуратор Bartycrouch
Стандартный файл-конфигуратор Bartycrouch

Это стандартная конфигурация.

Настройка

Прописываем paths и codePaths чтобы файлы локализации нашлись быстрее:

// Указывайте путь к файлам в вашем проекте, например:
paths = <a class="a" href="https://github.com/FlineDev/BartyCrouch#configuration" target="_blank">"App/Localisations/"] // <mark>strings</mark>-файл
codePaths = ["App/Data/"] // Файл с <mark>enum</mark>, если используете <mark>supportedLanguageEnumPaths</mark>

Что пригодится для задачи interfaces:

  1. subpathsToIgnore = ["."] — пути к файлам, которые нужно игнорировать.
  2. defaultToBase = true — добавляет значение от стандартного языка к новым не локализованным ключам.
  3. ignoreEmptyStrings = true — запрещает создание view для пустых строк.
  4. unstripped = true — сохраняет пробелы в начале и конце strings-файлов.

Что пригодится для задачи normalize:

  1. separateWithEmptyLine = false — создаёт пробелы между строками.
  2. sourceLocale = "." — переопределяет основной язык.
  3. harmonizeWithSource = true — синхронизирует ключи с остальными языками.
  4. sortByKeys = true — сортирует ключи по алфавиту.

Опций больше, весь список смотрите [в документации.

После настройки конфига запустите проверку:

bartycrouch update

BartyCrouch проверит ключи, добавит их в strings-файлы и избавится от ненужных строк. Команды, которые будут выполнятся через update, можно настроить. Например:

[update]
tasks = ["interfaces", "normalize", "code"]

Теперь при вызове отработают только 3 задачи. Ещё есть lint-задача, которая по умолчанию делает поверхностную проверку. Она ищет повторяющиеся ключи и пустые строки.

Встроить в Xcode

Чтобы не вызывать Bartycrouch вручную, можно встроить его в Xcode — проверка будет запускаться при каждом билде. Переходим в таргет проекта -> Build Phase, нажимаем на плюсик и создаём новый скрипт:

 Добавление скрипта Bartycrouch в проект
Добавление скрипта Bartycrouch в проект

Вставляем код:

if which bartycrouch > /dev/null; then
    bartycrouch update -x
    bartycrouch lint -x
else
    echo "warning: BartyCrouch not installed, download it from https://github.com/FlineDev/BartyCrouch"
fi

Теперь Bartycrouch делает проверку автоматически.

Плюрализация

Пригодится, если захотим локализовать количество, например:

  1. У Тима нет наушников;
  2. У Тима 1 наушник;
  3. У Тима 2 наушника;
  4. У Тима 7 наушников;

Создаём функцию:

func headphonesCount(count: Int) -> String {
    let formatString: String = NSLocalizedString("headphones count", comment: "Don't localise, will localise in stringsdict") 
    let resultString: String = String.localizedStringWithFormat(formatString, count) // передаем count
    return resultString // возвращаем нужный текст
}

Создаём новый файл. В поиске пишем strings и выбираем Stringsdict File. Называем Localizable и добавляем в проект.

 Добавление Stringsdict файла
Добавление Stringsdict файла

Переходим в файл и видим структуру:

 Структура файла Stringsdict
Структура файла Stringsdict
  1. Localised String Key — ключ локализации - headphones count.
  2. Localised Format Key — параметр, значение которого войдёт в строку результата. В нашем случае только один: count.
  3. NSStringFormatSpecTypeKey — указывает единственный возможный тип перевода NSStringPluralRuleType. Он значит то, что в переводе встречается множество имён существительных (то, что мы хотим локализовать). Его не трогаем.
  4. NSStringFormatValueTypeKey — строковый спецификатор формата числа. Например, `d` для целых чисел. Полный список тут.
  5. zero, one, two, few, many, other — различные формы множественного числа для языков. Обязательное other — оно будет использовано, если переданное число не удовлетворит ни одному из перечисленных условий. Остальные можно убрать, если они не используются.

Заполняем файл:

 Заполненный ключ headphones count
Заполненный ключ headphones count

Видим, что two, few, many и other повторяются. Обязательно только other, поэтому остальные убираем.

 Отрефракторенный ключ headphones count
Отрефракторенный ключ headphones count

Файл заполнен, но при вызове функции headphonesCount(count: 1) вместо перевода мы получим ключ headphones count.

Xcode не локализует stringsdict автоматически

Чтобы локализовать stringsdict, перейдём в инспектор -> кнопка Localize

 Расположение кнопки Localize в инспекторе
Расположение кнопки Localize в инспекторе

Затем выбираем языки, для которых нужно создать stringsdict-файлы.

 Выбор языков для перевода в инспекторе
Выбор языков для перевода в инспекторе

Локализовать .stringsdict можно прямо в созданном файле. Выбираем Localizable (Russian) в левом меню.

 stringsdict-файлы на сайдбаре
stringsdict-файлы на сайдбаре

Заполняем строки, добавляем few для корректного перевода числа на русском.

 Локализованный ключ headphones count
Локализованный ключ headphones count

Что получим:

headphonesCount(count: 0) // У Тима нет наушников
headphonesCount(count: 1) // У Тима 1 наушник
headphonesCount(count: 2) // У Тима 2 наушника
headphonesCount(count: 7) // У Тима 7 наушников

Если нужно локализовать другое слово, создайте новое значение в stringsdict-файле. Например, посчитаем яблоки. Для этого создаём функцию с новым ключом:

func applesCount(count: Int) -> String {
    let formatString: String = NSLocalizedString("apples count", comment: "")
    let resultString: String = String.localizedStringWithFormat(formatString, count)
    return resultString
}

Переходим в stringsdict, создаём новое значение apples count. Настраиваем так же, как в прошлых шагах.

 Новый заполненный ключ apples count
Новый заполненный ключ apples count

Новое значение всё ещё можно локализовать прямо в файле, но в этот раз для перевода используем другой способ и экспортируем локализацию через Product -> Export Localizations.... Открываем нужный xcloc-каталог:

 Локализация stringsdict-файла в переводчике Xcode
Локализация stringsdict-файла в переводчике Xcode

Переводим и импортируем в проект через Product -> Import Localizations.... В stringsdict-файле русского языка осталось лишнее значение many. Удаляем его.

 Отрефракторенный ключ apples count
Отрефракторенный ключ apples count

Проверяем:

applesCount(count: 0) // У Тима нет яблок
applesCount(count: 1) // У Тима 1 яблоко
applesCount(count: 7) // У Тима 7 яблок
applesCount(count: 131) // У Тима 131 яблоко
applesCount(count: 152) // У Тима 152 яблока

Локализация SPM-пакетов

Чтобы локализовать SPM-пакет, создадим папку внутри пакета с идентификатором языка. Например, en.lproj. У каждого языка есть свой идентификатор, весь список можно глянуть по ссылке. В папке создаём файл Localizable.strings.

Повторяем процедуру для каждого нужного языка.

 Структура локализуемого пакета
Структура локализуемого пакета

В файле Package выставляем defaultLocalization — этот язык будет использоваться по умолчанию. Указываем папку с файлами локализации в resources.

 Структура файла локализуемого пакета
Структура файла локализуемого пакета

В файле Localizable.strings каждого языка должны храниться пары ключ-значение NSLocalizedString, которые мы используем в пакете. Например:

NSLocalizedString("first key", bundle: .module, comment: "")

Указываем bundle: .module в инициализаторе NSLocalizedString. Так мы указываем, что строку нужно искать в пакете. А в Localizable.strings локализуем как обычно:

/<i> No comment provided by engineer. </i>/
"first key" = "First key";

Экспорт и импорт

 Экспорт локализации пакета
Экспорт локализации пакета

Чтобы экспортировать пакет, перейдите в Product -> Export Localisations и выберите пакет. Выше мы как раз рассмотрели способы локализации экспортированных файлов.

При экспорте главного таргета экспортируются и локальные SPM-пакеты
Xcode ниже 14 версии не экспортирует и не импортирует ключи в локальных SPM-пакетах

Локализация специальных данных

Она пригодится, если захотите локализовать данные в правильном формате в зависимости от выбранного языка. Например, сумму 3 000,00 ₽, дату 24 апр. 2022 г. или процент 54 %.

 Пример локализации процента на разные языки с помощью форматтера
Пример локализации процента на разные языки с помощью форматтера

В английском языке процент пишется слитно с числом 61%, а в немецком - раздельно 61 %. Что бы значения автоматически изменялись правильно - существуют форматтеры, подробнее о них в нашей статье по форматтерам.

Идентификаторы языка

Чтобы получить идентификатор локали, вызовите Locale.current.identifier. Вернётся значение языкприложения_ЯЗЫКРЕГИОНА, например, en_US. Полный список таких идентификаторов найдёте по ссылке

Apple используют ISO стандартизацию, поэтому если на устройстве язык, который не соответствует региону, вернутся разные значения. Например, для en_RU вместо вернётся RUB

Локализация изображений

Представим, что нам нужно показывать флаг страны по локализации приложения. Переходим в Assets -> Добавляем стандартное изображение. Потом переходим в инспектор -> Localize...

 Расположение кнопки Localize... в Assets каталоге Xcode
Расположение кнопки Localize... в Assets каталоге Xcode

Выбираем языки, на которые хотим локализовать изображение. Добавляем нужные изображения в появившихся полях.

 Assets после настройки
Assets после настройки

Проверяем, как отображается изображение на разных языках.

 Превью локализованного изображения
Превью локализованного изображения

Языки справа-налево RTL

В английском, русском, немецком и других языках текст пишется слева-направо. Но есть языки с направлением справа-налево, например иврит, персидский и арабский.

Направление текста слева-направо называется Left-to-Right, сокр. LTR. Направление справа-налево называется RTL
 Пример LTR и RTL интерфейса
Пример LTR и RTL интерфейса

Направление текста и интерфейса можно определить для приложения и для каждой вью отдельно. Направление приложения определяется в объекте UIApplication:

if UIApplication.shared.userInterfaceLayoutDirection == .leftToRight {
    // Код для LTR направления
} else {
    // Код для RTL направления
}

Иногда направление конкретной вью не соответствует направлению приложения. Чтобы получить направление для конкретной вью, используйте effectiveUserInterfaceLayoutDirection:

if view.effectiveUserInterfaceLayoutDirection == .leftToRight {
    // Код для LTR
} else {
    // Код для RTL
}

Картинки

Картинки тоже подчинаются направлению интерфейса. Символы из SFSymbols отзеркаливаются из коробки.

 Направление SFSymbols в LTR и RTL
Направление SFSymbols в LTR и RTL

Кастомные иконки нужно отзеркаливать вручную, используйте метод imageFlippedForRightToLeftLayoutDirection():

// Оригинальная иконка
let image = UIImage.init(named: "icon")

// Отзеркаленная
let flippedImage = image?.imageFlippedForRightToLeftLayoutDirection()
Логотипы, фотографии и иллюстрации отзеркаливать не нужно. Иконки, которые повторяют реальные объекты, тоже не нужно отзеркаливать
 Эти изображения не нужно отзеркаливать
Эти изображения не нужно отзеркаливать

Текст

Для лейблов выставляем textAlignment = .natural. Так текст будет выравниваться согласно языку приложения: .left для LTR, .right для RTL.

Есть исключения. Например, ваш арабский текст включает абзац английского текста в одну или две строки. Тогда можно оставить направление справа-налево для обоих языков. Но если абзац больше 3 строк, то выравнивание должно соответствовать языку.

 Выравнивание текста на разных языках
Выравнивание текста на разных языках
Списки на разных языках выравниваются по направлению интерфейса приложения.
 Выравнивание списка на разных языках
Выравнивание списка на разных языках

Фреймы

Создаем квадрат размером 100 на 100. Позицию `x` будем менять согласно направлению интерфейса:

let space = 30
squareView.frame = .init(x: 0, y: 0, width: 100, height: 100);

if squareView.effectiveUserInterfaceLayoutDirection == .leftToRight {
    squareView.frame.origin.x = space
} else {
    squareView.frame.origin.x = view.frame.width - space - squareView.frame.width
}

Теперь, если лейаут LTR - квадрат будет стоять в 30 поинтах от левого края. Если RTL - в 30 поинтах от правого:

 Лейаут квадрата на фреймах в LTR и RTL
Лейаут квадрата на фреймах в LTR и RTL

Auto Layout

leftAnchor и rightAnchor это всегда левый и правый край соответственно. Даже в RTL это не меняется. Но если использовать leadingAnchor и trailingAnchor, то края поменяются местами для направления справа-налево. Для LTR это будет левый и правый край, для RTL наоборот - правый и левый.

Создаем квадрат размером 100 на 100. Указываем leadingAnchor и констрейнты:

squareView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200).isActive = true
squareView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
squareView.widthAnchor.constraint(equalToConstant: 100).isActive = true
squareView.heightAnchor.constraint(equalToConstant: 100).isActive = true

Теперь в LTR направлении квадрат будет стоять в 30 поинтах от левого края, а в RTL - от правого. trailingAnchor работает так же, только для правого края.

 Авто лейаут квадрата в LTR и RTL
Авто лейаут квадрата в LTR и RTL

Рекомендации

Теперь поделюсь советами по работе с локализацией, чтобы вы могли сэкономить время и избежать переиспользования кода.

Отдельный файл для ключей

Создаём файл, внутри делаем enum Texts. В нём создаём статические переменные, которые вернут NSLocalizedString. Его можно структурировать, создавая дочерние enum внутри других enum:

enum Texts {
    
    enum FirstController {
        
        static var title: String { NSLocalizedString("first controller title", comment: "") }
        static var subtitle: String { NSLocalizedString("first controller subtitle", comment: "") }
        static var action_button: String { NSLocalizedString("first controller action button", comment: "") }
        static var cancel_button: String { NSLocalizedString("first controller cancel button", comment: "") }
    }
    
    enum SecondController {
        
        static var title: String { NSLocalizedString("second controller title", comment: "") }
        static var subtitle: String { NSLocalizedString("second controller subtitle", comment: "") }
        static var action_button: String { NSLocalizedString("second controller action button", comment: "") }
        static var cancel_button: String { NSLocalizedString("second controller cancel button", comment: "") }
    }
}

В проекте получение строки будет выглядеть так:

titleLabel.text = Texts.FirstController.title

Если переменных много, можно создать несколько файлов и разгруппировать ключи.

Часто используемые слова

Функциональные слова, такие как ОК, Отменить, Удалить можно вынести в отдельный enum Shared и использовать по всему приложению, чтобы не дублировать локализации:

enum Shared {
        
    static var ok: String { NSLocalizedString("shared ok", comment: "") }
    static var cancel: String { NSLocalizedString("shared cancel", comment: "") }
    static var delete: String { NSLocalizedString("shared delete", comment: "") }    
}

Shared можно вынести в отдельный пакет, чтобы использовать для разных таргетов проекта.

Передача параметров в ключ

Чтобы красиво передать параметры в NSLocalizedString, создайте функцию:

static func fruitName(name: String) -> String {
    return String(format: NSLocalizedString("fruit name %@", comment: ""), name)
}

Вызываем в коде:

fruitNameLabel.text = Texts.fruitName(name: "Apple")

Как называть ключи

NSLocalizedString принимает 2 параметра, которые будут видны при локализации — ключ и комментарий. Можно создать непонятный ключ и подробно описать в комментарии, зачем он нужен. Но лучше делать понятные имена. Например, секции в футере с фидбеком на экране настроек:

NSLocalizedString("settings controller table feedback section footer", comment: "")
Рекомендуем не заполнять пустые пространства нижним подчеркиванием `_`. Даже в маленьких проектах ключи становятся большими - Xcode криво переносит длинные строки. Сохраняйте пробелы

Полезные инструменты

Особенности

  1. Интерфейс должен быть динамическим. Заранее рассчитать ширину и высоту лейбла под текст не получится, потому что одни и те же слова занимают разное место в зависимости от языка. Обычное «Как ты?» переводится на французский как «Comment allez-vous?».
  2. В английском языке функциональные слова пишутся с большой буквы. Так, кнопка «Add new» пишется с двух заглавных «Add New». В русском языке такое встречается, но не часто.
Поправить или дополнить статью через Pull Request