CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vuetify

Vue Material Component Framework implementing Google's Material Design specification with comprehensive UI components, theming system, and accessibility features.

Pending
Overview
Eval results
Files

internationalization.mddocs/

Internationalization

Comprehensive internationalization and localization system supporting 44+ languages with right-to-left (RTL) text direction, locale-specific formatting, and complete UI translation capabilities.

Capabilities

Locale Support

Built-in support for multiple languages with complete UI translations and locale-specific formatting.

/**
 * Locale configuration options
 */
interface LocaleOptions {
  /** Current locale identifier */
  locale?: string;
  /** Fallback locale for missing translations */
  fallback?: string;
  /** Translation messages for locales */
  messages?: Record<string, LocaleMessages>;
  /** Adapter for external i18n libraries */
  adapter?: LocaleAdapter;
}

interface LocaleMessages {
  /** Nested message structure */
  [key: string]: string | LocaleMessages;
}

interface LocaleAdapter {
  /** Get translated message */
  t: (key: string, ...params: unknown[]) => string;
  /** Format number according to locale */
  n: (value: number, options?: Intl.NumberFormatOptions) => string;
  /** Get current locale */
  current: string;
  /** Set new locale */
  setLocale: (locale: string) => void;
}

/**
 * Locale instance from useLocale composable
 */
interface LocaleInstance {
  /** Current active locale */
  current: Ref<string>;
  /** Fallback locale */
  fallback: Ref<string>;
  /** Available locale messages */
  messages: Ref<Record<string, LocaleMessages>>;
  /** Translation function */
  t: (key: string, ...params: unknown[]) => string;
  /** Number formatting function */
  n: (value: number) => string;
  /** Provider for component injection */
  provide: Record<string, any>;
  /** Right-to-left state */
  isRtl: Ref<boolean>;
}

Usage Examples:

<template>
  <div>
    <!-- Language selector -->
    <v-select
      v-model="locale.current.value"
      :items="availableLanguages"
      :label="locale.t('language')"
      prepend-icon="mdi-translate"
      variant="outlined"
    >
      <template #item="{ props, item }">
        <v-list-item v-bind="props">
          <template #prepend>
            <span class="fi" :class="`fi-${item.raw.flag}`"></span>
          </template>
          <v-list-item-title>{{ item.title }}</v-list-item-title>
          <v-list-item-subtitle>{{ item.raw.native }}</v-list-item-subtitle>
        </v-list-item>
      </template>
    </v-select>

    <!-- Translated content -->
    <v-card class="mt-4">
      <v-card-title>{{ locale.t('welcome') }}</v-card-title>
      <v-card-text>
        <p>{{ locale.t('description') }}</p>
        <p>{{ locale.t('userGreeting', userName) }}</p>
        <p>{{ locale.t('itemCount', itemCount) }}</p>
        
        <!-- Formatted numbers -->
        <v-list>
          <v-list-item>
            <v-list-item-title>{{ locale.t('price') }}</v-list-item-title>
            <v-list-item-subtitle>{{ locale.n(1234.56) }}</v-list-item-subtitle>
          </v-list-item>
          <v-list-item>
            <v-list-item-title>{{ locale.t('quantity') }}</v-list-item-title>
            <v-list-item-subtitle>{{ locale.n(42) }}</v-list-item-subtitle>
          </v-list-item>
          <v-list-item>
            <v-list-item-title>{{ locale.t('percentage') }}</v-list-item-title>
            <v-list-item-subtitle>{{ formatPercentage(0.75) }}</v-list-item-subtitle>
          </v-list-item>
        </v-list>

        <!-- Date formatting -->
        <v-divider class="my-4" />
        <div class="date-formats">
          <h4>{{ locale.t('dateFormats') }}</h4>
          <p><strong>{{ locale.t('today') }}:</strong> {{ formatDate(new Date(), 'full') }}</p>
          <p><strong>{{ locale.t('short') }}:</strong> {{ formatDate(new Date(), 'short') }}</p>
          <p><strong>{{ locale.t('time') }}:</strong> {{ formatTime(new Date()) }}</p>
        </div>
      </v-card-text>
    </v-card>

    <!-- Form with translated labels and validation -->
    <v-form class="mt-4">
      <v-text-field
        v-model="formData.name"
        :label="locale.t('name')"
        :rules="nameRules"
        variant="outlined"
      />
      
      <v-text-field
        v-model="formData.email"
        :label="locale.t('email')"
        :rules="emailRules"
        type="email"
        variant="outlined"
      />
      
      <v-textarea
        v-model="formData.message"
        :label="locale.t('message')"
        :rules="messageRules"
        variant="outlined"
      />
      
      <v-btn color="primary" @click="submitForm">
        {{ locale.t('submit') }}
      </v-btn>
    </v-form>
  </div>
