国际化与 RTL
多方向支持
简介
本文档提供了如何在 Reka UI 中利用多方向支持及 SSR 支持的指南。Reka UI 依赖 Floating UI 来定位浮动元素,这需要为其提供当前 Web 应用的方向信息。
Reka 组件默认为 LTR,但您完全可以控制要支持的方向(仅 LTR、仅 RTL,或两者都支持)。本节提供了最佳实践,以轻松支持 RTL 方向。
RTL
ConfigProvider 是一个包装器组件,用于提供全局配置,包括 Web 应用的方向性。
当创建需要从右到左 (RTL) 阅读方向的本地化应用时,您需要使用 ConfigProvider 组件包装您的应用程序,以确保所有基础组件都能根据 dir 属性调整其行为。
要使所有 Reka UI 组件变为 RTL 模式,请将整个应用包装在 ConfigProvider 中,并传递值为 rtl 的 dir 属性。
将以下代码添加到您的 app.vue 或主布局组件中:
<script setup lang="ts">
import { ConfigProvider } from 'reka-ui'
</script>
<template>
<ConfigProvider dir="rtl">
<slot />
</ConfigProvider>
</template>所有包装在该提供者内的 Reka 组件都会继承 dir 属性。
动态方向
要动态更改 Reka UI 的方向,我们可以利用 useTextDirection 组合式函数,并将其与我们的 ConfigProvider 结合使用。
但首先,我们需要安装 @vueuse/core 包。
$ npm add @vueuse/core然后在您的根 Vue 文件中:
<script setup lang="ts">
import { useTextDirection } from '@vueuse/core'
import { ConfigProvider } from 'reka-ui'
import { computed } from 'vue'
const textDirection = useTextDirection()
const dir = computed(() => textDirection.value === 'rtl' ? 'rtl' : 'ltr')
</script>
<template>
<ConfigProvider :dir="dir">
<slot />
</ConfigProvider>
</template>为了支持 SSR - 当服务器无法访问 html 及其方向时,在 useTextDirection 中设置 initialValue。
<script setup lang="ts">
import { ConfigProvider } from 'reka-ui'
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 来明确定义方向。
textDirection 是一个 Ref,通过将其值更改为 "ltr" 或 "rtl",html 标签上的 dir 属性也会相应改变。
国际化
某些语言是从左到右 (LTR) 书写的,而其他语言则是从右到左 (RTL) 书写的。在多语言 Web 应用中,您需要在翻译的同时配置方向性。这是一个使用 reka-ui 基础组件实现此功能的简化指南。
但首先,让我们安装一些必需的包。
依赖项
我们依赖 VueI18n 来管理我们要支持的不同翻译。
$ npm add vue-i18n@latest接下来,在 main.ts 中为不同语言添加一些“hello”的翻译。
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, // 必须设置为 `false` 以使用组合式 API
locale: 'en', // 设置默认语言环境
availableLocales: ['en', 'fa', 'ar', 'ja'],
messages,
})
createApp(App)
.use(i18n)
.mount('#app')语言选择器
设置翻译并添加 vue-i18n 插件后,您需要在 app.vue 中添加一个语言选择器。通过使用此 reka-ui 选择基础组件更改语言:
- 翻译会对新语言做出反应
- Web 应用的方向会对新语言做出反应
<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 'reka-ui'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
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 处于 <span class="text-bold text-purple-500">{{ dir }}</span> 模式
</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>