Internationalization (i18n) Complete Guide
Library: @olorin/shared-i18nSupported Languages: 10 (Hebrew, English, Spanish, Chinese, French, Italian, Hindi, Tamil, Bengali, Japanese) Last Updated: 2026-02-12
Overview
Bayit+ supports 10 languages with full internationalization across all platforms (Web, iOS, Android, tvOS). The system uses i18next with platform-specific adapters for storage and configuration.
Supported Languages
| Language | Code | Direction | Status |
|---|---|---|---|
| Hebrew | he | RTL | Primary |
| English | en | LTR | Primary |
| Spanish | es | LTR | Complete |
| Chinese (Simplified) | zh | LTR | Complete |
| French | fr | LTR | Complete |
| Italian | it | LTR | Complete |
| Hindi | hi | LTR | Complete |
| Tamil | ta | LTR | Complete |
| Bengali | bn | LTR | Complete |
| Japanese | ja | LTR | Complete |
Architecture
Package Structure
olorin-core/packages/shared-i18n/
├── src/
│ ├── index.ts # Main export
│ ├── web.ts # Web platform config
│ ├── native.ts # React Native config
│ └── config.ts # Shared configuration
├── locales/ # Translation files
│ ├── en/
│ │ └── translation.json
│ ├── he/
│ │ └── translation.json
│ ├── es/
│ │ └── translation.json
│ └── ... (10 languages)
└── package.jsonPlatform Variants
Web (React):
- Uses
localStoragefor persistence - Browser language detection
react-i18nextintegration
Mobile (React Native iOS/Android):
- Uses
AsyncStoragefor persistence - Device language detection
- Platform-specific date/time formatting
tvOS (React Native for TV):
- Same as Mobile
- TV-optimized language selection UI
Installation & Setup
Web Platform
// app/main.tsx
import i18n from '@olorin/shared-i18n';
import { initWebI18n } from '@olorin/shared-i18n/web';
// Initialize i18n
initWebI18n();
// Use with React
import { I18nextProvider } from 'react-i18next';
<I18nextProvider i18n={i18n}>
<App />
</I18nextProvider>Mobile Platform (iOS/Android)
// app/index.tsx
import i18n from '@olorin/shared-i18n';
import { initNativeI18n } from '@olorin/shared-i18n/native';
// Initialize i18n
await initNativeI18n();
// Use with React Native
<App />tvOS Platform
// Same as Mobile
import { initNativeI18n } from '@olorin/shared-i18n/native';
await initNativeI18n();iOS/tvOS Native (SwiftUI)
The iOS and tvOS apps use a dedicated BayitLocalization Swift Package for native localization:
ios-app/Packages/BayitLocalization/
├── Sources/
│ ├── Resources/
│ │ ├── en.json
│ │ ├── he.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── bn.json
│ │ ├── ja.json
│ │ ├── zh.json
│ │ ├── ta.json
│ │ └── it.json
│ └── BayitLocalization.swift
└── Package.swiftUsage in SwiftUI:
import BayitLocalization
struct MyView: View {
var body: some View {
Text(L10n.common.welcome)
Text(L10n.zehAni.magicMirror.title)
}
}Adding New Keys: When adding new features, update ALL 10 locale JSON files in ios-app/Packages/BayitLocalization/Sources/Resources/. The JSON structure mirrors the web/mobile translation files with the same namespace organization.
Usage
Basic Translation
import { useTranslation } from 'react-i18next';
function MyComponent() {
const { t } = useTranslation();
return (
<div>
<h1>{t('common.welcome')}</h1>
<p>{t('common.description')}</p>
</div>
);
}Translation with Variables
const { t } = useTranslation();
// Translation file: "greeting": "Hello, {{name}}!"
<Text>{t('common.greeting', { name: 'John' })}</Text>
// Output: "Hello, John!"Pluralization
const { t } = useTranslation();
// Translation file:
// "items": "{{count}} item",
// "items_plural": "{{count}} items"
<Text>{t('common.items', { count: 1 })}</Text>
// Output: "1 item"
<Text>{t('common.items', { count: 5 })}</Text>
// Output: "5 items"Date/Time Formatting
import { formatDate, formatTime } from '@olorin/shared-i18n';
const date = new Date('2026-01-30T12:30:00Z');
// Format date according to current language
const formattedDate = formatDate(date);
// en: "January 30, 2026"
// he: "30 בינואר 2026"
// es: "30 de enero de 2026"
// Format time
const formattedTime = formatTime(date);
// en: "12:30 PM"
// he: "12:30"
// es: "12:30"Number Formatting
import { formatNumber, formatCurrency } from '@olorin/shared-i18n';
// Format number with locale
formatNumber(1234567.89);
// en: "1,234,567.89"
// fr: "1 234 567,89"
// Format currency
formatCurrency(99.99, 'USD');
// en: "$99.99"
// es: "99,99 US$"Language Switching
Programmatic Language Change
import i18n from '@olorin/shared-i18n';
// Change language
await i18n.changeLanguage('es');
// Get current language
const currentLang = i18n.language; // 'es'Language Selector Component
import { useTranslation } from 'react-i18next';
import { GlassButton } from '@bayit/glass';
function LanguageSelector() {
const { i18n } = useTranslation();
const languages = [
{ code: 'he', name: 'עברית' },
{ code: 'en', name: 'English' },
{ code: 'es', name: 'Español' },
{ code: 'fr', name: 'Français' },
];
return (
<div className="language-selector">
{languages.map(lang => (
<GlassButton
key={lang.code}
variant={i18n.language === lang.code ? 'primary' : 'secondary'}
onPress={() => i18n.changeLanguage(lang.code)}
>
{lang.name}
</GlassButton>
))}
</div>
);
}Translation File Structure
Namespace Organization
{
"common": {
"welcome": "Welcome",
"description": "Stream Israeli content",
"loading": "Loading...",
"error": "An error occurred"
},
"navigation": {
"home": "Home",
"movies": "Movies",
"series": "Series",
"podcasts": "Podcasts"
},
"auth": {
"login": "Log In",
"signup": "Sign Up",
"logout": "Log Out",
"forgotPassword": "Forgot Password?"
},
"content": {
"play": "Play",
"pause": "Pause",
"addToWatchlist": "Add to Watchlist",
"remove": "Remove"
}
}Complete Namespace Registry
As of 2026-02-12, the following namespaces are active across all platforms:
| Namespace | Description | Platforms |
|---|---|---|
common | Shared UI strings (buttons, labels, errors) | All |
account | Login, registration, profile | All |
navigation | Menu items, tab labels | All |
content | VOD library, series, movies | All |
player | Playback controls, overlays | All |
live | Live TV, EPG, recordings | All |
radio | Radio stations and streaming | All |
audiobooks | Audiobook library and playback | All |
podcasts | Podcast directory and playback | All |
search | Search, LLM search, suggestions | All |
social | Friends, DMs, watch party | Web, iOS, Mobile |
missions | Daily missions, gamification, leaderboard | All |
chess | Chess gameplay | All |
trivia | Live trivia during streaming | All |
phonetic-mirror | Pronunciation practice | Web, iOS, Mobile |
talkback | Voice-response learning | Web, iOS, Mobile |
comprehension | Comprehension quizzes | All |
star-story | AI-generated stories | Web, iOS, tvOS |
grandparent-bridge | News clips, family sharing | Web, iOS, tvOS, Mobile |
chameleon-avatar | Avatar style transfer | Web, iOS, tvOS |
zeh-ani | Zeh Ani full feature suite | All |
family-controls | Parental controls, PIN, ratings | All |
household | Household management | Web, iOS, Mobile |
settings | App settings, preferences | All |
subscription | Plans, billing, payments | Web, iOS, Mobile |
cultures | Cultural content (Jerusalem, Tel Aviv) | All |
calendar | Jewish calendar, holidays | All |
beta | Beta 500 credits program | All |
onboarding | User onboarding flows | Web, iOS, Mobile |
help | Help and support | All |
errors | Error messages | All |
admin | Admin dashboard | Web |
Nested Keys
{
"settings": {
"profile": {
"title": "Profile Settings",
"name": "Name",
"email": "Email",
"save": "Save Changes"
},
"notifications": {
"title": "Notification Settings",
"email": "Email Notifications",
"push": "Push Notifications"
}
}
}Usage:
t('settings.profile.title') // "Profile Settings"
t('settings.notifications.email') // "Email Notifications"RTL (Right-to-Left) Support
Detecting RTL
import { isRTL } from '@olorin/shared-i18n';
const rtl = isRTL(i18n.language);
// true for Hebrew ('he'), Arabic ('ar')
// false for English ('en'), Spanish ('es')Applying RTL Styles
Web:
import { useTranslation } from 'react-i18next';
function MyComponent() {
const { i18n } = useTranslation();
const direction = i18n.dir(); // 'ltr' or 'rtl'
return (
<div dir={direction}>
<h1>{t('common.title')}</h1>
</div>
);
}React Native:
import { I18nManager } from 'react-native';
// Enable RTL
I18nManager.allowRTL(true);
I18nManager.forceRTL(true);
// Restart required after changing RTLRTL-Aware Styling
// Tailwind CSS (Web)
<div className="ml-4 rtl:mr-4 rtl:ml-0">
Content
</div>
// StyleSheet (Mobile)
const styles = StyleSheet.create({
container: {
marginLeft: I18nManager.isRTL ? 0 : 16,
marginRight: I18nManager.isRTL ? 16 : 0,
},
});Adding New Languages
Step 1: Add Translation Files
Create translation file:
mkdir -p locales/new-lang
touch locales/new-lang/translation.jsonExample: locales/ar/translation.json (Arabic)
{
"common": {
"welcome": "مرحبا",
"description": "بث المحتوى الإسرائيلي"
}
}Step 2: Update Configuration
// shared-i18n/src/config.ts
export const SUPPORTED_LANGUAGES = [
'he', 'en', 'es', 'zh', 'fr', 'it',
'hi', 'ta', 'bn', 'ja',
'ar' // Add new language
];
export const RTL_LANGUAGES = ['he', 'ar'];Step 3: Add Language Metadata
// Add to language selector
const languages = [
// ...existing
{ code: 'ar', name: 'العربية', direction: 'rtl' }
];Step 4: Test Translation
# Run i18n tests
npm test -- i18n
# Manual testing
i18n.changeLanguage('ar');Translation Management
Translation Keys Naming Convention
Format: namespace.category.key
Examples:
common.button.save
common.button.cancel
auth.login.title
auth.login.emailPlaceholder
content.movie.play
content.series.addToWatchlistGuidelines:
- Use camelCase for keys
- Keep keys descriptive but concise
- Group related keys under common namespace
- Avoid deep nesting (max 3 levels)
Missing Translations
Fallback Behavior:
- Use English (en) as fallback
- Show translation key if no fallback
- Log warning in development
// With fallback
t('missing.key') // Falls back to English
// Development warning:
// "Translation key 'missing.key' not found for language 'es'"Detecting Missing Keys:
// Enable missing key handler (development only)
i18n.on('missingKey', (lngs, namespace, key) => {
console.warn(`Missing translation: ${namespace}:${key} for ${lngs}`);
});Best Practices
DO
- Externalize all user-facing text - No hardcoded strings
- Use descriptive keys -
common.button.savenotbtn1 - Include context - Helps translators understand usage
- Test all languages - Verify translations display correctly
- Support RTL - Test with Hebrew to verify RTL support
- Use pluralization - Handle singular/plural correctly
- Format dates/numbers - Use locale-aware formatting
- Keep translations synced - All languages complete
DON'T
- Don't hardcode text - Always use
t()function - Don't concatenate translations - Use variables instead
- Don't assume text length - Translations vary in length
- Don't use images with text - Doesn't translate
- Don't skip RTL testing - Hebrew users need RTL
- Don't use machine translation only - Review by native speakers
- Don't forget accessibility - ARIA labels need translation
Common Issues
Issue: Translations Not Loading
Solution:
// Check i18n initialization
console.log('i18n initialized:', i18n.isInitialized);
console.log('Current language:', i18n.language);
console.log('Available languages:', i18n.languages);
// Manually load translations
import translation from './locales/en/translation.json';
i18n.addResourceBundle('en', 'translation', translation);Issue: RTL Not Working
Solution:
// Verify RTL detection
console.log('Direction:', i18n.dir());
console.log('Is RTL:', I18nManager.isRTL); // React Native
// Force RTL (React Native)
I18nManager.forceRTL(true);
RNRestart.Restart(); // Requires app restartIssue: Date Formatting Incorrect
Solution:
// Use locale-aware date formatting
import { format } from 'date-fns';
import { he, enUS, es } from 'date-fns/locale';
const locales = { he, en: enUS, es };
format(date, 'PPP', { locale: locales[i18n.language] });Testing
Unit Testing Translations
import i18n from '@olorin/shared-i18n';
describe('i18n', () => {
beforeAll(async () => {
await i18n.init();
});
it('translates common keys', () => {
expect(i18n.t('common.welcome')).toBe('Welcome');
});
it('handles pluralization', () => {
expect(i18n.t('common.items', { count: 1 })).toBe('1 item');
expect(i18n.t('common.items', { count: 5 })).toBe('5 items');
});
it('supports RTL languages', () => {
i18n.changeLanguage('he');
expect(i18n.dir()).toBe('rtl');
i18n.changeLanguage('en');
expect(i18n.dir()).toBe('ltr');
});
});E2E Testing
// Playwright (Web)
test('switches language to Spanish', async ({ page }) => {
await page.goto('/');
// Open language selector
await page.click('[data-testid="language-button"]');
// Select Spanish
await page.click('[data-testid="lang-es"]');
// Verify translation
await expect(page.locator('h1')).toHaveText('Bienvenido');
});
// Detox (Mobile)
it('should switch language to Hebrew', async () => {
// Open settings
await element(by.id('settings-button')).tap();
// Select language
await element(by.id('language-selector')).tap();
await element(by.id('lang-he')).tap();
// Verify RTL
await expect(element(by.id('home-screen'))).toHaveValue('rtl');
});Related Documents
- Web Development Guide
- Mobile Development Guide
- tvOS Development Guide
- Accessibility Guide
- Shared Components Reference
Document Status: Complete Last Updated: 2026-02-12 Maintained by: Localization Team Next Review: 2026-04-30