</template>

<script setup>
import { useLocale } from 'vuetify';

const locale = useLocale();

const userName = ref('John Doe');
const itemCount = ref(5);

const formData = reactive({
  name: '',
  email: '',
  message: ''
});

const availableLanguages = [
  { value: 'en', title: 'English', native: 'English', flag: 'us' },
  { value: 'es', title: 'Spanish', native: 'Español', flag: 'es' },
  { value: 'fr', title: 'French', native: 'Français', flag: 'fr' },
  { value: 'de', title: 'German', native: 'Deutsch', flag: 'de' },
  { value: 'it', title: 'Italian', native: 'Italiano', flag: 'it' },
  { value: 'pt', title: 'Portuguese', native: 'Português', flag: 'pt' },
  { value: 'ru', title: 'Russian', native: 'Русский', flag: 'ru' },
  { value: 'zh', title: 'Chinese', native: '中文', flag: 'cn' },
  { value: 'ja', title: 'Japanese', native: '日本語', flag: 'jp' },
  { value: 'ar', title: 'Arabic', native: 'العربية', flag: 'sa' },
  { value: 'he', title: 'Hebrew', native: 'עברית', flag: 'il' },
  { value: 'hi', title: 'Hindi', native: 'हिन्दी', flag: 'in' }
];

// Validation rules that respect current locale
const nameRules = computed(() => [
  v => !!v || locale.t('validation.required', locale.t('name')),
  v => v.length >= 2 || locale.t('validation.minLength', locale.t('name'), 2)
]);

const emailRules = computed(() => [
  v => !!v || locale.t('validation.required', locale.t('email')),
  v => /.+@.+\..+/.test(v) || locale.t('validation.email')
]);

const messageRules = computed(() => [
  v => !!v || locale.t('validation.required', locale.t('message')),
  v => v.length >= 10 || locale.t('validation.minLength', locale.t('message'), 10)
]);

// Locale-aware formatting functions
const formatPercentage = (value) => {
  return new Intl.NumberFormat(locale.current.value, {
    style: 'percent',
    minimumFractionDigits: 0,
    maximumFractionDigits: 1
  }).format(value);
};

const formatDate = (date, style = 'medium') => {
  const options = {
    full: { dateStyle: 'full' },
    long: { dateStyle: 'long' },
    medium: { dateStyle: 'medium' },
    short: { dateStyle: 'short' }
  };
  
  return new Intl.DateTimeFormat(locale.current.value, options[style]).format(date);
};

const formatTime = (date) => {
  return new Intl.DateTimeFormat(locale.current.value, {
    timeStyle: 'short'
  }).format(date);
};

const submitForm = () => {
  console.log('Form submitted:', formData);
  // Show success message in current locale
  alert(locale.t('formSubmitted'));
};

// Initialize locale messages
onMounted(() => {
  // Load locale-specific messages
  loadLocaleMessages();
});

