0
# Internationalization
1
2
React Admin provides comprehensive internationalization (i18n) support for building multi-language admin applications. The system handles UI translations, locale-specific formatting, right-to-left (RTL) languages, and translatable content management.
3
4
## i18n Provider Interface
5
6
The i18n provider defines how your application handles translations and locale management.
7
8
```typescript { .api }
9
import { I18nProvider } from 'react-admin';
10
11
interface I18nProvider {
12
translate: (key: string, options?: TranslateOptions) => string;
13
changeLocale: (locale: string) => Promise<void>;
14
getLocale: () => string;
15
getLocales?: () => Locale[];
16
}
17
18
interface TranslateOptions {
19
smart_count?: number;
20
_?: string;
21
[key: string]: any;
22
}
23
24
interface Locale {
25
locale: string;
26
name: string;
27
}
28
```
29
30
### Basic i18n Provider Example
31
32
```typescript
33
import polyglotI18nProvider from 'ra-i18n-polyglot';
34
import englishMessages from 'ra-language-english';
35
import frenchMessages from 'ra-language-french';
36
import spanishMessages from 'ra-language-spanish';
37
38
const messages = {
39
en: englishMessages,
40
fr: frenchMessages,
41
es: spanishMessages,
42
};
43
44
const i18nProvider = polyglotI18nProvider(
45
locale => messages[locale],
46
'en', // default locale
47
[
48
{ locale: 'en', name: 'English' },
49
{ locale: 'fr', name: 'Français' },
50
{ locale: 'es', name: 'Español' }
51
]
52
);
53
54
// Usage in Admin
55
<Admin
56
dataProvider={dataProvider}
57
i18nProvider={i18nProvider}
58
>
59
<Resource name="posts" list={PostList} />
60
</Admin>
61
```
62
63
## Translation Hooks
64
65
### useTranslate
66
67
Hook for translating messages in components.
68
69
```typescript { .api }
70
import { useTranslate } from 'react-admin';
71
72
type TranslateFunction = (key: string, options?: TranslateOptions) => string;
73
74
const useTranslate: () => TranslateFunction;
75
```
76
77
#### Usage Examples
78
79
```typescript
80
import { useTranslate } from 'react-admin';
81
82
const MyComponent = () => {
83
const translate = useTranslate();
84
85
return (
86
<div>
87
<h1>{translate('page.dashboard.title')}</h1>
88
<p>{translate('page.dashboard.welcome', { name: 'John' })}</p>
89
<p>{translate('post.count', { smart_count: 5 })}</p>
90
</div>
91
);
92
};
93
94
// Message files:
95
// en.json: { "page.dashboard.title": "Dashboard", "page.dashboard.welcome": "Welcome, %{name}!", "post.count": "One post |||| %{smart_count} posts" }
96
// fr.json: { "page.dashboard.title": "Tableau de bord", "page.dashboard.welcome": "Bienvenue, %{name} !", "post.count": "Un article |||| %{smart_count} articles" }
97
```
98
99
### useTranslateLabel
100
101
Hook for translating field and resource labels.
102
103
```typescript { .api }
104
import { useTranslateLabel } from 'react-admin';
105
106
const useTranslateLabel: () => (params: TranslateLabelParams) => string;
107
108
interface TranslateLabelParams {
109
source?: string;
110
resource?: string;
111
label?: string;
112
}
113
```
114
115
#### Usage Example
116
117
```typescript
118
import { useTranslateLabel } from 'react-admin';
119
120
const CustomField = ({ source, resource, label }) => {
121
const translateLabel = useTranslateLabel();
122
123
const fieldLabel = translateLabel({ source, resource, label });
124
125
return (
126
<div>
127
<label>{fieldLabel}</label>
128
{/* field content */}
129
</div>
130
);
131
};
132
```
133
134
## Locale Management
135
136
### useLocale & useSetLocale
137
138
Hooks for getting and setting the current locale.
139
140
```typescript { .api }
141
import { useLocale, useSetLocale } from 'react-admin';
142
143
const useLocale: () => string;
144
const useSetLocale: () => (locale: string) => Promise<void>;
145
```
146
147
### useLocales
148
149
Hook for getting available locales.
150
151
```typescript { .api }
152
import { useLocales } from 'react-admin';
153
154
const useLocales: () => Locale[] | undefined;
155
```
156
157
### Locale Management Examples
158
159
```typescript
160
import { useLocale, useSetLocale, useLocales } from 'react-admin';
161
162
const LanguageSwitcher = () => {
163
const locale = useLocale();
164
const setLocale = useSetLocale();
165
const locales = useLocales();
166
167
const handleLocaleChange = (newLocale) => {
168
setLocale(newLocale);
169
};
170
171
return (
172
<select value={locale} onChange={(e) => handleLocaleChange(e.target.value)}>
173
{locales?.map(locale => (
174
<option key={locale.locale} value={locale.locale}>
175
{locale.name}
176
</option>
177
))}
178
</select>
179
);
180
};
181
182
// Using LocalesMenuButton component
183
import { LocalesMenuButton } from 'react-admin';
184
185
const AppBar = () => (
186
<AppBar>
187
<TitlePortal />
188
<Box flex="1" />
189
<LocalesMenuButton />
190
<UserMenu />
191
</AppBar>
192
);
193
```
194
195
## Translatable Content
196
197
### TranslatableInputs
198
199
Component for managing translatable form inputs.
200
201
```typescript { .api }
202
import { TranslatableInputs } from 'react-admin';
203
204
interface TranslatableInputsProps {
205
locales?: string[];
206
defaultLocale?: string;
207
selector?: React.ComponentType<TranslatableInputsTabsProps>;
208
children: React.ReactNode;
209
className?: string;
210
sx?: any;
211
}
212
213
const TranslatableInputs: React.FC<TranslatableInputsProps>;
214
```
215
216
#### TranslatableInputs Example
217
218
```typescript
219
import {
220
TranslatableInputs,
221
TextInput,
222
Edit,
223
SimpleForm
224
} from 'react-admin';
225
226
const PostEdit = () => (
227
<Edit>
228
<SimpleForm>
229
<TextInput source="id" disabled />
230
231
<TranslatableInputs locales={['en', 'fr', 'es']}>
232
<TextInput source="title" validate={required()} />
233
<TextInput
234
source="content"
235
multiline
236
rows={4}
237
validate={required()}
238
/>
239
<TextInput source="metaDescription" />
240
</TranslatableInputs>
241
242
<DateInput source="publishedAt" />
243
</SimpleForm>
244
</Edit>
245
);
246
247
// Data structure:
248
// {
249
// id: 1,
250
// title: { en: "Hello", fr: "Bonjour", es: "Hola" },
251
// content: { en: "Content", fr: "Contenu", es: "Contenido" },
252
// publishedAt: "2024-01-15"
253
// }
254
```
255
256
### TranslatableFields
257
258
Component for displaying translatable content in show/list views.
259
260
```typescript { .api }
261
import { TranslatableFields } from 'react-admin';
262
263
interface TranslatableFieldsProps {
264
locales?: string[];
265
selector?: React.ComponentType<TranslatableFieldsTabsProps>;
266
children: React.ReactNode;
267
className?: string;
268
sx?: any;
269
}
270
271
const TranslatableFields: React.FC<TranslatableFieldsProps>;
272
```
273
274
#### TranslatableFields Example
275
276
```typescript
277
import {
278
TranslatableFields,
279
TextField,
280
Show,
281
SimpleShowLayout
282
} from 'react-admin';
283
284
const PostShow = () => (
285
<Show>
286
<SimpleShowLayout>
287
<TextField source="id" />
288
289
<TranslatableFields locales={['en', 'fr', 'es']}>
290
<TextField source="title" />
291
<TextField source="content" />
292
<TextField source="metaDescription" />
293
</TranslatableFields>
294
295
<DateField source="publishedAt" />
296
</SimpleShowLayout>
297
</Show>
298
);
299
```
300
301
### Translatable Context and Hooks
302
303
```typescript { .api }
304
import {
305
useTranslatable,
306
useTranslatableContext,
307
TranslatableContext,
308
TranslatableContextProvider
309
} from 'react-admin';
310
311
interface TranslatableContextValue {
312
locale: string;
313
locales: string[];
314
selectLocale: (locale: string) => void;
315
getSource: (source: string) => string;
316
getLabel: (source: string) => string;
317
}
318
319
const useTranslatable: (options?: UseTranslatableOptions) => TranslatableContextValue;
320
const useTranslatableContext: () => TranslatableContextValue;
321
```
322
323
## Message Structure and Patterns
324
325
### Resource and Field Labels
326
327
React Admin follows specific conventions for translation keys:
328
329
```typescript
330
// Translation message structure
331
const messages = {
332
resources: {
333
posts: {
334
name: 'Post |||| Posts',
335
fields: {
336
title: 'Title',
337
content: 'Content',
338
publishedAt: 'Published At',
339
author: 'Author'
340
}
341
},
342
users: {
343
name: 'User |||| Users',
344
fields: {
345
firstName: 'First Name',
346
lastName: 'Last Name',
347
email: 'Email Address'
348
}
349
}
350
},
351
ra: {
352
action: {
353
save: 'Save',
354
delete: 'Delete',
355
cancel: 'Cancel',
356
edit: 'Edit',
357
show: 'Show',
358
create: 'Create'
359
},
360
boolean: {
361
true: 'Yes',
362
false: 'No'
363
},
364
page: {
365
list: 'List of %{name}',
366
edit: 'Edit %{name} #%{id}',
367
show: '%{name} #%{id}',
368
create: 'Create %{name}',
369
dashboard: 'Dashboard'
370
},
371
input: {
372
file: {
373
upload_several: 'Drop some files to upload, or click to select one.',
374
upload_single: 'Drop a file to upload, or click to select it.'
375
},
376
image: {
377
upload_several: 'Drop some pictures to upload, or click to select one.',
378
upload_single: 'Drop a picture to upload, or click to select it.'
379
}
380
},
381
message: {
382
yes: 'Yes',
383
no: 'No',
384
are_you_sure: 'Are you sure?',
385
about: 'About',
386
not_found: 'Either you typed a wrong URL, or you followed a bad link.'
387
},
388
navigation: {
389
no_results: 'No results found',
390
no_more_results: 'The page number %{page} is out of boundaries. Try the previous page.',
391
page_out_of_boundaries: 'Page number %{page} out of boundaries',
392
page_out_from_end: 'Cannot go after last page',
393
page_out_from_begin: 'Cannot go before page 1',
394
page_range_info: '%{offsetBegin}-%{offsetEnd} of %{total}',
395
page_rows_per_page: 'Rows per page:',
396
next: 'Next',
397
prev: 'Previous'
398
},
399
notification: {
400
updated: 'Element updated |||| %{smart_count} elements updated',
401
created: 'Element created',
402
deleted: 'Element deleted |||| %{smart_count} elements deleted',
403
bad_item: 'Incorrect element',
404
item_doesnt_exist: 'Element does not exist',
405
http_error: 'Server communication error',
406
data_provider_error: 'dataProvider error. Check the console for details.',
407
i18n_error: 'Cannot load the translations for the specified language',
408
canceled: 'Action cancelled',
409
logged_out: 'Your session has ended, please reconnect.'
410
},
411
validation: {
412
required: 'Required',
413
minLength: 'Must be %{min} characters at least',
414
maxLength: 'Must be %{max} characters or less',
415
minValue: 'Must be at least %{min}',
416
maxValue: 'Must be %{max} or less',
417
number: 'Must be a number',
418
email: 'Must be a valid email',
419
oneOf: 'Must be one of: %{options}',
420
regex: 'Must match a specific format (regexp): %{pattern}'
421
}
422
}
423
};
424
```
425
426
### Custom Message Keys
427
428
```typescript
429
// Custom application messages
430
const customMessages = {
431
// Custom page titles
432
page: {
433
analytics: 'Analytics Dashboard',
434
reports: 'Reports',
435
settings: 'Application Settings'
436
},
437
438
// Custom actions
439
action: {
440
publish: 'Publish',
441
unpublish: 'Unpublish',
442
archive: 'Archive',
443
duplicate: 'Duplicate',
444
export_pdf: 'Export to PDF'
445
},
446
447
// Custom notifications
448
notification: {
449
post_published: 'Post published successfully',
450
export_started: 'Export started. You will be notified when complete.',
451
bulk_update_success: '%{smart_count} item updated |||| %{smart_count} items updated'
452
},
453
454
// Status messages
455
status: {
456
draft: 'Draft',
457
published: 'Published',
458
archived: 'Archived',
459
pending: 'Pending Review'
460
}
461
};
462
```
463
464
## Advanced i18n Features
465
466
### Pluralization
467
468
React Admin supports pluralization using the Polyglot.js format:
469
470
```typescript
471
const messages = {
472
post: {
473
count: 'One post |||| %{smart_count} posts',
474
notification: {
475
deleted: 'Post deleted |||| %{smart_count} posts deleted'
476
}
477
}
478
};
479
480
// Usage
481
const translate = useTranslate();
482
const message1 = translate('post.count', { smart_count: 1 }); // "One post"
483
const message5 = translate('post.count', { smart_count: 5 }); // "5 posts"
484
```
485
486
### Interpolation
487
488
Variable interpolation in translations:
489
490
```typescript
491
const messages = {
492
welcome: 'Welcome back, %{name}!',
493
last_login: 'Last login: %{date} at %{time}',
494
notification: 'You have %{count} new %{type}'
495
};
496
497
// Usage
498
const translate = useTranslate();
499
const welcome = translate('welcome', { name: 'John Doe' });
500
const login = translate('last_login', {
501
date: '2024-01-15',
502
time: '14:30'
503
});
504
```
505
506
### RTL Language Support
507
508
```typescript
509
import { Admin } from 'react-admin';
510
import { createTheme } from '@mui/material/styles';
511
512
// RTL theme configuration
513
const rtlTheme = createTheme({
514
direction: 'rtl',
515
palette: {
516
primary: { main: '#1976d2' }
517
}
518
});
519
520
const App = () => {
521
const [locale, setLocale] = useState('en');
522
const theme = locale === 'ar' ? rtlTheme : defaultTheme;
523
524
return (
525
<Admin
526
theme={theme}
527
i18nProvider={i18nProvider}
528
dataProvider={dataProvider}
529
>
530
<Resource name="posts" list={PostList} />
531
</Admin>
532
);
533
};
534
```
535
536
### Dynamic Language Loading
537
538
```typescript
539
const i18nProvider = polyglotI18nProvider(
540
async (locale) => {
541
if (locale === 'en') {
542
return import('ra-language-english').then(messages => messages.default);
543
}
544
if (locale === 'fr') {
545
return import('ra-language-french').then(messages => messages.default);
546
}
547
// Fallback to English
548
return import('ra-language-english').then(messages => messages.default);
549
},
550
'en'
551
);
552
```
553
554
### Custom Translation Component
555
556
```typescript
557
import { useTranslate } from 'react-admin';
558
559
const T = ({ message, values, children, ...props }) => {
560
const translate = useTranslate();
561
const translatedText = translate(message, values);
562
563
if (children) {
564
return children(translatedText);
565
}
566
567
return <span {...props}>{translatedText}</span>;
568
};
569
570
// Usage
571
<T message="welcome.title" values={{ name: 'John' }} />
572
573
<T message="post.count" values={{ smart_count: 5 }}>
574
{(text) => <strong>{text}</strong>}
575
</T>
576
```
577
578
## Testing i18n
579
580
### Test Translation Provider
581
582
```typescript
583
import { TestTranslationProvider } from 'react-admin';
584
585
const TestComponent = () => (
586
<TestTranslationProvider messages={{ 'test.message': 'Test Message' }}>
587
<MyComponent />
588
</TestTranslationProvider>
589
);
590
```
591
592
### Mock i18n in Tests
593
594
```typescript
595
const mockTranslate = (key, options = {}) => {
596
// Simple mock that returns the key with interpolated values
597
return key.replace(/%\{(\w+)\}/g, (match, param) => options[param] || match);
598
};
599
600
jest.mock('react-admin', () => ({
601
...jest.requireActual('react-admin'),
602
useTranslate: () => mockTranslate
603
}));
604
```
605
606
## Complete i18n Example
607
608
```typescript
609
import { Admin, Resource, List, Edit, Create, SimpleForm, TextInput } from 'react-admin';
610
import polyglotI18nProvider from 'ra-i18n-polyglot';
611
import englishMessages from 'ra-language-english';
612
import frenchMessages from 'ra-language-french';
613
614
// Custom messages
615
const customEnglishMessages = {
616
...englishMessages,
617
resources: {
618
posts: {
619
name: 'Post |||| Posts',
620
fields: {
621
title: 'Title',
622
content: 'Content',
623
status: 'Publication Status'
624
}
625
}
626
},
627
custom: {
628
actions: {
629
publish: 'Publish Post',
630
schedule: 'Schedule Publication'
631
},
632
status: {
633
draft: 'Draft',
634
published: 'Published',
635
scheduled: 'Scheduled'
636
}
637
}
638
};
639
640
const customFrenchMessages = {
641
...frenchMessages,
642
resources: {
643
posts: {
644
name: 'Article |||| Articles',
645
fields: {
646
title: 'Titre',
647
content: 'Contenu',
648
status: 'Statut de publication'
649
}
650
}
651
},
652
custom: {
653
actions: {
654
publish: 'Publier l\'article',
655
schedule: 'Programmer la publication'
656
},
657
status: {
658
draft: 'Brouillon',
659
published: 'Publié',
660
scheduled: 'Programmé'
661
}
662
}
663
};
664
665
const messages = {
666
en: customEnglishMessages,
667
fr: customFrenchMessages
668
};
669
670
const i18nProvider = polyglotI18nProvider(
671
locale => messages[locale],
672
'en',
673
[
674
{ locale: 'en', name: 'English' },
675
{ locale: 'fr', name: 'Français' }
676
]
677
);
678
679
const PostEdit = () => (
680
<Edit>
681
<SimpleForm>
682
<TranslatableInputs locales={['en', 'fr']}>
683
<TextInput source="title" />
684
<TextInput source="content" multiline rows={4} />
685
</TranslatableInputs>
686
</SimpleForm>
687
</Edit>
688
);
689
690
const App = () => (
691
<Admin
692
dataProvider={dataProvider}
693
i18nProvider={i18nProvider}
694
>
695
<Resource name="posts" list={PostList} edit={PostEdit} />
696
</Admin>
697
);
698
```
699
700
React Admin's internationalization system provides comprehensive multi-language support with flexible translation management, locale-specific formatting, and robust tools for building global admin applications.