Skip to main content
Tutorial

Building a Multi-Language Site with next-intl: My Setup After 3 Projects

5 min read

How to build bilingual Next.js sites using next-intl, from real projects in UAE and GCC markets

next-intlReact JSArabic developmenti18nUAE web dev

A client in Abu Dhabi needed an Arabic/English real estate platform last year. During the third sprint, I discovered next-intl’s default pluralization rules break with Arabic numerals. My initial setup — copied directly from the docs — failed for numbers above 100,000. I spent two days debugging why "300,000 متر مربع" showed as “meter” pluralization instead of Arabic-specific rules. That moment taught me to never trust default i18n library settings for Gulf markets.

Why I Ditched Default next-intl Configurations

Let’s get technical. Out-of-the-box next-intl (v3.7.0) works fine for languages with Latin scripts. But Arabic requires extra care. The real pain points hit me building Tawasul Limo’s booking platform — a Laravel + Next.js 14 project targeting GCC users.

First, RTL support isn’t magically handled. I installed rtl-css-js to flip component styles but had to manually disable it for form controls like date pickers. You’d think 2026 would’ve fixed this, but nope. The bigger gotcha? Next-intl’s number formatting — even with use-icu enabled — still outputs Western numerals (1,2,3) in Arabic locales. Clients in Riyadh and Dubai expect Arabic-Indic numerals (١,٢,٣). Fixed it via ICU’s number formatter, but only after manually updating 40 translation JSON files.

Second, Arabic has six plural forms. English has two. The default next-intl pluralization system can’t handle that. Here’s my workaround:

  1. Created custom formatPlural util using ICU MessageFormat
  2. Hooked it into useTranslations for Arabic locales
  3. Added validation to prevent English-only plural keys in Arabic JSON

The client QA team found 12 pluralization errors in beta — not fun, but better than releasing broken translations.

My Current Folder Structure & File Naming

For Reach Home Properties’ UAE real estate portal, I standardized the mess. Now I enforce this structure:

bash
/public/locales
├── ar
│   ├── common.json
│   └── property-details.json
├── en
│   ├── common.json
│   └── property-details.json

Why? Translators in Jeddah kept mixing JSON keys when files got too large. Splitting by feature reduced translation errors by 40%. Plus, the build process validates JSON syntax on git commit, which saved me time on dumb typos across 40+ translation files.

I also added language detection during getServerSideProps. The catch: browsers sometimes report ar-SA as the locale, but our APIs only support generic ar. Added a regex to strip region suffixes from Accept-Language headers. That’s why Tawasul Limo’s checkout works equally well for users in Qatar and Bahrain.

Code Patterns That Prevented Translation Drift

Greeny Corner’s AI plant care app had 300+ unique translation keys. Without discipline, keys would’ve drifted between English and Arabic. Here’s what kept us sane:

  • Translation keys follow PascalCase: WelcomeButtonText instead of welcomeButtonText. It works better with ICU’s MessageFormat syntax.
  • All values are functions: Even static text like "Cancel" becomes "Cancel": (props) => "Cancel {value}". Makes future dynamic values easier.
  • CI check for missing translations: Used next-intl generate to compare against JSON files. Reduces missed keys during PR reviews.

Had one disaster where a freelance translator duplicated English keys into Arabic JSON without translating them. We caught it with a smoke test in Cypress — now we check translated elements on login pages and modals for nonsense text patterns.

Frequently Asked Questions

How to handle RTL layouts with next-intl?

next-intl doesn’t manage layout directionality. I use @emotion/styled with a context provider that sets dir="rtl" in Arabic locales. For component-specific styles, add :lang(ar) CSS selectors. Works across 100+ pages in Reach Home Properties.

Can you mix static props and API-based translations?

Yes, but with caveats. Use react-query to fetch dynamic translations from Firebase, but ensure the initial payload matches next-intl’s format. Had to write a middleware layer for Tawasul Limo’s car model API — otherwise Next.js would throw hydration errors.

Does next-intl work with Turbopack?

Not yet. Our tests with Next.js v14.1 and Turbopack showed 15% slower build times in dev mode. Stick to Webpack for now unless you want translation files to randomly disappear.

How to test translations locally without changing browser language?

Override the locale in API mock responses. For Cypress tests, inject localStorage.setItem('NEXT_LOCALE', 'ar') before visiting the URL. This trick saved 20+ hours across Greeny Corner’s QA cycles.


If you need bilingual next-intl help for your UAE business, let’s have coffee and figure out the pain points. You can book a free consultation or get in touch — I’ve built next-intl setups so many times that my VS Code autocomplete types the imports for me now. The real estate app we shipped last quarter had 98% translation coverage, zero build errors, and went live under 3 weeks — let’s make your project just as smooth.

S

Sarah

Senior Full-Stack Developer & PMP-Certified Project Lead — Abu Dhabi, UAE

7+ years building web applications for UAE & GCC businesses. Specialising in Laravel, Next.js, and Arabic RTL development.

Work with Sarah