const loadLocaleMessages = () => {
  // This would typically load from external files or API
  const messages = {
    en: {
      language: 'Language',
      welcome: 'Welcome to our application',
      description: 'This is a fully internationalized application supporting multiple languages and locales.',
      userGreeting: 'Hello, {0}!',
      itemCount: 'You have {0} items',
      price: 'Price',
      quantity: 'Quantity',
      percentage: 'Percentage',
      dateFormats: 'Date Formats',
      today: 'Today',
      short: 'Short',
      time: 'Time',
      name: 'Name',
      email: 'Email',
      message: 'Message',
      submit: 'Submit',
      formSubmitted: 'Form submitted successfully!',
      validation: {
        required: '{0} is required',
        minLength: '{0} must be at least {1} characters',
        email: 'Please enter a valid email address'
      }
    },
    es: {
      language: 'Idioma',
      welcome: 'Bienvenido a nuestra aplicación',
      description: 'Esta es una aplicación completamente internacionalizada que admite múltiples idiomas y configuraciones regionales.',
      userGreeting: '¡Hola, {0}!',
      itemCount: 'Tienes {0} artículos',
      price: 'Precio',
      quantity: 'Cantidad',
      percentage: 'Porcentaje',
      dateFormats: 'Formatos de Fecha',
      today: 'Hoy',
      short: 'Corto',
      time: 'Hora',
      name: 'Nombre',
      email: 'Correo electrónico',
      message: 'Mensaje',
      submit: 'Enviar',
      formSubmitted: '¡Formulario enviado con éxito!',
      validation: {
        required: '{0} es requerido',
        minLength: '{0} debe tener al menos {1} caracteres',
        email: 'Por favor ingrese una dirección de correo válida'
      }
    },
    ar: {
      language: 'اللغة',
      welcome: 'مرحباً بك في تطبيقنا',
      description: 'هذا تطبيق مدوّل بالكامل يدعم لغات ومناطق متعددة.',
      userGreeting: 'مرحباً، {0}!',
      itemCount: 'لديك {0} عناصر',
      price: 'السعر',
      quantity: 'الكمية',
      percentage: 'النسبة المئوية',
      dateFormats: 'تنسيقات التاريخ',
      today: 'اليوم',
      short: 'مختصر',
      time: 'الوقت',
      name: 'الاسم',
      email: 'البريد الإلكتروني',
      message: 'الرسالة',
      submit: 'إرسال',
      formSubmitted: 'تم إرسال النموذج بنجاح!',
      validation: {
        required: '{0} مطلوب',
        minLength: '{0} يجب أن يكون على الأقل {1} أحرف',
        email: 'يرجى إدخال عنوان بريد إلكتروني صالح'
      }
    }
  };
  
  locale.messages.value = { ...locale.messages.value, ...messages };
};
</script>

<style>
/* Flag icons support */
.fi {
  width: 20px;
  height: 15px;
  background-size: cover;
  border-radius: 2px;
  margin-right: 8px;
}

.date-formats {
  background: rgba(0,0,0,0.02);
  padding: 16px;
  border-radius: 8px;
  margin-top: 16px;
}
</style>

RTL (Right-to-Left) Support

Comprehensive right-to-left text direction support for Arabic, Hebrew, and other RTL languages.

/**
 * RTL configuration options
 */
interface RtlOptions {
  /** RTL configuration per locale */
  rtl?: Record<string, boolean>;
}

/**
 * RTL instance from useRtl composable
 */
interface RtlInstance {
  /** Whether current locale uses RTL */
  isRtl: Ref<boolean>;
  /** RTL value as computed property */
  rtl: Ref<boolean>;
  /** RTL CSS classes */
  rtlClasses: Ref<string>;
}

/**
 * RTL-aware layout utilities
 */
interface RtlLayoutUtils {
  /** Get start/end positioning for RTL */
  getLogicalProps: (props: Record<string, any>) => Record<string, any>;
  /** Convert physical directions to logical */
  toLogicalDirection: (direction: PhysicalDirection) => LogicalDirection;
  /** Get RTL-aware margin/padding */
  getSpacing: (spacing: SpacingConfig) => Record<string, string>;
}

type PhysicalDirection = 'left' | 'right' | 'top' | 'bottom';
type LogicalDirection = 'start' | 'end' | 'top' | 'bottom';

interface SpacingConfig {
  start?: string | number;
  end?: string | number;
  top?: string | number;
  bottom?: string | number;
}

Usage Examples:

