Реалізація поддержки RTL-мов (арабська, іврит) у мобільній програмі
Додати арабську або іврит до вже работающей програми — це не «поменяти locale та встановити layoutDirection = rtl». Типова картина після такої спроби: іконки «назад» дивляться вправо, але стрілка прогрес-бара так і йде зліва направо; текст в TextField вирівняний правильно, але placeholder по-прежнему ліворуч; анімації slide-in «приїжджають» з неправильної сторони, а користувацький Canvas малює графіки дзеркально лише в режимі відладки на емуляторі — на реальному пристрої ні.
Де реально ломається RTL
iOS — Auto Layout та SwiftUI
Поведінка визначається semanticContentAttribute у UIView. За умовчанням .unspecified — система сама вирішує по locale. Проблема починається з користувацьких view: UIView.draw(_:), CALayer, Core Graphics — все це ігнорує semanticContentAttribute та малює в абсолютних координатах. Потрібно явно застосовувати CGAffineTransform(scaleX: -1, y: 1) або переписувати логіку під userInterfaceLayoutDirection.
У SwiftUI HStack та padding(.leading) автоматично дзеркаляться при layoutDirection == .rightToLeft. Але alignment: .leading у тексті при RTL — це вже правий край. Якщо ви явно вказуєте .trailing, все переворачивается. Користувацькі Shape через Path малюються в абсолютних координатах — дзеркалити доведеться вручну через .environment(\.layoutDirection, .rightToLeft) у сполученні з transform.
Іконки з SF Symbols: частина з них має RTL-варіант (наприклад, arrow.right → автоматично arrow.left при RTL). Але логотипи та брендові іконки дзеркалити не можна — для них .semanticContentAttribute = .forceLeftToRight.
Android — LayoutDirection та Drawable
android:supportsRtl="true" у маніфесті — обов'язково, але недостатньо. start/end замість left/right у XML-атрибутах (paddingStart, layout_marginEnd) — Lint не поймає все, особливо в програмно створюваних LayoutParams.
VectorDrawable з autoMirrored="true" — правильний спосіб дзеркалити іконки. Але BitmapDrawable та PNG з mipmap не дзеркаляються автоматично. Потрібно або rotationY = 180f через код, або окремий ресурс у drawable-ldrtl-*.
У Jetpack Compose Modifier.padding(start = 16.dp) коректно дзеркаляється при RTL, а Modifier.offset(x = 16.dp) — ні. Різниця неочевидна, поки не запустиш на арабській локалі. Alignment.Start та Alignment.End працюють правильно; Alignment.Left — ні.
Анімації. SlideInHorizontally у Compose приймає лямбду initialOffsetX: для LTR це -fullWidth, для RTL — +fullWidth. Без перевірки LocalLayoutDirection.current анімація їде з неправильної сторони.
Як ми це реалізуємо
Аудит починається з запуску програми на арабській локалі через adb shell setprop persist.sys.locale ar-AE (Android) та Settings → General → Language & Region (iOS Simulator) — це виявляє 80% проблем ще до правок.
На iOS працюємо з UIView.appearance(whenContainedInInstancesOf:) для системного наслідування semanticContentAttribute, переписуємо користувацькі draw() під conditional flipping, перевіряємо всі NSTextAlignment.natural vs .left у старих XIB-файлах (.natural дзеркаляється, .left — ні).
На Android проводимо lint-аудит на hardcoded-left/right атрибути, замінюємо LayoutParams programmatically через MarginLayoutParamsCompat.setMarginStart/End, додаємо autoMirrored до векторних іконок напрямку.
Flutter потребує окремої уваги: TextDirection.rtl прокидується через Directionality widget. Але CustomPainter малює в абсолютних координатах Canvas — дзеркалирование через canvas.scale(-1, 1) з canvas.translate(-size.width, 0). Row з textDirection: TextDirection.rtl та стандартні Material віджети працюють коректно; проблеми — у користувацьких компонентах та анімаціях через AnimationController з hardcoded offset.
React Native — I18nManager.isRTL для умовної логіки, flexDirection: 'row' дзеркаляється при RTL автоматично на iOS, на Android — лише з React Native 0.63+. Для старих версій потрібна I18nManager.forceRTL(true) з перезапуском програми.
Типові помилки, які пропускають у ревью
- Жорсткозаданий
textAlign: 'left'у користувацьких компонентах замість'auto'або'start' -
transform: [{scaleX: -1}]застосований до іконки без перевіркиI18nManager.isRTL— іконка дзеркаляється завжди - Числа в арабському тексті:
١٢٣(eastern arabic numerals) vs123— залежить від контексту відображення, потрібно явно задаватиNSLocale/Locale - Бідіректціональний текст (арабська + англійське ім'я):
NSAttributedStringз явнимNSWritingDirectionAttributeNameінакше порядок слів ломається
Етапи роботи
- Аудит — запуск на арабській/ивритській локалі, скриншоти всіх екранів, складання списку артефактів
- Класифікація — layout issues, icon mirroring, text alignment, animation direction
- Правки — ітеративно по компонентам, починаючи з навігації та основних екранів
- Тестування — на реальних пристроях (Samsung з One UI змінює поведінку частини компонентів відносно AOSP)
- RTL-скриншот-тести — додаємо в CI, щоб регресії ловилися автоматично
Повний RTL-аудит та реалізація для програми середнього масштабу: 3-5 днів. Стоимость залежить від кількості екранів та платформ.







