0
# Internationalization
1
2
Comprehensive internationalization and localization system supporting 44+ languages with right-to-left (RTL) text direction, locale-specific formatting, and complete UI translation capabilities.
3
4
## Capabilities
5
6
### Locale Support
7
8
Built-in support for multiple languages with complete UI translations and locale-specific formatting.
9
10
```typescript { .api }
11
/**
12
* Locale configuration options
13
*/
14
interface LocaleOptions {
15
/** Current locale identifier */
16
locale?: string;
17
/** Fallback locale for missing translations */
18
fallback?: string;
19
/** Translation messages for locales */
20
messages?: Record<string, LocaleMessages>;
21
/** Adapter for external i18n libraries */
22
adapter?: LocaleAdapter;
23
}
24
25
interface LocaleMessages {
26
/** Nested message structure */
27
[key: string]: string | LocaleMessages;
28
}
29
30
interface LocaleAdapter {
31
/** Get translated message */
32
t: (key: string, ...params: unknown[]) => string;
33
/** Format number according to locale */
34
n: (value: number, options?: Intl.NumberFormatOptions) => string;
35
/** Get current locale */
36
current: string;
37
/** Set new locale */
38
setLocale: (locale: string) => void;
39
}
40
41
/**
42
* Locale instance from useLocale composable
43
*/
44
interface LocaleInstance {
45
/** Current active locale */
46
current: Ref<string>;
47
/** Fallback locale */
48
fallback: Ref<string>;
49
/** Available locale messages */
50
messages: Ref<Record<string, LocaleMessages>>;
51
/** Translation function */
52
t: (key: string, ...params: unknown[]) => string;
53
/** Number formatting function */
54
n: (value: number) => string;
55
/** Provider for component injection */
56
provide: Record<string, any>;
57
/** Right-to-left state */
58
isRtl: Ref<boolean>;
59
}
60
```
61
62
**Usage Examples:**
63
64
```vue
65
<template>
66
<div>
67
<!-- Language selector -->
68
<v-select
69
v-model="locale.current.value"
70
:items="availableLanguages"
71
:label="locale.t('language')"
72
prepend-icon="mdi-translate"
73
variant="outlined"
74
>
75
<template #item="{ props, item }">
76
<v-list-item v-bind="props">
77
<template #prepend>
78
<span class="fi" :class="`fi-${item.raw.flag}`"></span>
79
</template>
80
<v-list-item-title>{{ item.title }}</v-list-item-title>
81
<v-list-item-subtitle>{{ item.raw.native }}</v-list-item-subtitle>
82
</v-list-item>
83
</template>
84
</v-select>
85
86
<!-- Translated content -->
87
<v-card class="mt-4">
88
<v-card-title>{{ locale.t('welcome') }}</v-card-title>
89
<v-card-text>
90
<p>{{ locale.t('description') }}</p>
91
<p>{{ locale.t('userGreeting', userName) }}</p>
92
<p>{{ locale.t('itemCount', itemCount) }}</p>
93
94
<!-- Formatted numbers -->
95
<v-list>
96
<v-list-item>
97
<v-list-item-title>{{ locale.t('price') }}</v-list-item-title>
98
<v-list-item-subtitle>{{ locale.n(1234.56) }}</v-list-item-subtitle>
99
</v-list-item>
100
<v-list-item>
101
<v-list-item-title>{{ locale.t('quantity') }}</v-list-item-title>
102
<v-list-item-subtitle>{{ locale.n(42) }}</v-list-item-subtitle>
103
</v-list-item>
104
<v-list-item>
105
<v-list-item-title>{{ locale.t('percentage') }}</v-list-item-title>
106
<v-list-item-subtitle>{{ formatPercentage(0.75) }}</v-list-item-subtitle>
107
</v-list-item>
108
</v-list>
109
110
<!-- Date formatting -->
111
<v-divider class="my-4" />
112
<div class="date-formats">
113
<h4>{{ locale.t('dateFormats') }}</h4>
114
<p><strong>{{ locale.t('today') }}:</strong> {{ formatDate(new Date(), 'full') }}</p>
115
<p><strong>{{ locale.t('short') }}:</strong> {{ formatDate(new Date(), 'short') }}</p>
116
<p><strong>{{ locale.t('time') }}:</strong> {{ formatTime(new Date()) }}</p>
117
</div>
118
</v-card-text>
119
</v-card>
120
121
<!-- Form with translated labels and validation -->
122
<v-form class="mt-4">
123
<v-text-field
124
v-model="formData.name"
125
:label="locale.t('name')"
126
:rules="nameRules"
127
variant="outlined"
128
/>
129
130
<v-text-field
131
v-model="formData.email"
132
:label="locale.t('email')"
133
:rules="emailRules"
134
type="email"
135
variant="outlined"
136
/>
137
138
<v-textarea
139
v-model="formData.message"
140
:label="locale.t('message')"
141
:rules="messageRules"
142
variant="outlined"
143
/>
144
145
<v-btn color="primary" @click="submitForm">
146
{{ locale.t('submit') }}
147
</v-btn>
148
</v-form>
149
</div>
150
</template>
151
152
<script setup>
153
import { useLocale } from 'vuetify';
154
155
const locale = useLocale();
156
157
const userName = ref('John Doe');
158
const itemCount = ref(5);
159
160
const formData = reactive({
161
name: '',
162
email: '',
163
message: ''
164
});
165
166
const availableLanguages = [
167
{ value: 'en', title: 'English', native: 'English', flag: 'us' },
168
{ value: 'es', title: 'Spanish', native: 'Español', flag: 'es' },
169
{ value: 'fr', title: 'French', native: 'Français', flag: 'fr' },
170
{ value: 'de', title: 'German', native: 'Deutsch', flag: 'de' },
171
{ value: 'it', title: 'Italian', native: 'Italiano', flag: 'it' },
172
{ value: 'pt', title: 'Portuguese', native: 'Português', flag: 'pt' },
173
{ value: 'ru', title: 'Russian', native: 'Русский', flag: 'ru' },
174
{ value: 'zh', title: 'Chinese', native: '中文', flag: 'cn' },
175
{ value: 'ja', title: 'Japanese', native: '日本語', flag: 'jp' },
176
{ value: 'ar', title: 'Arabic', native: 'العربية', flag: 'sa' },
177
{ value: 'he', title: 'Hebrew', native: 'עברית', flag: 'il' },
178
{ value: 'hi', title: 'Hindi', native: 'हिन्दी', flag: 'in' }
179
];
180
181
// Validation rules that respect current locale
182
const nameRules = computed(() => [
183
v => !!v || locale.t('validation.required', locale.t('name')),
184
v => v.length >= 2 || locale.t('validation.minLength', locale.t('name'), 2)
185
]);
186
187
const emailRules = computed(() => [
188
v => !!v || locale.t('validation.required', locale.t('email')),
189
v => /.+@.+\..+/.test(v) || locale.t('validation.email')
190
]);
191
192
const messageRules = computed(() => [
193
v => !!v || locale.t('validation.required', locale.t('message')),
194
v => v.length >= 10 || locale.t('validation.minLength', locale.t('message'), 10)
195
]);
196
197
// Locale-aware formatting functions
198
const formatPercentage = (value) => {
199
return new Intl.NumberFormat(locale.current.value, {
200
style: 'percent',
201
minimumFractionDigits: 0,
202
maximumFractionDigits: 1
203
}).format(value);
204
};
205
206
const formatDate = (date, style = 'medium') => {
207
const options = {
208
full: { dateStyle: 'full' },
209
long: { dateStyle: 'long' },
210
medium: { dateStyle: 'medium' },
211
short: { dateStyle: 'short' }
212
};
213
214
return new Intl.DateTimeFormat(locale.current.value, options[style]).format(date);
215
};
216
217
const formatTime = (date) => {
218
return new Intl.DateTimeFormat(locale.current.value, {
219
timeStyle: 'short'
220
}).format(date);
221
};
222
223
const submitForm = () => {
224
console.log('Form submitted:', formData);
225
// Show success message in current locale
226
alert(locale.t('formSubmitted'));
227
};
228
229
// Initialize locale messages
230
onMounted(() => {
231
// Load locale-specific messages
232
loadLocaleMessages();
233
});
234
235
const loadLocaleMessages = () => {
236
// This would typically load from external files or API
237
const messages = {
238
en: {
239
language: 'Language',
240
welcome: 'Welcome to our application',
241
description: 'This is a fully internationalized application supporting multiple languages and locales.',
242
userGreeting: 'Hello, {0}!',
243
itemCount: 'You have {0} items',
244
price: 'Price',
245
quantity: 'Quantity',
246
percentage: 'Percentage',
247
dateFormats: 'Date Formats',
248
today: 'Today',
249
short: 'Short',
250
time: 'Time',
251
name: 'Name',
252
email: 'Email',
253
message: 'Message',
254
submit: 'Submit',
255
formSubmitted: 'Form submitted successfully!',
256
validation: {
257
required: '{0} is required',
258
minLength: '{0} must be at least {1} characters',
259
email: 'Please enter a valid email address'
260
}
261
},
262
es: {
263
language: 'Idioma',
264
welcome: 'Bienvenido a nuestra aplicación',
265
description: 'Esta es una aplicación completamente internacionalizada que admite múltiples idiomas y configuraciones regionales.',
266
userGreeting: '¡Hola, {0}!',
267
itemCount: 'Tienes {0} artículos',
268
price: 'Precio',
269
quantity: 'Cantidad',
270
percentage: 'Porcentaje',
271
dateFormats: 'Formatos de Fecha',
272
today: 'Hoy',
273
short: 'Corto',
274
time: 'Hora',
275
name: 'Nombre',
276
email: 'Correo electrónico',
277
message: 'Mensaje',
278
submit: 'Enviar',
279
formSubmitted: '¡Formulario enviado con éxito!',
280
validation: {
281
required: '{0} es requerido',
282
minLength: '{0} debe tener al menos {1} caracteres',
283
email: 'Por favor ingrese una dirección de correo válida'
284
}
285
},
286
ar: {
287
language: 'اللغة',
288
welcome: 'مرحباً بك في تطبيقنا',
289
description: 'هذا تطبيق مدوّل بالكامل يدعم لغات ومناطق متعددة.',
290
userGreeting: 'مرحباً، {0}!',
291
itemCount: 'لديك {0} عناصر',
292
price: 'السعر',
293
quantity: 'الكمية',
294
percentage: 'النسبة المئوية',
295
dateFormats: 'تنسيقات التاريخ',
296
today: 'اليوم',
297
short: 'مختصر',
298
time: 'الوقت',
299
name: 'الاسم',
300
email: 'البريد الإلكتروني',
301
message: 'الرسالة',
302
submit: 'إرسال',
303
formSubmitted: 'تم إرسال النموذج بنجاح!',
304
validation: {
305
required: '{0} مطلوب',
306
minLength: '{0} يجب أن يكون على الأقل {1} أحرف',
307
email: 'يرجى إدخال عنوان بريد إلكتروني صالح'
308
}
309
}
310
};
311
312
locale.messages.value = { ...locale.messages.value, ...messages };
313
};
314
</script>
315
316
<style>
317
/* Flag icons support */
318
.fi {
319
width: 20px;
320
height: 15px;
321
background-size: cover;
322
border-radius: 2px;
323
margin-right: 8px;
324
}
325
326
.date-formats {
327
background: rgba(0,0,0,0.02);
328
padding: 16px;
329
border-radius: 8px;
330
margin-top: 16px;
331
}
332
</style>
333
```
334
335
### RTL (Right-to-Left) Support
336
337
Comprehensive right-to-left text direction support for Arabic, Hebrew, and other RTL languages.
338
339
```typescript { .api }
340
/**
341
* RTL configuration options
342
*/
343
interface RtlOptions {
344
/** RTL configuration per locale */
345
rtl?: Record<string, boolean>;
346
}
347
348
/**
349
* RTL instance from useRtl composable
350
*/
351
interface RtlInstance {
352
/** Whether current locale uses RTL */
353
isRtl: Ref<boolean>;
354
/** RTL value as computed property */
355
rtl: Ref<boolean>;
356
/** RTL CSS classes */
357
rtlClasses: Ref<string>;
358
}
359
360
/**
361
* RTL-aware layout utilities
362
*/
363
interface RtlLayoutUtils {
364
/** Get start/end positioning for RTL */
365
getLogicalProps: (props: Record<string, any>) => Record<string, any>;
366
/** Convert physical directions to logical */
367
toLogicalDirection: (direction: PhysicalDirection) => LogicalDirection;
368
/** Get RTL-aware margin/padding */
369
getSpacing: (spacing: SpacingConfig) => Record<string, string>;
370
}
371
372
type PhysicalDirection = 'left' | 'right' | 'top' | 'bottom';
373
type LogicalDirection = 'start' | 'end' | 'top' | 'bottom';
374
375
interface SpacingConfig {
376
start?: string | number;
377
end?: string | number;
378
top?: string | number;
379
bottom?: string | number;
380
}
381
```
382
383
**Usage Examples:**
384
385
```vue
386
<template>
387
<div :dir="rtl.isRtl.value ? 'rtl' : 'ltr'" class="rtl-demo">
388
<!-- RTL toggle -->
389
<v-card class="mb-4">
390
<v-card-title class="d-flex align-center">
391
<v-icon class="me-2">mdi-translate</v-icon>
392
{{ rtl.isRtl.value ? 'اتجاه النص' : 'Text Direction' }}
393
<v-spacer />
394
<v-btn-toggle v-model="textDirection" mandatory>
395
<v-btn value="ltr" size="small">
396
{{ rtl.isRtl.value ? 'يسار إلى يمين' : 'LTR' }}
397
</v-btn>
398
<v-btn value="rtl" size="small">
399
{{ rtl.isRtl.value ? 'يمين إلى يسار' : 'RTL' }}
400
</v-btn>
401
</v-btn-toggle>
402
</v-card-title>
403
</v-card>
404
405
<!-- Navigation with RTL support -->
406
<v-app-bar :class="rtl.rtlClasses.value" color="primary">
407
<v-btn
408
:icon="rtl.isRtl.value ? 'mdi-menu-right' : 'mdi-menu'"
409
@click="drawer = !drawer"
410
/>
411
<v-toolbar-title>
412
{{ rtl.isRtl.value ? 'تطبيق ويب' : 'Web Application' }}
413
</v-toolbar-title>
414
<v-spacer />
415
<v-btn icon>
416
<v-icon>{{ rtl.isRtl.value ? 'mdi-account-circle' : 'mdi-account-circle' }}</v-icon>
417
</v-btn>
418
</v-app-bar>
419
420
<!-- Navigation drawer with RTL positioning -->
421
<v-navigation-drawer
422
v-model="drawer"
423
:location="rtl.isRtl.value ? 'right' : 'left'"
424
temporary
425
>
426
<v-list nav>
427
<v-list-item
428
v-for="item in navigationItems"
429
:key="item.id"
430
:prepend-icon="item.icon"
431
:title="item.title"
432
@click="navigateTo(item.route)"
433
/>
434
</v-list>
435
</v-navigation-drawer>
436
437
<!-- Content with RTL text alignment -->
438
<v-main class="pa-4">
439
<v-row>
440
<v-col cols="12" md="8">
441
<!-- Article content -->
442
<v-card>
443
<v-card-title :class="{ 'text-right': rtl.isRtl.value }">
444
{{ rtl.isRtl.value ? 'محتوى المقال' : 'Article Content' }}
445
</v-card-title>
446
<v-card-text>
447
<p :class="rtlTextClass">
448
{{ articleText }}
449
</p>
450
451
<!-- RTL-aware list -->
452
<v-list class="mt-4">
453
<v-list-subheader :class="{ 'text-right': rtl.isRtl.value }">
454
{{ rtl.isRtl.value ? 'العناصر' : 'Items' }}
455
</v-list-subheader>
456
<v-list-item
457
v-for="item in listItems"
458
:key="item.id"
459
:class="{ 'text-right': rtl.isRtl.value }"
460
>
461
<template #prepend>
462
<v-icon :class="rtl.isRtl.value ? 'ms-2' : 'me-2'">
463
{{ item.icon }}
464
</v-icon>
465
</template>
466
<v-list-item-title>{{ item.title }}</v-list-item-title>
467
<v-list-item-subtitle>{{ item.subtitle }}</v-list-item-subtitle>
468
</v-list-item>
469
</v-list>
470
</v-card-text>
471
</v-card>
472
</v-col>
473
474
<v-col cols="12" md="4">
475
<!-- Sidebar with RTL layout -->
476
<v-card>
477
<v-card-title :class="{ 'text-right': rtl.isRtl.value }">
478
{{ rtl.isRtl.value ? 'الشريط الجانبي' : 'Sidebar' }}
479
</v-card-title>
480
<v-card-text>
481
<!-- Form with RTL support -->
482
<v-form>
483
<v-text-field
484
v-model="searchQuery"
485
:label="rtl.isRtl.value ? 'البحث' : 'Search'"
486
:prepend-inner-icon="rtl.isRtl.value ? undefined : 'mdi-magnify'"
487
:append-inner-icon="rtl.isRtl.value ? 'mdi-magnify' : undefined"
488
variant="outlined"
489
:style="{ textAlign: rtl.isRtl.value ? 'right' : 'left' }"
490
/>
491
492
<v-select
493
v-model="selectedCategory"
494
:items="categories"
495
:label="rtl.isRtl.value ? 'الفئة' : 'Category'"
496
variant="outlined"
497
/>
498
499
<v-textarea
500
v-model="comments"
501
:label="rtl.isRtl.value ? 'التعليقات' : 'Comments'"
502
:style="{ textAlign: rtl.isRtl.value ? 'right' : 'left' }"
503
variant="outlined"
504
/>
505
</v-form>
506
507
<!-- Action buttons with RTL spacing -->
508
<div :class="rtlButtonClass">
509
<v-btn color="primary" :class="rtl.isRtl.value ? 'ml-2' : 'mr-2'">
510
{{ rtl.isRtl.value ? 'إرسال' : 'Submit' }}
511
</v-btn>
512
<v-btn variant="outlined">
513
{{ rtl.isRtl.value ? 'إلغاء' : 'Cancel' }}
514
</v-btn>
515
</div>
516
</v-card-text>
517
</v-card>
518
519
<!-- Tags with RTL flow -->
520
<v-card class="mt-4">
521
<v-card-title :class="{ 'text-right': rtl.isRtl.value }">
522
{{ rtl.isRtl.value ? 'العلامات' : 'Tags' }}
523
</v-card-title>
524
<v-card-text>
525
<div :class="rtlChipClass">
526
<v-chip
527
v-for="tag in tags"
528
:key="tag"
529
:class="rtl.isRtl.value ? 'ml-1 mb-1' : 'mr-1 mb-1'"
530
size="small"
531
>
532
{{ tag }}
533
</v-chip>
534
</div>
535
</v-card-text>
536
</v-card>
537
</v-col>
538
</v-row>
539
540
<!-- Data table with RTL support -->
541
<v-card class="mt-4">
542
<v-card-title :class="{ 'text-right': rtl.isRtl.value }">
543
{{ rtl.isRtl.value ? 'جدول البيانات' : 'Data Table' }}
544
</v-card-title>
545
<v-data-table
546
:headers="tableHeaders"
547
:items="tableItems"
548
:class="{ 'text-right': rtl.isRtl.value }"
549
/>
550
</v-card>
551
</v-main>
552
</div>
553
</template>
554
555
<script setup>
556
import { useRtl, useLocale } from 'vuetify';
557
558
const rtl = useRtl();
559
const locale = useLocale();
560
561
const textDirection = ref('ltr');
562
const drawer = ref(false);
563
const searchQuery = ref('');
564
const selectedCategory = ref('');
565
const comments = ref('');
566
567
// Watch text direction changes
568
watch(textDirection, (direction) => {
569
const newLocale = direction === 'rtl' ? 'ar' : 'en';
570
locale.current.value = newLocale;
571
});
572
573
const navigationItems = computed(() => [
574
{
575
id: 1,
576
title: rtl.isRtl.value ? 'الرئيسية' : 'Home',
577
icon: 'mdi-home',
578
route: '/'
579
},
580
{
581
id: 2,
582
title: rtl.isRtl.value ? 'المنتجات' : 'Products',
583
icon: 'mdi-package-variant',
584
route: '/products'
585
},
586
{
587
id: 3,
588
title: rtl.isRtl.value ? 'حول' : 'About',
589
icon: 'mdi-information',
590
route: '/about'
591
},
592
{
593
id: 4,
594
title: rtl.isRtl.value ? 'اتصل بنا' : 'Contact',
595
icon: 'mdi-phone',
596
route: '/contact'
597
}
598
]);
599
600
const articleText = computed(() =>
601
rtl.isRtl.value
602
? 'هذا مثال على النص العربي الذي يُكتب من اليمين إلى اليسار. يجب أن يظهر النص بشكل صحيح مع محاذاة مناسبة وتدفق طبيعي للقراءة. النظام يدعم جميع ميزات الـ RTL بما في ذلك ترتيب العناصر والأيقونات والأزرار.'
603
: 'This is an example of English text that reads from left to right. The text should display properly with appropriate alignment and natural reading flow. The system supports all RTL features including element ordering, icons, and buttons.'
604
);
605
606
const listItems = computed(() => [
607
{
608
id: 1,
609
title: rtl.isRtl.value ? 'العنصر الأول' : 'First Item',
610
subtitle: rtl.isRtl.value ? 'وصف العنصر الأول' : 'Description of first item',
611
icon: 'mdi-numeric-1-circle'
612
},
613
{
614
id: 2,
615
title: rtl.isRtl.value ? 'العنصر الثاني' : 'Second Item',
616
subtitle: rtl.isRtl.value ? 'وصف العنصر الثاني' : 'Description of second item',
617
icon: 'mdi-numeric-2-circle'
618
},
619
{
620
id: 3,
621
title: rtl.isRtl.value ? 'العنصر الثالث' : 'Third Item',
622
subtitle: rtl.isRtl.value ? 'وصف العنصر الثالث' : 'Description of third item',
623
icon: 'mdi-numeric-3-circle'
624
}
625
]);
626
627
const categories = computed(() =>
628
rtl.isRtl.value
629
? [
630
{ title: 'تقنية', value: 'tech' },
631
{ title: 'تصميم', value: 'design' },
632
{ title: 'تطوير', value: 'development' }
633
]
634
: [
635
{ title: 'Technology', value: 'tech' },
636
{ title: 'Design', value: 'design' },
637
{ title: 'Development', value: 'development' }
638
]
639
);
640
641
const tags = computed(() =>
642
rtl.isRtl.value
643
? ['تقنية', 'ويب', 'تطوير', 'تصميم', 'واجهة المستخدم']
644
: ['Technology', 'Web', 'Development', 'Design', 'UI']
645
);
646
647
const tableHeaders = computed(() => [
648
{
649
title: rtl.isRtl.value ? 'الاسم' : 'Name',
650
key: 'name',
651
align: rtl.isRtl.value ? 'end' : 'start'
652
},
653
{
654
title: rtl.isRtl.value ? 'العمر' : 'Age',
655
key: 'age',
656
align: rtl.isRtl.value ? 'end' : 'start'
657
},
658
{
659
title: rtl.isRtl.value ? 'المدينة' : 'City',
660
key: 'city',
661
align: rtl.isRtl.value ? 'end' : 'start'
662
}
663
]);
664
665
const tableItems = computed(() => [
666
{
667
name: rtl.isRtl.value ? 'أحمد علي' : 'Ahmed Ali',
668
age: 25,
669
city: rtl.isRtl.value ? 'الرياض' : 'Riyadh'
670
},
671
{
672
name: rtl.isRtl.value ? 'سارة محمد' : 'Sara Mohamed',
673
age: 30,
674
city: rtl.isRtl.value ? 'القاهرة' : 'Cairo'
675
},
676
{
677
name: rtl.isRtl.value ? 'محمد أحمد' : 'Mohamed Ahmed',
678
age: 28,
679
city: rtl.isRtl.value ? 'دبي' : 'Dubai'
680
}
681
]);
682
683
// RTL-aware CSS classes
684
const rtlTextClass = computed(() => ({
685
'text-right': rtl.isRtl.value,
686
'text-left': !rtl.isRtl.value
687
}));
688
689
const rtlButtonClass = computed(() => ({
690
'd-flex': true,
691
'justify-end': rtl.isRtl.value,
692
'justify-start': !rtl.isRtl.value,
693
'mt-4': true
694
}));
695
696
const rtlChipClass = computed(() => ({
697
'd-flex': true,
698
'flex-wrap': true,
699
'justify-end': rtl.isRtl.value,
700
'justify-start': !rtl.isRtl.value
701
}));
702
703
const navigateTo = (route) => {
704
console.log(`Navigating to: ${route}`);
705
drawer.value = false;
706
};
707
</script>
708
709
<style>
710
.rtl-demo {
711
min-height: 100vh;
712
}
713
714
/* RTL-specific overrides */
715
.v-locale--is-rtl .v-navigation-drawer--is-mobile.v-navigation-drawer--temporary.v-navigation-drawer--left {
716
right: 0;
717
left: auto;
718
}
719
720
.v-locale--is-rtl .v-list-item__prepend {
721
margin-inline-start: 0;
722
margin-inline-end: 16px;
723
}
724
725
.v-locale--is-rtl .v-data-table th,
726
.v-locale--is-rtl .v-data-table td {
727
text-align: right !important;
728
}
729
730
.v-locale--is-rtl input,
731
.v-locale--is-rtl textarea {
732
text-align: right;
733
}
734
735
/* Custom RTL utilities */
736
.ms-2 {
737
margin-inline-start: 8px !important;
738
}
739
740
.me-2 {
741
margin-inline-end: 8px !important;
742
}
743
744
.ml-1 {
745
margin-left: 4px !important;
746
}
747
748
.mr-1 {
749
margin-right: 4px !important;
750
}
751
752
.ml-2 {
753
margin-left: 8px !important;
754
}
755
756
.mr-2 {
757
margin-right: 8px !important;
758
}
759
</style>
760
```
761
762
### Supported Languages
763
764
Complete list of the 44+ supported languages with their locale codes and characteristics.
765
766
```typescript { .api }
767
/**
768
* Supported language configurations
769
*/
770
interface SupportedLanguages {
771
/** Afrikaans */
772
af: LocaleConfig;
773
/** Arabic */
774
ar: LocaleConfig;
775
/** Azerbaijani */
776
az: LocaleConfig;
777
/** Bulgarian */
778
bg: LocaleConfig;
779
/** Catalan */
780
ca: LocaleConfig;
781
/** Central Kurdish */
782
ckb: LocaleConfig;
783
/** Czech */
784
cs: LocaleConfig;
785
/** Danish */
786
da: LocaleConfig;
787
/** German */
788
de: LocaleConfig;
789
/** Greek */
790
el: LocaleConfig;
791
/** English */
792
en: LocaleConfig;
793
/** Spanish */
794
es: LocaleConfig;
795
/** Estonian */
796
et: LocaleConfig;
797
/** Persian */
798
fa: LocaleConfig;
799
/** Finnish */
800
fi: LocaleConfig;
801
/** French */
802
fr: LocaleConfig;
803
/** Hebrew */
804
he: LocaleConfig;
805
/** Croatian */
806
hr: LocaleConfig;
807
/** Hungarian */
808
hu: LocaleConfig;
809
/** Indonesian */
810
id: LocaleConfig;
811
/** Italian */
812
it: LocaleConfig;
813
/** Japanese */
814
ja: LocaleConfig;
815
/** Khmer */
816
km: LocaleConfig;
817
/** Korean */
818
ko: LocaleConfig;
819
/** Lithuanian */
820
lt: LocaleConfig;
821
/** Latvian */
822
lv: LocaleConfig;
823
/** Dutch */
824
nl: LocaleConfig;
825
/** Norwegian */
826
no: LocaleConfig;
827
/** Polish */
828
pl: LocaleConfig;
829
/** Portuguese */
830
pt: LocaleConfig;
831
/** Romanian */
832
ro: LocaleConfig;
833
/** Russian */
834
ru: LocaleConfig;
835
/** Slovak */
836
sk: LocaleConfig;
837
/** Slovenian */
838
sl: LocaleConfig;
839
/** Serbian Cyrillic */
840
srCyrl: LocaleConfig;
841
/** Serbian Latin */
842
srLatn: LocaleConfig;
843
/** Swedish */
844
sv: LocaleConfig;
845
/** Thai */
846
th: LocaleConfig;
847
/** Turkish */
848
tr: LocaleConfig;
849
/** Ukrainian */
850
uk: LocaleConfig;
851
/** Vietnamese */
852
vi: LocaleConfig;
853
/** Chinese Simplified */
854
zhHans: LocaleConfig;
855
/** Chinese Traditional */
856
zhHant: LocaleConfig;
857
}
858
859
interface LocaleConfig {
860
/** Language code */
861
code: string;
862
/** Display name in English */
863
name: string;
864
/** Display name in native language */
865
nativeName: string;
866
/** Right-to-left text direction */
867
rtl: boolean;
868
/** Plural rules function */
869
plural: (n: number) => number;
870
/** Date/time formatting options */
871
dateTime: DateTimeFormatOptions;
872
/** Number formatting options */
873
number: NumberFormatOptions;
874
}
875
876
interface DateTimeFormatOptions {
877
/** Date format patterns */
878
dateFormats: {
879
full: string;
880
long: string;
881
medium: string;
882
short: string;
883
};
884
/** Time format patterns */
885
timeFormats: {
886
full: string;
887
long: string;
888
medium: string;
889
short: string;
890
};
891
/** First day of week (0 = Sunday, 1 = Monday) */
892
firstDayOfWeek: number;
893
/** Weekend days */
894
weekendDays: number[];
895
}
896
897
interface NumberFormatOptions {
898
/** Decimal separator */
899
decimal: string;
900
/** Thousands separator */
901
thousands: string;
902
/** Currency formatting */
903
currency: {
904
/** Currency code */
905
code: string;
906
/** Currency symbol */
907
symbol: string;
908
/** Symbol position */
909
position: 'before' | 'after';
910
};
911
}
912
```
913
914
**Usage Examples:**
915
916
```javascript
917
// Language configuration examples
918
const supportedLanguages = {
919
// English (Left-to-Right)
920
en: {
921
code: 'en',
922
name: 'English',
923
nativeName: 'English',
924
rtl: false,
925
plural: (n) => n === 1 ? 0 : 1,
926
dateTime: {
927
dateFormats: {
928
full: 'EEEE, MMMM d, y',
929
long: 'MMMM d, y',
930
medium: 'MMM d, y',
931
short: 'M/d/yy'
932
},
933
timeFormats: {
934
full: 'h:mm:ss a zzzz',
935
long: 'h:mm:ss a z',
936
medium: 'h:mm:ss a',
937
short: 'h:mm a'
938
},
939
firstDayOfWeek: 0, // Sunday
940
weekendDays: [0, 6] // Sunday, Saturday
941
},
942
number: {
943
decimal: '.',
944
thousands: ',',
945
currency: {
946
code: 'USD',
947
symbol: '$',
948
position: 'before'
949
}
950
}
951
},
952
953
// Arabic (Right-to-Left)
954
ar: {
955
code: 'ar',
956
name: 'Arabic',
957
nativeName: 'العربية',
958
rtl: true,
959
plural: (n) => {
960
// Arabic plural rules (complex)
961
if (n === 0) return 0;
962
if (n === 1) return 1;
963
if (n === 2) return 2;
964
if (n % 100 >= 3 && n % 100 <= 10) return 3;
965
if (n % 100 >= 11) return 4;
966
return 5;
967
},
968
dateTime: {
969
dateFormats: {
970
full: 'EEEE، d MMMM y',
971
long: 'd MMMM y',
972
medium: 'dd/MM/y',
973
short: 'd/M/y'
974
},
975
timeFormats: {
976
full: 'h:mm:ss a zzzz',
977
long: 'h:mm:ss a z',
978
medium: 'h:mm:ss a',
979
short: 'h:mm a'
980
},
981
firstDayOfWeek: 6, // Saturday
982
weekendDays: [5, 6] // Friday, Saturday
983
},
984
number: {
985
decimal: '٫',
986
thousands: '٬',
987
currency: {
988
code: 'SAR',
989
symbol: 'ر.س',
990
position: 'after'
991
}
992
}
993
},
994
995
// Chinese Simplified
996
zhHans: {
997
code: 'zh-Hans',
998
name: 'Chinese Simplified',
999
nativeName: '中文(简体)',
1000
rtl: false,
1001
plural: (n) => 0, // Chinese has no plural forms
1002
dateTime: {
1003
dateFormats: {
1004
full: 'y年M月d日EEEE',
1005
long: 'y年M月d日',
1006
medium: 'y年M月d日',
1007
short: 'y/M/d'
1008
},
1009
timeFormats: {
1010
full: 'zzzz ah:mm:ss',
1011
long: 'z ah:mm:ss',
1012
medium: 'ah:mm:ss',
1013
short: 'ah:mm'
1014
},
1015
firstDayOfWeek: 1, // Monday
1016
weekendDays: [0, 6] // Sunday, Saturday
1017
},
1018
number: {
1019
decimal: '.',
1020
thousands: ',',
1021
currency: {
1022
code: 'CNY',
1023
symbol: '¥',
1024
position: 'before'
1025
}
1026
}
1027
},
1028
1029
// Hebrew (Right-to-Left)
1030
he: {
1031
code: 'he',
1032
name: 'Hebrew',
1033
nativeName: 'עברית',
1034
rtl: true,
1035
plural: (n) => n === 1 ? 0 : 1,
1036
dateTime: {
1037
dateFormats: {
1038
full: 'EEEE, d בMMMM y',
1039
long: 'd בMMMM y',
1040
medium: 'd בMMM y',
1041
short: 'd.M.y'
1042
},
1043
timeFormats: {
1044
full: 'H:mm:ss zzzz',
1045
long: 'H:mm:ss z',
1046
medium: 'H:mm:ss',
1047
short: 'H:mm'
1048
},
1049
firstDayOfWeek: 0, // Sunday
1050
weekendDays: [5, 6] // Friday, Saturday
1051
},
1052
number: {
1053
decimal: '.',
1054
thousands: ',',
1055
currency: {
1056
code: 'ILS',
1057
symbol: '₪',
1058
position: 'before'
1059
}
1060
}
1061
}
1062
};
1063
1064
// Vuetify configuration with multiple locales
1065
import { createVuetify } from 'vuetify';
1066
import { en, ar, zhHans, he } from 'vuetify/locale';
1067
1068
const vuetify = createVuetify({
1069
locale: {
1070
locale: 'en',
1071
fallback: 'en',
1072
messages: { en, ar, zhHans, he },
1073
rtl: {
1074
ar: true,
1075
he: true,
1076
fa: true,
1077
ckb: true
1078
}
1079
}
1080
});
1081
1082
// Dynamic locale loading
1083
const loadLocale = async (localeCode) => {
1084
try {
1085
// Load Vuetify translations
1086
const vuetifyMessages = await import(`vuetify/locale/${localeCode}`);
1087
1088
// Load application translations
1089
const appMessages = await import(`../locales/${localeCode}.js`);
1090
1091
// Merge messages
1092
return {
1093
...vuetifyMessages.default,
1094
...appMessages.default
1095
};
1096
} catch (error) {
1097
console.warn(`Failed to load locale: ${localeCode}`, error);
1098
return null;
1099
}
1100
};
1101
1102
// Usage in application
1103
export const useInternationalization = () => {
1104
const locale = useLocale();
1105
const rtl = useRtl();
1106
1107
const changeLanguage = async (newLocale) => {
1108
const messages = await loadLocale(newLocale);
1109
if (messages) {
1110
locale.messages.value[newLocale] = messages;
1111
locale.current.value = newLocale;
1112
1113
// Update document attributes
1114
document.documentElement.lang = newLocale;
1115
document.documentElement.dir = rtl.isRtl.value ? 'rtl' : 'ltr';
1116
}
1117
};
1118
1119
return {
1120
locale,
1121
rtl,
1122
changeLanguage,
1123
supportedLanguages: Object.keys(supportedLanguages)
1124
};
1125
};
1126
```
1127
1128
## Types
1129
1130
```typescript { .api }
1131
// Core internationalization types
1132
type LocaleCode = string;
1133
type LocaleKey = string;
1134
type TranslationMessage = string;
1135
1136
// Locale configuration
1137
interface LocaleConfiguration {
1138
locale: LocaleCode;
1139
fallback: LocaleCode;
1140
messages: Record<LocaleCode, LocaleMessages>;
1141
adapter?: LocaleAdapter;
1142
rtl?: Record<LocaleCode, boolean>;
1143
}
1144
1145
// Message interpolation
1146
type MessageParameters = Array<string | number>;
1147
interface MessageContext {
1148
locale: LocaleCode;
1149
key: LocaleKey;
1150
parameters: MessageParameters;
1151
}
1152
1153
// Pluralization
1154
type PluralRule = (count: number, locale: LocaleCode) => number;
1155
interface PluralRules {
1156
[locale: string]: PluralRule;
1157
}
1158
1159
// Date and time formatting
1160
interface DateTimeLocaleConfig {
1161
calendar: CalendarType;
1162
numberingSystem: NumberingSystem;
1163
timeZone: string;
1164
}
1165
1166
type CalendarType = 'gregory' | 'islamic' | 'hebrew' | 'persian' | 'buddhist';
1167
type NumberingSystem = 'latn' | 'arab' | 'deva' | 'thai' | 'hanidec';
1168
1169
// RTL configuration
1170
interface RtlConfiguration {
1171
locales: Record<LocaleCode, boolean>;
1172
autoDetect: boolean;
1173
fallbackDirection: 'ltr' | 'rtl';
1174
}
1175
1176
// Formatting options
1177
interface LocaleFormatOptions {
1178
date: Intl.DateTimeFormatOptions;
1179
time: Intl.DateTimeFormatOptions;
1180
number: Intl.NumberFormatOptions;
1181
currency: Intl.NumberFormatOptions;
1182
percent: Intl.NumberFormatOptions;
1183
}
1184
1185
// Locale metadata
1186
interface LocaleMetadata {
1187
code: LocaleCode;
1188
name: string;
1189
nativeName: string;
1190
region: string;
1191
script: string;
1192
direction: 'ltr' | 'rtl';
1193
pluralRules: string;
1194
territories: string[];
1195
}
1196
```