<template>
  <div :dir="rtl.isRtl.value ? 'rtl' : 'ltr'" class="rtl-demo">
    <!-- RTL toggle -->
    <v-card class="mb-4">
      <v-card-title class="d-flex align-center">
        <v-icon class="me-2">mdi-translate</v-icon>
        {{ rtl.isRtl.value ? 'اتجاه النص' : 'Text Direction' }}
        <v-spacer />
        <v-btn-toggle v-model="textDirection" mandatory>
          <v-btn value="ltr" size="small">
            {{ rtl.isRtl.value ? 'يسار إلى يمين' : 'LTR' }}
          </v-btn>
          <v-btn value="rtl" size="small">
            {{ rtl.isRtl.value ? 'يمين إلى يسار' : 'RTL' }}
          </v-btn>
        </v-btn-toggle>
      </v-card-title>
    </v-card>

    <!-- Navigation with RTL support -->
    <v-app-bar :class="rtl.rtlClasses.value" color="primary">
      <v-btn
        :icon="rtl.isRtl.value ? 'mdi-menu-right' : 'mdi-menu'"
        @click="drawer = !drawer"
      />
      <v-toolbar-title>
        {{ rtl.isRtl.value ? 'تطبيق ويب' : 'Web Application' }}
      </v-toolbar-title>
      <v-spacer />
      <v-btn icon>
        <v-icon>{{ rtl.isRtl.value ? 'mdi-account-circle' : 'mdi-account-circle' }}</v-icon>
      </v-btn>
    </v-app-bar>

    <!-- Navigation drawer with RTL positioning -->
    <v-navigation-drawer
      v-model="drawer"
      :location="rtl.isRtl.value ? 'right' : 'left'"
      temporary
    >
      <v-list nav>
        <v-list-item
          v-for="item in navigationItems"
          :key="item.id"
          :prepend-icon="item.icon"
          :title="item.title"
          @click="navigateTo(item.route)"
        />
      </v-list>
    </v-navigation-drawer>

    <!-- Content with RTL text alignment -->
    <v-main class="pa-4">
      <v-row>
        <v-col cols="12" md="8">
          <!-- Article content -->
          <v-card>
            <v-card-title :class="{ 'text-right': rtl.isRtl.value }">
              {{ rtl.isRtl.value ? 'محتوى المقال' : 'Article Content' }}
            </v-card-title>
            <v-card-text>
              <p :class="rtlTextClass">
                {{ articleText }}
              </p>
              
              <!-- RTL-aware list -->
              <v-list class="mt-4">
                <v-list-subheader :class="{ 'text-right': rtl.isRtl.value }">
                  {{ rtl.isRtl.value ? 'العناصر' : 'Items' }}
                </v-list-subheader>
                <v-list-item
                  v-for="item in listItems"
                  :key="item.id"
                  :class="{ 'text-right': rtl.isRtl.value }"
                >
                  <template #prepend>
                    <v-icon :class="rtl.isRtl.value ? 'ms-2' : 'me-2'">
                      {{ item.icon }}
                    </v-icon>
                  </template>
                  <v-list-item-title>{{ item.title }}</v-list-item-title>
                  <v-list-item-subtitle>{{ item.subtitle }}</v-list-item-subtitle>
                </v-list-item>
              </v-list>
            </v-card-text>
          </v-card>
        </v-col>
        
        <v-col cols="12" md="4">
          <!-- Sidebar with RTL layout -->
          <v-card>
            <v-card-title :class="{ 'text-right': rtl.isRtl.value }">
              {{ rtl.isRtl.value ? 'الشريط الجانبي' : 'Sidebar' }}
            </v-card-title>
            <v-card-text>
              <!-- Form with RTL support -->
              <v-form>
                <v-text-field
                  v-model="searchQuery"
                  :label="rtl.isRtl.value ? 'البحث' : 'Search'"
                  :prepend-inner-icon="rtl.isRtl.value ? undefined : 'mdi-magnify'"
                  :append-inner-icon="rtl.isRtl.value ? 'mdi-magnify' : undefined"
                  variant="outlined"
                  :style="{ textAlign: rtl.isRtl.value ? 'right' : 'left' }"
                />
                
                <v-select
                  v-model="selectedCategory"
                  :items="categories"
                  :label="rtl.isRtl.value ? 'الفئة' : 'Category'"
                  variant="outlined"
                />
                
                <v-textarea
                  v-model="comments"
                  :label="rtl.isRtl.value ? 'التعليقات' : 'Comments'"
                  :style="{ textAlign: rtl.isRtl.value ? 'right' : 'left' }"
                  variant="outlined"
                />
              </v-form>

              <!-- Action buttons with RTL spacing -->
              <div :class="rtlButtonClass">
                <v-btn color="primary" :class="rtl.isRtl.value ? 'ml-2' : 'mr-2'">
                  {{ rtl.isRtl.value ? 'إرسال' : 'Submit' }}
                </v-btn>
                <v-btn variant="outlined">
                  {{ rtl.isRtl.value ? 'إلغاء' : 'Cancel' }}
                </v-btn>
              </div>
            </v-card-text>
          </v-card>

          <!-- Tags with RTL flow -->
          <v-card class="mt-4">
            <v-card-title :class="{ 'text-right': rtl.isRtl.value }">
              {{ rtl.isRtl.value ? 'العلامات' : 'Tags' }}
            </v-card-title>
            <v-card-text>
              <div :class="rtlChipClass">
                <v-chip
                  v-for="tag in tags"
                  :key="tag"
                  :class="rtl.isRtl.value ? 'ml-1 mb-1' : 'mr-1 mb-1'"
                  size="small"
                >
                  {{ tag }}
                </v-chip>
              </div>
            </v-card-text>
          </v-card>
        </v-col>
      </v-row>

      <!-- Data table with RTL support -->
      <v-card class="mt-4">
        <v-card-title :class="{ 'text-right': rtl.isRtl.value }">
          {{ rtl.isRtl.value ? 'جدول البيانات' : 'Data Table' }}
        </v-card-title>
        <v-data-table
          :headers="tableHeaders"
          :items="tableItems"
          :class="{ 'text-right': rtl.isRtl.value }"
        />
      </v-card>
    </v-main>
  </div>
