Skip to main content

Internationalization (i18n)

UniPulse supports English (LTR) and Arabic (RTL) using react-i18next with JSON translation files and the Alexandria font family.


Supported Languages

LanguageCodeDirectionStatus
EnglishenLTRComplete
ArabicarRTLComplete

File Structure

apps/web/src/i18n/
├── config.ts # i18next configuration
├── en/ # English translations
│ ├── common.json # Shared strings (buttons, labels, errors)
│ ├── dashboard.json # Dashboard page
│ ├── composer.json # Composer page
│ ├── analytics.json # Analytics page
│ ├── conversations.json # Conversations page
│ ├── workflows.json # Workflows page
│ ├── ecommerce.json # E-Commerce page
│ ├── admin.json # Admin pages
│ └── ... # One file per feature domain
└── ar/ # Arabic translations (mirrors en/ structure)
├── common.json
├── dashboard.json
├── composer.json
└── ...

Configuration

// apps/web/src/i18n/config.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

import enCommon from './en/common.json';
import arCommon from './ar/common.json';
// ... other namespace imports

i18n.use(initReactI18next).init({
resources: {
en: { common: enCommon, dashboard: enDashboard, /* ... */ },
ar: { common: arCommon, dashboard: arDashboard, /* ... */ },
},
defaultNS: 'common',
fallbackLng: 'en',
interpolation: { escapeValue: false },
});

Usage in Components

Basic Translation

import { useTranslation } from 'react-i18next';

function DashboardHeader() {
const { t } = useTranslation('dashboard');
return <h1>{t('title')}</h1>;
}

With Interpolation

// en/common.json: { "welcome": "Welcome, {{name}}!" }
<p>{t('common:welcome', { name: user.name })}</p>

With Plurals

// en/common.json: { "posts_count": "{{count}} post", "posts_count_other": "{{count}} posts" }
<p>{t('common:posts_count', { count: posts.length })}</p>

Namespace-Scoped

// Access strings from a specific namespace
const { t } = useTranslation('analytics');
<h2>{t('timeSeries.title')}</h2>

// Access from another namespace with prefix
<p>{t('common:buttons.save')}</p>

RTL Support

When Arabic is selected, the dir="rtl" attribute is set on the <html> element. Tailwind CSS v4 handles RTL layout automatically using CSS logical properties.

// Language switching triggers RTL
function switchLanguage(lang: 'en' | 'ar') {
i18n.changeLanguage(lang);
document.documentElement.dir = lang === 'ar' ? 'rtl' : 'ltr';
document.documentElement.lang = lang;
}

Tailwind v4 Logical Properties

Tailwind v4 uses logical properties by default, which means:

Physical (LTR-only)Logical (LTR + RTL)Behavior
ml-4ms-4 (margin-inline-start)Left in LTR, right in RTL
mr-4me-4 (margin-inline-end)Right in LTR, left in RTL
pl-4ps-4 (padding-inline-start)Left in LTR, right in RTL
text-lefttext-startLeft in LTR, right in RTL
Use Logical Properties

Always prefer ms-, me-, ps-, pe-, text-start, text-end over ml-, mr-, pl-, pr-, text-left, text-right to ensure proper RTL support.


Font: Alexandria

The Alexandria font family supports both Latin and Arabic scripts with consistent visual weight.

/* Loaded in the global stylesheet */
@import url('https://fonts.googleapis.com/css2?family=Alexandria:wght@300;400;500;600;700&display=swap');

body {
font-family: 'Alexandria', sans-serif;
}

Adding New Translations

Step-by-Step

  1. Add the English key to the appropriate namespace file:
// i18n/en/workflows.json
{
"builder": {
"addNode": "Add Node",
"testRun": "Test Run"
}
}
  1. Add the Arabic translation to the mirror file:
// i18n/ar/workflows.json
{
"builder": {
"addNode": "إضافة عقدة",
"testRun": "تشغيل تجريبي"
}
}
  1. Use in your component:
const { t } = useTranslation('workflows');
<Button>{t('builder.addNode')}</Button>
Keep Translations Synchronized

Always add both English and Arabic translations at the same time. Missing Arabic keys will fall back to English, which creates a mixed-language UI.


Language Persistence

The selected language is stored in the Zustand ui.store and persisted to localStorage. On page load, the stored language preference is applied before rendering.


Cross-Reference