---
title: "Internationalization (RTL)"
description: "Akar support both LTR/RTL directions. Learn more about how to integrate internationalization."
canonical_url: "https://vinicunca.dev/akar/guides/i18n"
---
# Internationalization (RTL)

> Akar support both LTR/RTL directions. Learn more about how to integrate internationalization.

## Internationalization & RTL

## Multi-Direction Support

### Introduction

This documentation provides guidance on how to utilize multi-directional support in Akar with SSR support. Akar rely on [`Floating UI`](https://floating-ui.com/) to position floating elements, which requires to be fed the current direction of the web app.

Akar components are LTR by default, but you are in control of what direction (only LTR, RTL, or both) you want to support. This section provides best practices to easily support RTL direction.

### RTL

[`ConfigProvider`](https://vinicunca.dev/akar/utilities/config-provider) is a wrapper component to provide global configurations, including the directionality of the web app.

When creating localized apps that require right-to-left (RTL) reading direction, you need to wrap your application with the `ConfigProvider` component to ensure all of the primitives adjust their behavior based on the `dir` prop.

To make all Akar RTL, wrap your entire App in `ConfigProvider` and pass the `dir` prop with the value `rtl`.

Add the following code to your `app.vue` or main layout component:

```vue
<script setup lang="ts">
import { ConfigProvider } from 'akar';
</script>

<template>
  <ConfigProvider dir="rtl">
    <slot />
  </ConfigProvider>
</template>
```

All Akar components that are wrapped in the provider inherit the `dir` attribute.

### Dynamic Direction

To dynamically change the direction of Akar, we could leverage the [`useTextDirection`](https://vueuse.org/core/useTextDirection/) composable and combine it with our `ConfigProvider`.

But first, we need to install the [`@vueuse/core`](https://vueuse.org/) package.

```bash [pnpm]
pnpm add @vueuse/core
```

```bash [yarn]
yarn add @vueuse/core
```

```bash [npm]
npm install @vueuse/core
```

```bash [bun]
bun add @vueuse/core
```

Then in your root Vue file:

```vue
<script setup lang="ts">
import { useTextDirection } from '@vueuse/core';
import { ConfigProvider } from 'akar';
import { computed } from 'vue';

const textDirection = useTextDirection();
const dir = computed(() => textDirection.value === 'rtl' ? 'rtl' : 'ltr');
</script>

<template>
  <ConfigProvider :dir="dir">
    <slot />
  </ConfigProvider>
</template>
```

To support SSR - when the server has no access to the `html` and its direction, set `initialValue` in `useTextDirection`.

```vue
<script setup lang="ts">
import { ConfigProvider } from 'akar'
import { useTextDirection } from '@vueuse/core'

const textDirection = useTextDirection({ initialValue: 'rtl' })
const dir = computed(() => textDirection.value === 'rtl' ? 'rtl' : 'ltr')
</script>

<template>
  <ConfigProvider :dir="dir">
    <slot />
  </ConfigProvider>
</template>
```

> [!NOTE]
> 
> The `dir` prop doesn't support `auto` as a value, so we need an intermediate Ref to explicitly define the direction.

`textDirection` is a [`Ref`](https://vuejs.org/api/reactivity-core.html#ref), and by changing the value of it to either "ltr" or "rtl", the `dir` attribute on the `html` tag changes as well.

## Internationalization

Some languages are written from LTR and others are written in RTL. In a multi-language web app, you need to configure directionality alongside the translations. This is a simplified guide on how to achieve that using Akar primitives.

But first, let's install some required packages.

### Dependencies

We rely on [`VueI18n`](https://vue-i18n.intlify.dev/) to manage different translations we want to support.

```bash [pnpm]
pnpm add vue-i18n@latest
```

```bash [yarn]
yarn add vue-i18n@latest
```

```bash [npm]
npm install vue-i18n@latest
```

```bash [bun]
bun add vue-i18n@latest
```

Go ahead and add some translations for the word "hello" in different languages at `main.ts`.

```ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createI18n } from 'vue-i18n'

const messages = {
  en: {
    hello: 'Hello',
  },
  fa: {
    hello: 'درود',
  },
  ar: {
    hello: 'مرحبا',
  },
  ja: {
    hello: 'こんにちは',
  }
}

const i18n = createI18n({
  legacy: false, // you must set `false` to use the Composition API
  locale: 'en', // set default locale
  availableLocales: ['en', 'fa', 'ar', 'ja'],
  messages,
})

createApp(App)
  .use(i18n)
  .mount('#app')
```

### Language Selector

After setting the translations and adding the `vue-i18n` plugin, we need a language selector in your `app.vue`. By changing the language using this Akar select primitive:

1. The translations are reactive to the new language
2. The direction of the web app is reactive to the new language

```vue
<script setup lang="ts">
import { useTextDirection } from '@vueuse/core';
import { ConfigProvider, SelectContent, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectPortal, SelectRoot, SelectScrollDownButton, SelectScrollUpButton, SelectTrigger, SelectValue, SelectViewport, } from 'akar';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';

interface LanguageInfo {
  label: string;
  value: string;
  dir: 'ltr' | 'rtl';
}

const dir = useTextDirection({ initialValue: 'ltr' });
const { locale } = useI18n();

const selectedLanguage = ref<string>();

const languages: Array<LanguageInfo> = [
  { label: 'English', value: 'en', dir: 'ltr' },
  { label: 'Persian', value: 'fa', dir: 'rtl' },
  { label: 'Arabic', value: 'ar', dir: 'rtl' },
  { label: 'Japanese', value: 'ja', dir: 'ltr' },
];

function selectLanguage(newLanguage: string) {
  const langInfo = languages.find((item) => item.value === newLanguage);

  if (!langInfo) {
    return;
  }

  dir.value = langInfo.dir;
  locale.value = langInfo.value;
}
</script>

<template>
  <ConfigProvider :dir="dir">
    <div class="mx-auto p-10 flex flex-col gap-y-[8rem] max-w-[1400px] items-center justify-center">
      <div class="text-2xl">
        👋 {{ $t("hello") }}
      </div>
      <div class="text-2xl">
        HTML is in <span class="text-bold color-purple-500">{{ dir }} mode
        </span>
      </div>

      <SelectRoot
        v-model="selectedLanguage"
        @update:model-value="selectLanguage"
      >
        <SelectTrigger
          class="color-grass11 hover:bg-mauve3 data-[placeholder]:color-green9 text-[13px] leading-none px-[15px] outline-none rounded bg-white inline-flex gap-[5px] h-[35px] min-w-[160px] shadow-[0_2px_10px] shadow-black/10 items-center justify-between focus:shadow-[0_0_0_2px] focus:shadow-black"
          aria-label="Customize options"
        >
          <SelectValue placeholder="Select a language..." />
          <Icon
            icon="radix-icons:chevron-down"
            class="h-3.5 w-3.5"
          />
        </SelectTrigger>

        <SelectPortal>
          <SelectContent
            class="data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade will-change-[opacity,transform] rounded bg-white min-w-[160px] shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] z-[100]"
            :side-offset="5"
          >
            <SelectScrollUpButton
              class="color-violet11 bg-white flex h-[25px] cursor-default items-center justify-center"
            >
              <Icon icon="radix-icons:chevron-up" />
            </SelectScrollUpButton>

            <SelectViewport class="p-[5px]">
              <SelectLabel class="color-mauve11 text-xs leading-[25px] px-[25px]">
                Languages
              </SelectLabel>
              <SelectGroup>
                <SelectItem
                  v-for="(option, index) in languages"
                  :key="index"
                  class="color-grass11 data-[disabled]:color-mauve8 data-[highlighted]:bg-green9 data-[highlighted]:color-green1 text-[13px] leading-none pl-[25px] pr-[35px] rounded-[3px] flex h-[25px] select-none items-center relative data-[highlighted]:outline-none data-[disabled]:pointer-events-none"
                  :value="option.value"
                >
                  <SelectItemIndicator class="inline-flex w-[25px] items-center left-0 justify-center absolute">
                    <Icon icon="radix-icons:check" />
                  </SelectItemIndicator>
                  <SelectItemText>
                    {{ option.label }}
                  </SelectItemText>
                </SelectItem>
              </SelectGroup>
            </SelectViewport>

            <SelectScrollDownButton
              class="color-violet11 bg-white flex h-[25px] cursor-default items-center justify-center"
            >
              <Icon icon="radix-icons:chevron-down" />
            </SelectScrollDownButton>
          </SelectContent>
        </SelectPortal>
      </SelectRoot>
    </div>
  </ConfigProvider>
</template>
```


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