</template>

<script setup>
import { useRtl, useLocale } from 'vuetify';

const rtl = useRtl();
const locale = useLocale();

const textDirection = ref('ltr');
const drawer = ref(false);
const searchQuery = ref('');
const selectedCategory = ref('');
const comments = ref('');

// Watch text direction changes
watch(textDirection, (direction) => {
  const newLocale = direction === 'rtl' ? 'ar' : 'en';
  locale.current.value = newLocale;
});

const navigationItems = computed(() => [
  {
    id: 1,
    title: rtl.isRtl.value ? 'الرئيسية' : 'Home',
    icon: 'mdi-home',
    route: '/'
  },
  {
    id: 2,
    title: rtl.isRtl.value ? 'المنتجات' : 'Products',
    icon: 'mdi-package-variant',
    route: '/products'
  },
  {
    id: 3,
    title: rtl.isRtl.value ? 'حول' : 'About',
    icon: 'mdi-information',
    route: '/about'
  },
  {
    id: 4,
    title: rtl.isRtl.value ? 'اتصل بنا' : 'Contact',
    icon: 'mdi-phone',
    route: '/contact'
  }
]);

const articleText = computed(() =>
  rtl.isRtl.value
    ? 'هذا مثال على النص العربي الذي يُكتب من اليمين إلى اليسار. يجب أن يظهر النص بشكل صحيح مع محاذاة مناسبة وتدفق طبيعي للقراءة. النظام يدعم جميع ميزات الـ RTL بما في ذلك ترتيب العناصر والأيقونات والأزرار.'
    : '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.'
);

const listItems = computed(() => [
  {
    id: 1,
    title: rtl.isRtl.value ? 'العنصر الأول' : 'First Item',
    subtitle: rtl.isRtl.value ? 'وصف العنصر الأول' : 'Description of first item',
    icon: 'mdi-numeric-1-circle'
  },
  {
    id: 2,
    title: rtl.isRtl.value ? 'العنصر الثاني' : 'Second Item',
    subtitle: rtl.isRtl.value ? 'وصف العنصر الثاني' : 'Description of second item',
    icon: 'mdi-numeric-2-circle'
  },
  {
    id: 3,
    title: rtl.isRtl.value ? 'العنصر الثالث' : 'Third Item',
    subtitle: rtl.isRtl.value ? 'وصف العنصر الثالث' : 'Description of third item',
    icon: 'mdi-numeric-3-circle'
  }
]);

const categories = computed(() =>
  rtl.isRtl.value
    ? [
        { title: 'تقنية', value: 'tech' },
        { title: 'تصميم', value: 'design' },
        { title: 'تطوير', value: 'development' }
      ]
    : [
        { title: 'Technology', value: 'tech' },
        { title: 'Design', value: 'design' },
        { title: 'Development', value: 'development' }
      ]
);

const tags = computed(() =>
  rtl.isRtl.value
    ? ['تقنية', 'ويب', 'تطوير', 'تصميم', 'واجهة المستخدم']
    : ['Technology', 'Web', 'Development', 'Design', 'UI']
);

const tableHeaders = computed(() => [
  {
    title: rtl.isRtl.value ? 'الاسم' : 'Name',
    key: 'name',
    align: rtl.isRtl.value ? 'end' : 'start'
  },
  {
    title: rtl.isRtl.value ? 'العمر' : 'Age',
    key: 'age',
    align: rtl.isRtl.value ? 'end' : 'start'
  },
  {
    title: rtl.isRtl.value ? 'المدينة' : 'City',
    key: 'city',
    align: rtl.isRtl.value ? 'end' : 'start'
  }
]);

