コンテンツへスキップ

国際化とRTL

Radix PrimitivesはLTR/RTL両方の向きをサポートしています。Radixとの国際化の統合方法の詳細をご覧ください。

複数方向サポート

はじめに

このドキュメントでは、SSRサポートを含むRadix Vueでの複数方向サポートの使用方法に関するガイダンスを提供します。Radix Primitivesは、フローティング要素を配置するためにFloating UIに依存しており、ウェブアプリの現在の向きを入力する必要があります。

RadixコンポーネントはデフォルトでLTRですが、サポートする向き(LTRのみ、RTLのみ、または両方)を制御できます。このセクションでは、RTL方向を簡単にサポートするためのベストプラクティスを示します。

RTL

ConfigProviderは、ウェブアプリの方向性を含むグローバル設定を提供するラッパーコンポーネントです。

右から左(RTL)の読み取り方向を必要とするローカライズされたアプリを作成する場合は、dirプロップに基づいてすべてのプリミティブが動作を調整するように、ConfigProviderコンポーネントでアプリケーションをラップする必要があります。

すべてのRadixプリミティブをRTLにするには、ConfigProviderでアプリケーション全体をラップし、dirプロップに値rtlを渡します。

次のコードをapp.vueまたはメインレイアウトコンポーネントに追加します。

vue
<script setup lang="ts">
import { ConfigProvider } from 'radix-vue'
</script>

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

プロバイダーでラップされたすべてのRadixコンポーネントは、dir属性を継承します。

動的な方向

Radixプリミティブの方向を動的に変更するには、useTextDirectionコンポーザブルを利用し、ConfigProviderと組み合わせることができます。

しかしまず、@vueuse/coreパッケージをインストールする必要があります。

sh
$ npm add @vueuse/core

次に、ルートVueファイルで

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

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

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

SSRをサポートするには、サーバーがhtmlとその方向にアクセスできない場合、useTextDirectioninitialValueを設定します。

vue
<script setup lang="ts">
import { ConfigProvider } from 'radix-vue'
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>

情報

dirプロップは値としてautoをサポートしていないため、方向を明示的に定義するための中間Refが必要です。

textDirectionRefであり、その値を「ltr」または「rtl」に変更することで、htmlタグのdir属性も変更されます。

国際化

一部の言語はLTRで、他の言語はRTLで記述されています。多言語ウェブアプリでは、翻訳とともに方向性を設定する必要があります。これは、radix-vueプリミティブを使用してそれを実現する方法に関する簡略化されたガイドです。

しかしまず、必要なパッケージをいくつかインストールしましょう。

依存関係

サポートするさまざまな翻訳を管理するために、VueI18nに依存しています。

sh
$ npm add vue-i18n@9

main.tsでさまざまな言語の単語「hello」の翻訳を追加してみましょう。

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')

言語セレクター

翻訳を設定し、vue-i18nプラグインを追加した後、app.vueに言語セレクターが必要です。このradixセレクトプリミティブを使用して言語を変更することで

  1. 翻訳は新しい言語に反応します。
  2. ウェブアプリの方向は新しい言語に反応します。
vue
<script setup lang="ts">
import { ConfigProvider, SelectContent, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectPortal, SelectRoot, SelectScrollDownButton, SelectScrollUpButton, SelectTrigger, SelectValue, SelectViewport, } from 'radix-vue'
import { useTextDirection } from '@vueuse/core'

import { useI18n } from 'vue-i18n'
import { ref } from 'vue'

type LanguageInfo = {
  label: string
  value: string
  dir: 'ltr' | 'rtl'
}

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

const selectedLanguage = ref<string>()

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

      <SelectRoot
        v-model="selectedLanguage"
        @update:model-value="selectLanguage"
      >
        <SelectTrigger
          class="inline-flex min-w-[160px] items-center justify-between rounded px-[15px] text-[13px] leading-none h-[35px] gap-[5px] bg-white text-grass11 shadow-[0_2px_10px] shadow-black/10 hover:bg-mauve3 focus:shadow-[0_0_0_2px] focus:shadow-black data-[placeholder]:text-green9 outline-none"
          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="min-w-[160px] bg-white rounded shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade z-[100]"
            :side-offset="5"
          >
            <SelectScrollUpButton
              class="flex items-center justify-center h-[25px] bg-white text-violet11 cursor-default"
            >
              <Icon icon="radix-icons:chevron-up" />
            </SelectScrollUpButton>

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

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