or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

components.mdcomposables.mddata-display.mddirectives.mdfeedback.mdforms.mdframework-core.mdicons.mdindex.mdinternationalization.mdlab-components.mdnavigation.mdtheming.mdtransitions.mdutilities.md

internationalization.mddocs/

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

```