const tableItems = computed(() => [
  {
    name: rtl.isRtl.value ? 'أحمد علي' : 'Ahmed Ali',
    age: 25,
    city: rtl.isRtl.value ? 'الرياض' : 'Riyadh'
  },
  {
    name: rtl.isRtl.value ? 'سارة محمد' : 'Sara Mohamed',
    age: 30,
    city: rtl.isRtl.value ? 'القاهرة' : 'Cairo'
  },
  {
    name: rtl.isRtl.value ? 'محمد أحمد' : 'Mohamed Ahmed',
    age: 28,
    city: rtl.isRtl.value ? 'دبي' : 'Dubai'
  }
]);

// RTL-aware CSS classes
const rtlTextClass = computed(() => ({
  'text-right': rtl.isRtl.value,
  'text-left': !rtl.isRtl.value
}));

const rtlButtonClass = computed(() => ({
  'd-flex': true,
  'justify-end': rtl.isRtl.value,
  'justify-start': !rtl.isRtl.value,
  'mt-4': true
}));

const rtlChipClass = computed(() => ({
  'd-flex': true,
  'flex-wrap': true,
  'justify-end': rtl.isRtl.value,
  'justify-start': !rtl.isRtl.value
}));

const navigateTo = (route) => {
  console.log(`Navigating to: ${route}`);
  drawer.value = false;
};
</script>

<style>
.rtl-demo {
  min-height: 100vh;
}

/* RTL-specific overrides */
.v-locale--is-rtl .v-navigation-drawer--is-mobile.v-navigation-drawer--temporary.v-navigation-drawer--left {
  right: 0;
  left: auto;
}

.v-locale--is-rtl .v-list-item__prepend {
  margin-inline-start: 0;
  margin-inline-end: 16px;
}

.v-locale--is-rtl .v-data-table th,
.v-locale--is-rtl .v-data-table td {
  text-align: right !important;
}

.v-locale--is-rtl input,
.v-locale--is-rtl textarea {
  text-align: right;
}

/* Custom RTL utilities */
.ms-2 {
  margin-inline-start: 8px !important;
}

.me-2 {
  margin-inline-end: 8px !important;
}

.ml-1 {
  margin-left: 4px !important;
}

.mr-1 {
  margin-right: 4px !important;
}

.ml-2 {
  margin-left: 8px !important;
}

.mr-2 {
  margin-right: 8px !important;
}
</style>

Supported Languages

Complete list of the 44+ supported languages with their locale codes and characteristics.

/**
 * Supported language configurations
 */
interface SupportedLanguages {
  /** Afrikaans */
  af: LocaleConfig;
  /** Arabic */
  ar: LocaleConfig;
  /** Azerbaijani */
  az: LocaleConfig;
  /** Bulgarian */
  bg: LocaleConfig;
  /** Catalan */
  ca: LocaleConfig;
  /** Central Kurdish */
  ckb: LocaleConfig;
  /** Czech */
  cs: LocaleConfig;
  /** Danish */
  da: LocaleConfig;
  /** German */
  de: LocaleConfig;
  /** Greek */
  el: LocaleConfig;
  /** English */
  en: LocaleConfig;
  /** Spanish */
  es: LocaleConfig;
  /** Estonian */
  et: LocaleConfig;
  /** Persian */
  fa: LocaleConfig;
  /** Finnish */
  fi: LocaleConfig;
  /** French */
  fr: LocaleConfig;
  /** Hebrew */
  he: LocaleConfig;
  /** Croatian */
  hr: LocaleConfig;
  /** Hungarian */
  hu: LocaleConfig;
  /** Indonesian */
  id: LocaleConfig;
  /** Italian */
  it: LocaleConfig;
  /** Japanese */
  ja: LocaleConfig;
  /** Khmer */
  km: LocaleConfig;
  /** Korean */
  ko: LocaleConfig;
  /** Lithuanian */
  lt: LocaleConfig;
  /** Latvian */
  lv: LocaleConfig;
  /** Dutch */
  nl: LocaleConfig;
  /** Norwegian */
  no: LocaleConfig;
  /** Polish */
  pl: LocaleConfig;
  /** Portuguese */
  pt: LocaleConfig;
  /** Romanian */
  ro: LocaleConfig;
  /** Russian */
  ru: LocaleConfig;
  /** Slovak */
  sk: LocaleConfig;
  /** Slovenian */
  sl: LocaleConfig;
  /** Serbian Cyrillic */
  srCyrl: LocaleConfig;
  /** Serbian Latin */
  srLatn: LocaleConfig;
  /** Swedish */
  sv: LocaleConfig;
  /** Thai */
  th: LocaleConfig;
  /** Turkish */
  tr: LocaleConfig;
  /** Ukrainian */
  uk: LocaleConfig;
  /** Vietnamese */
  vi: LocaleConfig;
  /** Chinese Simplified */
  zhHans: LocaleConfig;
  /** Chinese Traditional */
  zhHant: LocaleConfig;
}

