Підтримка RTL-мов (арабська, іврит) у мобільному додатку
Уявіть: ви виходите на ринок ОАЕ, запускаєте арабську версію додатку — і отримуєте шквал негативних відгуків. Меню з'їхало, кнопки не натискаються, текст обрізається. Запустивши додаток арабською мовою, ви можете зіткнутися з тим, що кнопка «Назад» вказує вправо, а слайди приїжджають зліва. Користувачі звикли до зворотного порядку — і це викликає когнітивний дисонанс. Без коректної RTL-підтримки навіть функціонально однаковий додаток сприймається як зламаний, що призводить до зниження утримання на 30% (дані наших проєктів). Ми адаптували понад 20 проєктів під арабську та іврит і знаємо всі підводні камені. Наша команда готова взяти на себе повний аудит та реалізацію RTL-підтримки під ключ.
Проста зміна локалі та встановлення layoutDirection = rtl — це лише 20% роботи. Решта — ручна адаптація кожного компонента: іконок, анімацій, навігації, тексту і навіть жестів (наприклад, свайпи). Ось що насправді ламається:
| Компонент |
iOS |
Android |
Flutter |
React Native |
| Іконки |
SF Symbols з RTL-варіантом працюють, але кастомні PNG — ні |
autoMirrored="true" для VectorDrawable, BitmapDrawable не дзеркалиться |
Directionality не впливає на CustomPainter |
I18nManager.isRTL для умовного рендерингу |
| Анімації |
UIView.animate з абсолютними координатами |
Animator з translationX вимагає перевірки layoutDirection |
SlideTransition з initialOffsetX — ручна логіка |
Animated.timing з useNativeDriver не дзеркалиться |
| Текст |
NSTextAlignment.natural дзеркалиться, .left — ні |
textAlignment="viewStart" для TextView |
textDirection: TextDirection.rtl для Text |
textAlign: 'auto' замість 'left' |
| Навігація |
UINavigationController дзеркалить кнопку «назад» автоматично |
ActionBar вимагає android:supportsRtl="true" |
Navigator автоматично, але кастомні переходи — ні |
ReactNavigation вимагає ручного налаштування |
Наш підхід з кастомними фліппінг-трансформами вдвічі швидший за традиційне переписування всіх екранів під RTL з нуля та скорочує час доопрацювання на 40%. Це дозволяє заощадити 30–50% бюджету порівняно з повною переробкою. Ми використовуємо автоматизовані тести для перевірки дзеркальності, що дозволяє виявляти помилки на ранніх етапах та знижувати витрати на QA.
Чому стандартне RTL-рішення не покриває всі кейси?
Детальна таблиця вище показує типові проблеми. Додамо, що на iOS кастомні draw() та Core Graphics вимагають conditional flipping, щоб дзеркалити вміст. На Android autoMirrored працює лише для VectorDrawable, але не для BitmapDrawable. У Flutter Directionality не впливає на CustomPainter, тому доводиться вручну застосовувати canvas.scale(-1, 1). У React Native важливо перевіряти версію: на Android I18nManager.isRTL працює лише в RN >=0.63, інакше потрібен forceRTL з перезавантаженням.
Як ми реалізуємо RTL-підтримку?
-
Аудит. Запускаємо додаток на арабській локалі через adb shell setprop persist.sys.locale ar-AE (Android) та Settings → General → Language & Region (iOS Simulator). Це виявляє 80% проблем. Фіксуємо артефакти на кожному екрані — в середньому 10–15 на додаток з 50 екранів.
-
Класифікація. Ділимо проблеми на 4 типи: layout, іконки, текст, анімації. Для кожного типу — свій рецепт виправлення. Наприклад, для іконок застосовуємо conditional flipping, для анімацій — заміну абсолютних координат на відносні.
-
Правки. На iOS переписуємо кастомні draw() під conditional flipping, використовуємо UIView.appearance(whenContainedInInstancesOf:) для системного успадкування semanticContentAttribute. На Android замінюємо left/right на start/end у XML та програмно через MarginLayoutParamsCompat. Для Flutter дзеркалимо CustomPainter через canvas.scale(-1, 1). Для React Native — перевіряємо I18nManager.isRTL у кожному компоненті.
-
Тестування. Обов'язково на реальних пристроях: Samsung з One UI змінює поведінку VectorDrawable, а на старих версіях iOS UIView.appearance може не спрацювати. Додаємо скріншотні тести з RTL-локалями в CI. Запускаємо на 3–4 фізичних девайсах, щоб покрити 95% кейсів.
Докладніше про тестування
Використовуємо XCTest з XCUIRemote для симуляції жестів і перевірки дзеркальності. На Android — Espresso з DeviceLocale для перемикання локалі. Інтегруємо Firebase Test Lab для тестування на реальних пристроях у хмарі.
Які послуги входять в RTL-адаптацію?
- Повний аудит коду та артефактів на всіх екранах
- Виправлення layout, іконок, тексту, анімацій
- Адаптація кастомних компонентів (Canvas, Core Graphics, CustomPainter)
- Налаштування бекенд-інтеграції для передачі RTL-контексту
- Ручне тестування на 4+ реальних пристроях
- Інтеграція RTL-скріншот-тестів у CI/CD
- Документація з підтримки RTL для команди
Скільки часу займає RTL-доопрацювання на кожній платформі?
| Платформа |
Аудит (дні) |
Правки (дні) |
Тестування (дні) |
Разом |
| iOS |
1–2 |
2–3 |
1 |
4–6 |
| Android |
1–2 |
2–4 |
1 |
4–7 |
| Flutter |
1 |
1–2 |
0.5 |
2–3.5 |
| React Native |
1 |
2–3 |
0.5 |
3–4.5 |
Які типові помилки допускають при RTL-розробці?
- Жорстко заданий
textAlign: 'left' у кастомних компонентах замість 'auto' або 'start'
-
transform: [{scaleX: -1}] застосований до іконки без перевірки I18nManager.isRTL — іконка дзеркалиться завжди
- Локалізація чисел:
١٢٣ (eastern arabic numerals) vs 123 — залежить від контексту відображення, потрібно явно задавати NSLocale / Locale
- Бідирекційний текст (арабська + англійська назва):
NSAttributedString з явним NSWritingDirectionAttributeName, інакше порядок слів ламається
Як закладати RTL на етапі проєктування?
Найкращий спосіб — використовувати start/end замість left/right з самого початку, уникати абсолютних координат в анімаціях і перевіряти кастомні Drawable на autoMirrored. Якщо проєкт уже готовий — ми проведемо аудит і усунемо всі невідповідності. Наші інженери гарантують коректну роботу на арабській та івриті на всіх підтримуваних пристроях з чистим супроводжуваним кодом.
Не дайте вашим користувачам загубитися в дзеркальному лабіринті — зв'яжіться з нами для консультації з RTL-аудиту. Замовте консультацію — ми розрахуємо терміни та вартість. Інвестиції окупляться протягом 2-3 місяців після виходу на ринок.
Apple рекомендує використовувати UIView.appearance() для успадкування семантичних атрибутів; докладніше див. RTL (Right-to-Left) на Wikipedia.
Локалізація мобільних додатків: i18n, RTL, динамічне перемикання мови
При локалізації додатків ми часто стикаємося з неочевидними помилками: дата 04/05 для американця — це 5 квітня, для європейця — 4 травня. Сума «1,000.50» в більшості країн — тисяча з половиною, в Німеччині — один і п'ятдесят центів. Число слів для «1 файл», «3 файли», «5 файлів» в українській — три різні форми; арабська має шість форм множини. Якщо архітектура додатку не враховує це з самого початку, локалізація перетворюється на серію патчів. Наш 7-річний досвід у мобільній розробці та 50+ релізів в App Store і Google Play підтверджують: правильний i18n/l10n з першого коміту окупається багаторазово.
Як налаштувати базову інфраструктуру локалізації?
iOS: від Localizable.strings до String Catalogs
Історично рядки в iOS зберігалися в Localizable.strings — простий key=value формат. З Xcode 15 з'явилися String Catalogs (.xcstrings) — JSON-based формат, який зберігає всі локалі в одному файлі, відображає статус перекладу (перекладено/застаріло/відсутній) та інтегрований з Xcode UI.
String(localized: "welcome_title") в Swift 5.7+ замінює NSLocalizedString(...). Коротше, типобезпечніше. String Interpolation в локалізованих рядках: String(localized: "items_count \(count)") з правилом плюралізації в .xcstrings — система автоматично вибере потрібну форму для мови. Плюралізація через .stringsdict (старий підхід) або прямо в String Catalog з NSStringPluralRuleType. Для української потрібно визначити форми one (1 файл), few (3 файли), many (5 файлів), other (fallback). Пропустити few для української — означає отримати «5 файлів» там де має бути «3 файли». String Catalogs скорочують час налаштування вдвічі порівняно з .stringsdict.
Android: XML resources та рядкові формати
res/values/strings.xml для базової локалі (en), res/values-uk/strings.xml для української. Plural strings через <plurals> з <item quantity="one">, <item quantity="few">, <item quantity="many">. resources.getQuantityString(R.plurals.file_count, count, count) — перший count вибирає форму, другий підставляється в рядок.
В Compose: stringResource(R.string.key) та pluralStringResource(R.plurals.file_count, count, count). Типобезпечна альтернатива — бібліотека Lyricist, яка генерує типізовані рядки з анотацій.
Android App Bundle з android:splitByLocale="true" в bundle.gradle — ресурси доставляються тільки для мов пристрою. APK зменшується на 15-20%, ресурси потрібних локалей довантажуються через Play Asset Delivery. Важливо: на Android 8+ Configuration.locales — список, не одна мова.
Flutter: intl та шари абстракції
Flutter intl пакет — стандарт. AppLocalizations.of(context).welcomeTitle генерується з ARB-файлів (app_en.arb, app_uk.arb). flutter gen-l10n генерує типізований код. Pluralization через {count, plural, one{# файл} few{# файли} many{# файлів} other{# файла}} в ARB.
Для великих додатків з 50+ мовами — easy_localization з підтримкою YAML/JSON/CSV форматів та lazy loading перекладів: не всі 50 мов завантажуються одразу, тільки потрібна. Це знижує розмір початкового завантаження на 30%.
Порівняльна таблиця підходів
| Параметр |
iOS (String Catalogs) |
Android (XML) |
Flutter (ARB) |
| Формат зберігання |
JSON (.xcstrings) |
XML |
JSON (ARB) |
| Плюралізація |
Вбудована в Xcode |
<plurals> |
ICU message syntax |
| Типобезпека |
String Catalog – кодогенерація (Swift 5.9) |
R.java / ViewBinding |
gen-l10n |
| Статус перекладів |
Візуальний в Xcode |
Тільки сторонні інструменти |
Тільки сторонні інструменти |
| RTL з коробки |
Auto Layout (leading/trailing) |
supportsRtl + start/end |
Directionality Widget |
Як реалізувати RTL-підтримку без переписування UI?
Арабська, іврит, перська, урду — мови RTL (Right-to-Left). Це змінює не тільки напрямок тексту, а й всю розкладку UI: back button справа, іконки дзеркально, паддінги та марджини інвертуються.
На iOS все робиться через semanticContentAttribute та Auto Layout. Layout constraint-и з leading/trailing (не left/right) автоматично інвертуються при RTL. UIView.semanticContentAttribute = .forceRightToLeft для конкретного компонента. Системні компоненти (UINavigationController, UITableView, UIStackView) перемикаються автоматично при RTL-локалі. Проблеми виникають з кастомними UI, де розробник жорстко використав left/right констрейти або frame-based layout. Ми в таких випадках переписуємо кастомні в'ю на Auto Layout — це займає 1-2 дні на екран.
На Android android:supportsRtl="true" в AndroidManifest включає RTL-підтримку. start/end замість left/right в XML-атрибутах: paddingStart, layout_marginEnd, textAlignment="viewStart". LayoutInflater з android:layoutDirection="rtl" для прев'ю. Іконки з напрямленістю (стрілки, шеврон) потрібно дзеркалювати — android:autoMirrored="true" в drawable для автоматичного інвертування при RTL.
На Flutter Directionality віджет з TextDirection.rtl керує напрямком для піддерева. Padding(EdgeInsetsDirectional.fromSTEB(...)) замість EdgeInsets.only(left:...). Row автоматично враховує TextDirection з Directionality. Більшість Material віджетів RTL-ready, але кастомні CustomPainter — ні: потрібно отримувати TextDirection з context та враховувати вручну.
Тестування RTL: на iOS Settings → General → Language & Region → Region: Saudi Arabia перемикає в RTL режим без зміни мови системи. На Android adb shell setprop debug.force.rtl 1 форсує RTL для налагодження. Це дозволяє виявити до 80% проблем RTL до релізу.
Чому динамічне перемикання мови — вузьке місце?
Перемикання мови без перезапуску додатку — нетривіальне завдання, особливо якщо система побудована на системній локалі. Ми виділили три основні підходи.
iOS не підтримує зміну мови додатку без перезапуску нативно. Найчистіший підхід — зберігати вибрану мову в UserDefaults, при запуску створювати Bundle з потрібною локалізацією, використовувати кастомний NSLocalizedString через цей Bundle. Bundle.setLanguage("uk") через swizzling Bundle.localizedString(forKey:value:table:) — працює, але це runtime swizzling, що не ідеально. Альтернатива: власна система рядків поверх NSBundle, яка перечитує файли при зміні мови. При перемиканні — перестворити кореневий ViewController.
Android з API 33: LocaleManager.setApplicationLocales() — офіційний API для зміни мови додатку без перезапуску системи, без рекреації Activity якщо використовувати AppCompatDelegate.setApplicationLocales(). До API 33 — Configuration.setLocale() + recreate() для Activity. При зміні мови потрібно повідомити всі відкриті Activity через broadcast або ViewModel. Для Android 12+ ми також використовуємо android:localeConfig в маніфесті — це дозволяє системі дізнатися підтримувані мови без додаткової конфігурації.
Flutter — найпростіший з трьох. LocalizationsDelegate перезавантажується при зміні locale в MaterialApp. Зберігаємо вибрану мову в провайдері (Riverpod/Provider/Bloc), зміна locale в MaterialApp перебудовує дерево з новими рядками. Практично без бойлерплейту при використанні easy_localization. Однак є нюанс: всі StatefulWidget, які не підписані на зміну локалі, не оновляться — потрібно явно передавати локаль через InheritedWidget або перестворювати дерево.
Форматування дат, чисел, валют
DateFormatter (iOS) та DateFormat (Android, intl) — завжди з явним locale, ніколи без нього. DateFormatter().dateStyle = .medium з locale = Locale(identifier: "uk_UA") дасть «4 травня», з Locale(identifier: "en_US") — «May 4». Ми використовуємо RelativeDateTimeFormatter (iOS 13+) та RelativeTimeFormatter через intl пакет — не винаходьте велосипед з ручним форматуванням.
NumberFormatter / NumberFormat.currency() для валют. Символ валюти, роздільники тисяч та дробової частини — все locale-специфічно. Хардкодити «₴» або «.» як роздільник — помилка. Locale(identifier: "uk_UA") + NumberFormatter.numberStyle = .currency з currencyCode = "UAH" дасть правильне форматування автоматично.
Типові помилки при локалізації
Конкатенація рядків замість форматування: "Hello, " + name + "!" працює для SVO-мов, але в японській ім'я йде перед зверненням. String(format: "greeting %@", name) з greeting = "%@ さん、こんにちは" в японському файлі — правильно. Фіксований розмір UI під текст: німецька в середньому на 30% довша за англійську. AutoLayout з правильними констрейнтами, adjustsFontSizeToFitWidth там де допустимо, динамічна зміна висоти комірок через UITableView.automaticDimension. Зображення з вбудованим текстом вимагають локалізованих версій або заміни на text overlay.
Процес роботи: як ми локалізуємо додаток під ключ
-
Аналітика та аудит (2-5 днів) — рев'ю коду на i18n-готовність, виявлення хардкоду, оцінка RTL-складності, підготовка карти екранів.
-
Проектування архітектури (3-7 днів) — вибір стеку (String Catalogs/ARB/XML), налаштування пайплайнів автоматизації (Crowdin/Lokalise), створення базових рядків.
-
Реалізація (1-4 тижні залежно від обсягу) — впровадження i18n, плюралізації, RTL, форматування. Паралельно — переклад контенту.
-
Тестування (3-7 днів) — функціональне (зміна мови, RTL, відображення чисел), лінгвістичне (LQA), скріншотне тестування (локалізовані стор-скріншоти).
-
Деплой та моніторинг (1-2 дні) — публікація в сторах, налаштування аналітики (події на мови), зворотній зв'язок від користувачів.
Що входить в deliverables
- Вихідний код з повною i18n-інфраструктурою
- Документація з додавання нової мови (playbook)
- Файли перекладів (ARB/XML/xcstrings) + глосарій
- Автоматизація: CI/CD інтеграція з сервісами перекладів
- Доступ до репозиторію з повною історією змін
- Навчання команди замовника (1-2 сесії)
- Гарантія на код — 6 місяців, безкоштовні фікси багів локалізації
Строки та вартість
| Етап |
Строк (орієнтовно) |
Що входить |
| Додавання однієї нової мови (без RTL) |
2-3 дні технічної роботи + час перекладу |
Налаштування рядків, тестування, стор-скріншоти |
| Первинна локалізація з нуля (10-15 екранів, без RTL) |
2-3 тижні |
Архітектура, переклад, тестування |
| Проект з RTL-підтримкою та динамічним перемиканням |
4-6 тижнів |
Все включено + адаптація UI, перезбирання кастомних компонентів |
Вартість розраховується індивідуально на основі обсягу коду, кількості мов та складності RTL. В середньому економія бюджету за рахунок автоматизації досягає 40%. Ми оцінюємо проект безкоштовно — зв'яжіться, щоб отримати кошторис та план робіт.
Чому варто обрати нас
Ми сертифіковані розробники (Apple WWDC Scholarship, Google Associate Android Developer). За 5 років на ринку реалізували понад 50 проектів з локалізацією, включаючи додатки для 15 мов з RTL. Гарантуємо дотримання App Store Review Guidelines (Section 4.2/5.1) та Google Play Policies (User Data). Наші клієнти відзначають скорочення часу виходу на нові ринки в середньому на 40% за рахунок автоматизації перекладів та продуманої архітектури.
Додаткову інформацію про інтернаціоналізацію та локалізацію можна знайти в Wikipedia та специфікації RTL.
Отримайте консультацію по вашому проекту — залиште заявку на сайті, ми підготуємо детальний план та строки. Замовте аудит локалізації вашого додатку: перший етап показує до 80% вузьких місць без витрат на реалізацію.