🌐 Localization (AR / EN)
How iSpeaker Live delivers a true Arabic-first experience without compromising the English version.
On this page
Overview
iSpeaker Live ships in two languages from day one:
🇸🇦 Arabic — العربية
Default language, right-to-left, primary target audience. All UI text, emails, push notifications, and PDF invoices localised. Stored as locale code ar.
🇬🇧 English
Left-to-right alternative, full parity with Arabic. Useful for non-Arabic-speaking speakers and the diaspora. Locale code en.
Locale parity rule: if a string exists in one language, it must exist in the other. CI checks for missing keys per build.
Strategy
- Default to user profile: the user's preferred language is stored in
user_profiles.language(arby default). - Override per request: mobile and web can send
Accept-Languageor a?lang=parameter; the backend'slocalizationmiddleware switches the application locale accordingly. - Persistence: the chosen language is stored locally (cookie on web, secure storage on Flutter) so the experience is consistent on next launch.
- Notifications & emails use the recipient's stored language, not the sender's.
- Server-rendered content (invoice PDFs, transactional emails) renders in the user's locale.
RTL Handling
What flips in RTL
Page direction
<html dir="rtl"> on Arabic. All blocks flow right-to-left.
Layout
Sidebar moves to the right; back arrows flip; chevrons point left-to-right (←) instead of right-to-left.
Margins / padding
Use logical properties (margin-inline-start) or duplicate styles per direction selector.
Lists & indentation
Bullets & numbers appear on the right.
Text alignment
Default to start; manually right-align Arabic content blocks where intentional.
Form fields
Labels right-aligned; icons inside inputs flip side.
What does NOT flip in RTL
- Numbers — always LTR (e.g. price
120.50 ر.س). - Code & technical strings — URLs, file paths, slugs.
- Brand logo — stays as-is.
- Progress bars — fill direction stays meaningful (left-to-right for English, right-to-left for Arabic when the metaphor matches).
- Media players — playback controls stay LTR-style to match Western media conventions users are familiar with.
Test every screen in both directions. Mixed content (Arabic with embedded English / numbers) is where bugs hide — use the Unicode direction marks if needed.
Backend (Laravel 11)
Lang files
Located at lang/ar/*.php and lang/en/*.php. Current files:
auth.php— login & throttle messages.validation.php— Laravel validator messages.passwords.php— password reset.pagination.php— next / previous labels.wallet.php— invoice headers, currency symbol, VAT label.
Middleware
A custom localization middleware (applied to all API groups) sets app()->setLocale($lang) from:
X-Langrequest header (highest priority).- Authenticated user's
user_profiles.language. - Default
ar.
// Sample translated response
return response()->json([
'message' => __('wallet.transaction_completed'),
'amount' => 120.50,
'currency'=> __('wallet.currency'),
]);
Sample Arabic wallet strings
| Key | English | Arabic |
|---|---|---|
invoice | Invoice | فاتورة |
invoice_number | Invoice Number | رقم الفاتورة |
bill_to | Bill To | فاتورة إلى |
subtotal | Subtotal | المجموع الفرعي |
vat | VAT | ضريبة القيمة المضافة |
grand_total | Grand Total | المجموع الكلي |
currency | SAR | ر.س |
Web (Next.js 15)
- Locale state managed via React context + persisted to cookie / localStorage.
- The root layout sets
<html lang dir>based on context. - All UI strings live in dictionary maps; no inline literals in components.
- API requests carry
X-Langheader so server-side validation and notifications also localise. - Tailwind
rtl:variants used selectively; logical CSS properties preferred globally. - Date/time/number formatting via
Intl(e.g.Intl.DateTimeFormat('ar-SA')).
Mobile (Flutter)
Implementation lives in lib/core/localization/:
app_localizations.dart—AppLocalizationsclass withtranslate(key).app_localizations_delegate.dart— registeredLocalizationsDelegatesupportingenandar.languages/ar.dart&languages/en.dart— flatMap<String, String>of keys.
// Usage
final t = AppLocalizations.of(context);
Text(t.translate('login.welcome_back'));
- App-level locale persisted via secure storage.
MaterialApp.localerebuilt on change; the directionality follows automatically.- Text style uses
Tajawalfor Arabic,Interfor English (selected per locale in theme).
Localized Data
Some content needs to exist in both languages in the database:
| Table.field | Strategy |
|---|---|
categories.name_en / categories.name_ar | Twin columns. API serves the field matching current locale. |
courses.language | Enum ar / en — declares the course's content language (not the UI). |
user_profiles.language | Per-user UI preference. |
user_profiles.timezone | Default Asia/Riyadh. |
| User-generated content (posts, reviews, etc.) | Stored as written. We don't auto-translate — users see content in the author's language. |
Course / book marketplace filters offer a language filter so students can show only content in their preferred language.
Formatting
| Data | Arabic example | English example |
|---|---|---|
| Numbers | 1,234.56 (Arabic-Indic digits optional) | 1,234.56 |
| Currency | 120.50 ر.س | SAR 120.50 |
| Date | 13 يناير 2026 | Jan 13, 2026 |
| Time | 02:30 م | 2:30 PM |
| Relative time | منذ 5 دقائق | 5 minutes ago |
| Phone | +966 5X XXX XXXX | +966 5X XXX XXXX |
| Address | الرياض، السعودية | Riyadh, Saudi Arabia |
Time zones
- Default:
Asia/Riyadh(UTC+3). - Live rooms and consultations show the speaker's tz and the viewer's local tz.
- All timestamps stored in UTC; conversion happens at the edges.
Translation Workflow
- Developer adds a new key to
lang/en/<file>.php+languages/en.dart+ the web dictionary. - CI fails if a key exists in one locale and not the other.
- Translator (or AI assist + human review) fills in the Arabic value.
- PR review checks for tone, length (Arabic ~20% longer typically), and gender-neutral phrasing where applicable.
- Merged → deployed → tested visually in both directions.
Tone & voice
- Arabic: formal MSA, polite, gender-neutral when possible.
- English: clear and friendly, sentence case for buttons, title case for headings.
- Avoid idioms that don't translate. Avoid country-specific slang.
Localization QA
See dedicated test cases in Test Cases. Quick checklist:
- ☐ Switch language from settings — UI updates immediately without restart.
- ☐ Every page passes visual review in both
aranden. - ☐ Arabic text uses Tajawal font and renders crisply.
- ☐ Numbers, prices, and phone numbers stay LTR in Arabic context.
- ☐ Dates and times match the selected locale.
- ☐ Toasts, dialogs, and validation errors are translated.
- ☐ Invoice PDF and emails render in the recipient's language.
- ☐ FCM push notifications are in the user's language.
- ☐ Right-side icons flip in RTL (back arrow, chevrons).
- ☐ Mixed Arabic + English strings (e.g. brand names) render with correct bidi.
- ☐ Long Arabic strings don't overflow buttons or table cells.
- ☐ No untranslated keys leaking as
foo.bar.bazin the UI.