interface LocaleConfig {
  /** Language code */
  code: string;
  /** Display name in English */
  name: string;
  /** Display name in native language */
  nativeName: string;
  /** Right-to-left text direction */
  rtl: boolean;
  /** Plural rules function */
  plural: (n: number) => number;
  /** Date/time formatting options */
  dateTime: DateTimeFormatOptions;
  /** Number formatting options */
  number: NumberFormatOptions;
}

interface DateTimeFormatOptions {
  /** Date format patterns */
  dateFormats: {
    full: string;
    long: string;
    medium: string;
    short: string;
  };
  /** Time format patterns */
  timeFormats: {
    full: string;
    long: string;
    medium: string;
    short: string;
  };
  /** First day of week (0 = Sunday, 1 = Monday) */
  firstDayOfWeek: number;
  /** Weekend days */
  weekendDays: number[];
}

interface NumberFormatOptions {
  /** Decimal separator */
  decimal: string;
  /** Thousands separator */
  thousands: string;
  /** Currency formatting */
  currency: {
    /** Currency code */
    code: string;
    /** Currency symbol */
    symbol: string;
    /** Symbol position */
    position: 'before' | 'after';
  };
}

Usage Examples:

// Language configuration examples
const supportedLanguages = {
  // English (Left-to-Right)
  en: {
    code: 'en',
    name: 'English',
    nativeName: 'English',
    rtl: false,
    plural: (n) => n === 1 ? 0 : 1,
    dateTime: {
      dateFormats: {
        full: 'EEEE, MMMM d, y',
        long: 'MMMM d, y',
        medium: 'MMM d, y',
        short: 'M/d/yy'
      },
      timeFormats: {
        full: 'h:mm:ss a zzzz',
        long: 'h:mm:ss a z',
        medium: 'h:mm:ss a',
        short: 'h:mm a'
      },
      firstDayOfWeek: 0, // Sunday
      weekendDays: [0, 6] // Sunday, Saturday
    },
    number: {
      decimal: '.',
      thousands: ',',
      currency: {
        code: 'USD',
        symbol: '$',
        position: 'before'
      }
    }
  },

  // Arabic (Right-to-Left)
  ar: {
    code: 'ar',
    name: 'Arabic',
    nativeName: 'العربية',
    rtl: true,
    plural: (n) => {
      // Arabic plural rules (complex)
      if (n === 0) return 0;
      if (n === 1) return 1;
      if (n === 2) return 2;
      if (n % 100 >= 3 && n % 100 <= 10) return 3;
      if (n % 100 >= 11) return 4;
      return 5;
    },
    dateTime: {
      dateFormats: {
        full: 'EEEE، d MMMM y',
        long: 'd MMMM y',
        medium: 'dd‏/MM‏/y',
        short: 'd‏/M‏/y'
      },
      timeFormats: {
        full: 'h:mm:ss a zzzz',
        long: 'h:mm:ss a z',
        medium: 'h:mm:ss a',
        short: 'h:mm a'
      },
      firstDayOfWeek: 6, // Saturday
      weekendDays: [5, 6] // Friday, Saturday
    },
    number: {
      decimal: '٫',
      thousands: '٬',
      currency: {
        code: 'SAR',
        symbol: 'ر.س',
        position: 'after'
      }
    }
  },

  // Chinese Simplified
  zhHans: {
    code: 'zh-Hans',
    name: 'Chinese Simplified',
    nativeName: '中文(简体)',
    rtl: false,
    plural: (n) => 0, // Chinese has no plural forms
    dateTime: {
      dateFormats: {
        full: 'y年M月d日EEEE',
        long: 'y年M月d日',
        medium: 'y年M月d日',
        short: 'y/M/d'
      },
      timeFormats: {
        full: 'zzzz ah:mm:ss',
        long: 'z ah:mm:ss',
        medium: 'ah:mm:ss',
        short: 'ah:mm'
      },
      firstDayOfWeek: 1, // Monday
      weekendDays: [0, 6] // Sunday, Saturday
    },
    number: {
      decimal: '.',
      thousands: ',',
      currency: {
        code: 'CNY',
        symbol: '¥',
        position: 'before'
      }
    }
  },

  // Hebrew (Right-to-Left)
  he: {
    code: 'he',
    name: 'Hebrew',
    nativeName: 'עברית',
    rtl: true,
    plural: (n) => n === 1 ? 0 : 1,
    dateTime: {
      dateFormats: {
        full: 'EEEE, d בMMMM y',
        long: 'd בMMMM y',
        medium: 'd בMMM y',
        short: 'd.M.y'
      },
      timeFormats: {
        full: 'H:mm:ss zzzz',
        long: 'H:mm:ss z',
        medium: 'H:mm:ss',
        short: 'H:mm'
      },
      firstDayOfWeek: 0, // Sunday
      weekendDays: [5, 6] // Friday, Saturday
    },
    number: {
      decimal: '.',
      thousands: ',',
      currency: {
        code: 'ILS',
        symbol: '₪',
        position: 'before'
      }
    }
  }
};

