01Why bilingual matters in GCC
If you're building a product for Qatar, the UAE, Saudi Arabia, Kuwait, Bahrain, Oman or any of the wider Arabic-speaking world, "English-only" is a tax. Roughly 60–80% of GCC users prefer Arabic interfaces when given a choice — even highly bilingual professionals reach for Arabic on personal apps. Skip Arabic and you cap your market.
The good news in 2026 is that the tooling for bilingual apps is meaningfully better than it was five years ago. CSS logical properties are stable, React Native's I18nManager works, Flutter ships RTL-correct widgets out of the box, and LLMs can carry 80% of the translation load. The bad news is that most teams still treat Arabic as an afterthought — shipping mirrored layouts that look broken, fonts that fight each other, and number formats that no Arabic user actually wants.
This guide is the playbook we use at DreamIT across the 40+ bilingual products we've shipped. It assumes you're building on React, React Native, or Flutter — but most of it generalises.
02RTL fundamentals
Right-to-left is not just "flip the layout horizontally". It's a directional inversion of the entire reading flow. Done well, an Arabic user feels at home. Done badly, the app feels uncanny — like a poorly translated movie.
Web: use CSS logical properties. Replace margin-left: 16px with margin-inline-start: 16px. Replace padding-right with padding-inline-end. Replace left/right positioning with inset-inline-start/end. Flexbox direction is handled automatically when the document root has dir="rtl". Browser support is universal in 2026.
Set the direction at the root: <html dir="rtl" lang="ar"> when Arabic is active. Toggle dynamically when the user switches language; modern Tailwind, CSS-in-JS, and design systems handle the mirroring for free when you stick to logical properties.
React Native: I18nManager.forceRTL. Call I18nManager.forceRTL(true) when the user selects Arabic, then prompt for an app restart on Android (iOS handles it on next launch). React Native's flexbox flips automatically. The major gotcha is animations — anything using absolute pixel offsets needs to be mirrored manually.
Flutter: built-in support. Set locale: Locale('ar') and Flutter mirrors the entire widget tree automatically. EdgeInsetsDirectional and AlignmentDirectional replace their LTR-biased equivalents.
03Arabic font systems
This is where most teams visibly fail. The default Arabic font on Android (Droid Arabic Naskh) and iOS (Geeza Pro) are functional but ugly — and using them screams "we didn't care". A handful of free fonts are now production-ready:
- Tajawal — clean, modern, broad weight range (200–900). Our default for product UI. Pairs cleanly with Space Grotesk, Inter, or Manrope on the Latin side.
- Cairo — slightly more humanist than Tajawal. Excellent for editorial and content-heavy apps. Pairs with Lora, Source Serif, or Inter.
- IBM Plex Sans Arabic — designed to pair pixel-perfectly with IBM Plex Sans. The cleanest cross-script pairing available for free in 2026.
- Noto Sans Arabic — Google's workhorse. Boring but bulletproof. Useful for system-heavy apps where you want minimum surprise.
- Almarai and Reem Kufi — display fonts for headings and brand moments.
Two production rules. First, always pair an Arabic font deliberately with a Latin font. Don't leave it to system fallback — the weight, x-height and rhythm mismatch is visible even to non-readers. Second, load only the weights you need. Arabic fonts are large (300–600 KB per weight is normal). Subsetting and only loading 400 + 600 will save a meaningful chunk of TTI.
For Qatari and Khaleeji audiences specifically, Tajawal has become the de facto default. We use it across the SAFAR product line, including the Arabic edition of safar.mydreamit.dev.
04i18n architecture
Pick one library and stick with it. In 2026 our defaults are:
- React/Next.js: next-intl. Server-component friendly, namespacing is sane, ICU MessageFormat for plurals.
- React Native: i18next + react-i18next, with locize or Lokalise for translation management.
- Flutter: Built-in
intlpackage with ARB files, or easy_localization for richer features.
Three architectural rules we enforce on every project:
- Namespace by feature, not by screen.
common.json,auth.json,checkout.jsonbeats one gianttranslations.json. Easier to delegate, easier to lazy-load. - Use ICU MessageFormat for plurals. Arabic has six plural forms (zero, one, two, few, many, other). Don't fake it with English-style plurals — it will read as broken.
- Treat translation keys as code. Type them. Catch missing keys at build time with TypeScript or a CI check. Ship with a fallback locale, never with an empty string.
Bonus: for any string that varies by region (visa fees, currency, regulatory wording), separate translation from localisation data. Translations live in JSON; localisation data lives in your backend so it can change without a release.
05Numbers, dates, currency
This is the area where "we translated the app" most often becomes "we broke the app for Arabic users".
Numerals. Arabic users in the Gulf almost universally use Western (1234567890) numerals for prices, phone numbers and quantities — not Eastern Arabic (١٢٣٤٥٦٧٨٩٠). Don't auto-convert. Let the design system decide context-by-context. Eastern Arabic numerals are appropriate for religious content, formal certificates, and decorative typography — almost nothing else in a product UI.
Currency. Use Intl.NumberFormat with the appropriate locale and currency code. new Intl.NumberFormat('ar-QA', {style:'currency', currency:'QAR'}) produces the right symbol position and grouping. Never concatenate "$" + amount.
Dates. The Gregorian calendar is dominant in business contexts even for Arabic users. The Hijri calendar matters for religious or government-related dates. Many apps offer both side by side, or a toggle. Use Intl.DateTimeFormat with a Hijri calendar option ({calendar: 'islamic-umalqura'}) for Saudi audiences specifically.
Phone numbers. Always store E.164. Display formatted to the user's country: +974 for Qatar, +971 for UAE, +966 for KSA. The libphonenumber library handles every edge case.
06Forms & input UX
Forms are where bilingual UX gets subtle. A few rules that have saved us hundreds of hours of bug-fixing:
- Label alignment follows text direction. Labels go on the right in Arabic, left in English. Use logical properties.
- Inputs that accept numbers (phone, OTP, prices) stay LTR even in an RTL layout. Force
direction: ltron those inputs specifically — otherwise the cursor jumps and users hate you. - BiDi text. Strings that mix LTR (an English brand name, a URL, an email address) inside an RTL paragraph need Unicode bidirectional algorithm hints (LRE/RLE/PDF or LRM/RLM markers, or modern
dir="auto"). When in doubt, addunicode-bidi: isolateto embedded English. - Right-side icons. Search bars, password reveal toggles, clear-input X buttons all need to mirror in Arabic.
- Validation messages. Match the field's direction. Never auto-truncate Arabic validation messages — the meaning often comes at the end of the sentence.
07Translation workflow
The 2026 workflow we run on every project at DreamIT:
- Source-of-truth English copy goes into Lokalise/Locize/Crowdin. Engineering never edits strings outside this tool.
- First-pass Arabic via Claude or GPT-4o, with system prompts that include the product context, target dialect (MSA vs Khaleeji), and tone (formal, casual, technical). Costs about 1¢ per 1000 words. Quality is roughly 80% of human.
- Native human reviewer — ideally someone who has used the product or knows the domain — polishes. This catches the 20% the LLM gets wrong, which is almost always brevity, dialect colour, or product-specific terminology.
- In-context QA in the running app — not in the spreadsheet. Strings that read fine in isolation often break inside UI (too long, ambiguous pronoun, awkward break).
- Versioned releases — translation keys ship with the app version they were written for. Don't hot-swap translations without testing layouts.
End-to-end this is roughly 4× faster than fully human translation and produces materially better quality than fully machine translation. It's the workflow under every DreamIT mobile app shipped in the last 12 months.
08Testing RTL
You will not find RTL bugs by reading code. You find them by running the app in Arabic and clicking everything. The discipline:
- Every visual regression test should run twice — once in English, once in Arabic.
- Have at least one bilingual person on the QA team. Ideally a native Arabic speaker for whom Arabic is the daily-driver language.
- Use pseudo-localisation in addition to real Arabic, to catch hardcoded strings and overflow bugs (Arabic averages ~25% longer than English for the same meaning).
- Test the language switch itself — does state survive, does the app restart cleanly, do animations resume correctly?
- Test mixed content — an Arabic UI showing English-named products, English UI showing Arabic place names, etc.
09Common pitfalls
The bugs we see in nearly every audit:
- Icons that don't mirror. Back arrows, send arrows, breadcrumb chevrons, undo/redo, next/previous. All need to mirror in RTL. SF Symbols and Material Symbols both have RTL variants — use them.
- Animations sliding the wrong way. A side-drawer that slides in from the left in English should slide in from the right in Arabic. Same for screen transitions, snackbars, toasts.
- Charts with reversed reading order. Time-series charts should still read time left-to-right in MOST Arabic contexts (because the international convention is so strong), but legends, axis labels and tooltips should be in Arabic and mirrored where appropriate. Test with a native user — there's genuine ambiguity here.
- Truncation that cuts off meaning. Arabic sentences often carry the most important word at the end. Use ellipsis carefully or, better, allow wrapping.
- Hardcoded English placeholders in form fields. "Enter your name" leaks into Arabic builds. Catch with strict missing-translation checks in CI.
- Negative numbers. "-$100" in Arabic context should be "-١٠٠ ر.ق" or "-100 QAR" — not "$100-". Intl APIs handle this; manual formatting doesn't.
10The DreamIT bilingual playbook
Compressed to the page you should pin near your engineering team:
- Use logical CSS properties / I18nManager / EdgeInsetsDirectional. Never hardcode left/right.
- Pick one Arabic + Latin font pair per product. Subset to weights you use.
- One i18n library per platform. ICU MessageFormat for plurals.
- Intl APIs for all numbers, currency and dates. Never string-concatenate.
- Force-LTR on numeric inputs even in RTL layouts.
- LLM first-pass + human review for translations.
- Test in Arabic with a native speaker. Twice. Before launch and after every major release.
- Mirror icons. Mirror animations. Reverse drawer directions.
- Pseudo-localise in CI to catch hardcoded strings.
- Ship the language switcher prominently — never bury it in settings.
11FAQ
Best way to support RTL in React/React Native? CSS logical properties on the web; I18nManager.forceRTL on React Native. Avoid hardcoded left/right values.
Which Arabic fonts should I use in 2026? Tajawal, Cairo, IBM Plex Sans Arabic, Noto Sans Arabic for UI. Almarai and Reem Kufi for display. Always pair deliberately with a Latin font.
Do I need separate iOS and Android builds for Arabic? No. One codebase. React Native needs an app restart after forceRTL on Android.
Can I use LLMs to translate to Arabic? Yes for first pass, with human review for production. About 4× faster than fully human at near-equivalent quality.
Most common bilingual app bugs? Icons not mirroring, animations going the wrong way, number formatting, mixed LTR/RTL strings, charts with wrong axis order.
Building a bilingual product and want a sanity check? Book a 30-minute architecture review with our mobile and web teams — we'll review your i18n, RTL, and translation workflow in one call.