{ }
Published on

How to Localize an Expo App in Arabic with i18n-js & NativeWind (2025 Guide)

Authors
  • avatar
    Name
    Ahmed Farid
    Twitter
    @

TIP

Localise early—international-ready architecture prevents refactors when you add more languages later.

This guide walks you through transforming a plain Expo app into a fully-localised, Arabic-friendly experience. We’ll cover:

  1. Installing and configuring i18n-js.
  2. Managing translation JSON files with pluralisation.
  3. Detecting locale & forcing RTL with I18nManager.
  4. Styling Arabic text with NativeWind utilities.
  5. Formatting dates, numbers, and currencies.
  6. Dynamic language switching at runtime.
  7. Automated testing and accessibility.

Table of Contents

1. Prerequisites & Terminology

  • Expo SDK 50 (managed workflow).
  • Node.js 18+.
  • Basic Tailwind / NativeWind knowledge.
TermMeaning
RTLRight-to-left scripts (Arabic, Hebrew…)
i18n-jsLightweight internationalisation library for JS
I18nManagerReact Native API to control layout direction

2. Project Setup & Dependencies

npx create-expo-app ArabicLocaleApp
cd ArabicLocaleApp
yarn add i18n-js expo-localization nativewind tailwindcss-rtl
npx nativewind init

3. Organise Translation Files

Create src/locales/en.json & src/locales/ar.json:

// en.json
{
  "welcome": "Welcome, %{name}!",
  "items": {
    "one": "%{count} item",
    "other": "%{count} items",
  },
}
// ar.json
{
  "welcome": "مرحبا، %{name}!",
  "items": {
    "one": "عنصر واحد",
    "other": "%{count} عناصر",
  },
}

4. Configure i18n-js & Locale Detection

// src/i18n/config.ts
import * as Localization from 'expo-localization'
import I18n from 'i18n-js'
import en from './locales/en.json'
import ar from './locales/ar.json'

I18n.fallbacks = true
I18n.translations = { en, ar }
I18n.locale = Localization.locale // e.g. ar-EG

export default I18n

Use in components:

import I18n from '@/i18n/config'
;<Text>{I18n.t('welcome', { name: 'Ali' })}</Text>

5. Force RTL for Arabic & Persist Choice

// src/i18n/rtl.ts
import { I18nManager } from 'react-native'
import * as Updates from 'expo-updates'

export async function applyDirection(locale: string) {
  const isRTL = locale.startsWith('ar')
  if (I18nManager.isRTL !== isRTL) {
    I18nManager.allowRTL(true)
    I18nManager.forceRTL(isRTL)
    await Updates.reloadAsync() // full reload required
  }
}

Call applyDirection(I18n.locale) once at app start or when language changes.

6. Tailwind RTL Utilities with NativeWind

Update tailwind.config.js:

module.exports = {
  content: ['./App.{js,jsx,ts,tsx}', './src/**/*.{js,jsx,ts,tsx}'],
  plugins: [require('tailwindcss-rtl')],
  theme: {
    extend: {
      fontFamily: {
        arabic: ['Tajawal_400Regular', 'System'],
      },
    },
  },
}

Use logical classes:

<View className="space-s-4 flex-row-reverse">
  <Text className="font-arabic text-right">{I18n.t('welcome', { name })}</Text>
</View>

space-s-4 adds spacing on the start side, automatically flipping between LTR/RTL.

7. Load Arabic Fonts

yarn add @expo-google-fonts/tajawal expo-font
import { useFonts, Tajawal_400Regular } from '@expo-google-fonts/tajawal'

8. Format Dates & Numbers

import dayjs from 'dayjs'
import 'dayjs/locale/ar'

const formatted = dayjs().locale(I18n.locale).format('DD MMMM YYYY')

const price = new Intl.NumberFormat('ar-EG', { style: 'currency', currency: 'SAR' }).format(1234.5)

9. Dynamic Language Switcher

const changeLang = async (lang: 'en' | 'ar') => {
  I18n.locale = lang
  await applyDirection(lang)
  setLang(lang) // state for re-render
}

Persist choice via SecureStore or AsyncStorage.

10. E2E & Unit Testing

  • Jest: mock expo-localization to each locale.
  • Detox: run with I18nManager.forceRTL(true) to screenshot RTL.
  • Snapshot tests ensure no hard-coded English.

11. Accessibility & UX Tips

✅ Mirror navigation icons (back arrow) when RTL.
✅ Keep numbers Arabic-Indic if audience expects (ar-EG vs ar-SA).
✅ Avoid mixed alignment—whole paragraph should be text-right.

12. Performance Considerations

  • Lazy-load translation JSON per locale to reduce bundle size.
  • Memoise expensive Intl formatting.

13. Further Reading & Resources

  • i18n-js docs.
  • Expo Localisation API.
  • Tailwind RTL plugin.
  • W3C bidi guidelines.

14. Conclusion

You now have a robust Arabic localisation setup: translations, RTL layouts, fonts, formatting, and runtime switching—all without ejecting from Expo. 🌍📱