// Vuetify configuration with multiple locales
import { createVuetify } from 'vuetify';
import { en, ar, zhHans, he } from 'vuetify/locale';

const vuetify = createVuetify({
  locale: {
    locale: 'en',
    fallback: 'en',
    messages: { en, ar, zhHans, he },
    rtl: {
      ar: true,
      he: true,
      fa: true,
      ckb: true
    }
  }
});

// Dynamic locale loading
const loadLocale = async (localeCode) => {
  try {
    // Load Vuetify translations
    const vuetifyMessages = await import(`vuetify/locale/${localeCode}`);
    
    // Load application translations
    const appMessages = await import(`../locales/${localeCode}.js`);
    
    // Merge messages
    return {
      ...vuetifyMessages.default,
      ...appMessages.default
    };
  } catch (error) {
    console.warn(`Failed to load locale: ${localeCode}`, error);
    return null;
  }
};

// Usage in application
export const useInternationalization = () => {
  const locale = useLocale();
  const rtl = useRtl();
  
  const changeLanguage = async (newLocale) => {
    const messages = await loadLocale(newLocale);
    if (messages) {
      locale.messages.value[newLocale] = messages;
      locale.current.value = newLocale;
      
      // Update document attributes
      document.documentElement.lang = newLocale;
      document.documentElement.dir = rtl.isRtl.value ? 'rtl' : 'ltr';
    }
  };
  
  return {
    locale,
    rtl,
    changeLanguage,
    supportedLanguages: Object.keys(supportedLanguages)
  };
};

Types

// Core internationalization types
type LocaleCode = string;
type LocaleKey = string;
type TranslationMessage = string;

// Locale configuration
interface LocaleConfiguration {
  locale: LocaleCode;
  fallback: LocaleCode;
  messages: Record<LocaleCode, LocaleMessages>;
  adapter?: LocaleAdapter;
  rtl?: Record<LocaleCode, boolean>;
}

// Message interpolation
type MessageParameters = Array<string | number>;
interface MessageContext {
  locale: LocaleCode;
  key: LocaleKey;
  parameters: MessageParameters;
}

// Pluralization
type PluralRule = (count: number, locale: LocaleCode) => number;
interface PluralRules {
  [locale: string]: PluralRule;
}

// Date and time formatting
interface DateTimeLocaleConfig {
  calendar: CalendarType;
  numberingSystem: NumberingSystem;
  timeZone: string;
}

type CalendarType = 'gregory' | 'islamic' | 'hebrew' | 'persian' | 'buddhist';
type NumberingSystem = 'latn' | 'arab' | 'deva' | 'thai' | 'hanidec';

// RTL configuration
interface RtlConfiguration {
  locales: Record<LocaleCode, boolean>;
  autoDetect: boolean;
  fallbackDirection: 'ltr' | 'rtl';
}

// Formatting options
interface LocaleFormatOptions {
  date: Intl.DateTimeFormatOptions;
  time: Intl.DateTimeFormatOptions;
  number: Intl.NumberFormatOptions;
  currency: Intl.NumberFormatOptions;
  percent: Intl.NumberFormatOptions;
}

// Locale metadata
interface LocaleMetadata {
  code: LocaleCode;
  name: string;
  nativeName: string;
  region: string;
  script: string;
  direction: 'ltr' | 'rtl';
  pluralRules: string;
  territories: string[];
}

Install with Tessl CLI

npx tessl i tessl/npm-vuetify

docs

components.md

composables.md

data-display.md

directives.md

feedback.md

forms.md

framework-core.md

icons.md

index.md

internationalization.md

lab-components.md

navigation.md

theming.md

transitions.md

utilities.md

tile.json