feat(locale): provide dir on defineLocale (#2620)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
Alex
2024-11-13 16:19:21 +05:00
committed by GitHub
parent 9c00f7c7b7
commit 937585cb3f
26 changed files with 467 additions and 470 deletions

View File

@@ -17,6 +17,10 @@ export default defineCommand({
name: {
description: 'Locale name to create. For example: English.',
required: true
},
dir: {
description: 'Locale direction. For example: rtl.',
default: 'ltr'
}
},
async setup({ args }) {
@@ -32,6 +36,11 @@ export default defineCommand({
process.exit(1)
}
if (!['ltr', 'rtl'].includes(args.dir)) {
consola.error(`🚨 Direction ${args.dir} not supported!`)
process.exit(1)
}
if (!args.code.match(/^[a-z]{2}(?:_[a-z]{2,4})?$/)) {
consola.error(`🚨 ${args.code} is not a valid locale code!\nExample: en or en_us`)
process.exit(1)
@@ -45,7 +54,9 @@ export default defineCommand({
// Create new locale file
await fsp.copyFile(originLocaleFilePath, newLocaleFilePath)
const localeFile = await fsp.readFile(newLocaleFilePath, 'utf-8')
const rewrittenLocaleFile = localeFile.replace(/defineLocale\('(.*)'/, `defineLocale('${args.name}', '${normalizeLocale(args.code)}'`)
const rewrittenLocaleFile = localeFile
.replace(/name: '(.*)',/, `name: '${args.name}',`)
.replace(/code: '(.*)',/, `code: '${normalizeLocale(args.code)}',${(args.dir && args.dir !== 'ltr') ? `\n dir: '${args.dir}',` : ''}`)
await fsp.writeFile(newLocaleFilePath, rewrittenLocaleFile)
consola.success(`🪄 Generated ${newLocaleFilePath}`)

View File

@@ -12,11 +12,13 @@ select:
to: /getting-started/i18n/vue
---
## Usage
::note{to="/components/app"}
Nuxt UI provides an [App](/components/app) component that wraps your app to provide global configurations.
::
## Locale
### Locale
Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`:
@@ -32,14 +34,36 @@ import { fr } from '@nuxt/ui/locale'
</template>
```
### Direction
Each locale has a default direction, but you can override it using the `dir` prop if needed.
Use the `dir` prop with `ltr` or `rtl` to set the global reading direction of your app:
```vue [app.vue]
<script setup lang="ts">
import { fr } from '@nuxt/ui/locale'
</script>
<template>
<UApp dir="rtl" :locale="fr">
<NuxtPage />
</UApp>
</template>
```
### Custom locale
You also have the option to add your own locale using `defineLocale`:
```vue [app.vue]
<script setup lang="ts">
const locale = defineLocale('My custom locale', 'en', {
// implement pairs
const locale = defineLocale({
name: 'My custom locale',
code: 'en',
messages: {
// implement pairs
}
})
</script>
@@ -51,7 +75,7 @@ const locale = defineLocale('My custom locale', 'en', {
```
::tip
Look at the second parameter, there you need to pass the iso code of the language. Example:
Look at the `code` parameter, there you need to pass the iso code of the language. Example:
* `hi` Hindi (language)
* `de-AT`: German (language) as used in Austria (region)
::
@@ -125,65 +149,6 @@ const { locale } = useI18n()
::
### Supported languages
## Supported languages
:supported-languages
## Direction
Use the `dir` prop with `ltr` or `rtl` to set the global reading direction of your app:
```vue [app.vue]
<template>
<UApp dir="rtl">
<NuxtPage />
</UApp>
</template>
```
### Dynamic direction
To dynamically change the global reading direction of your app, you can use VueUse's [useTextDirection](https://vueuse.org/core/useTextDirection/) composable to detect and switch between LTR and RTL text directions.
::steps{level="4"}
#### Install the `@vueuse/core` package
::code-group{sync="pm"}
```bash [pnpm]
pnpm add @vueuse/core
```
```bash [yarn]
yarn add @vueuse/core
```
```bash [npm]
npm install @vueuse/core
```
```bash [bun]
bun add @vueuse/core
```
::
#### Set the `dir` prop using `useTextDirection`
```vue [app.vue]
<script setup lang="ts">
import { useTextDirection } from '@vueuse/core'
const textDirection = useTextDirection({ initialValue: 'ltr' })
const dir = computed(() => textDirection.value === 'rtl' ? 'rtl' : 'ltr')
</script>
<template>
<UApp :dir="dir">
<NuxtPage />
</UApp>
</template>
```
::

View File

@@ -12,11 +12,13 @@ select:
to: /getting-started/i18n/vue
---
## Usage
::note{to="/components/app"}
Nuxt UI provides an [App](/components/app) component that wraps your app to provide global configurations.
::
## Locale
### Locale
Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`:
@@ -32,6 +34,24 @@ import { fr } from '@nuxt/ui/locale'
</template>
```
### Direction
Each locale has a default direction, but you can override it using the `dir` prop if needed.
Use the `dir` prop with `ltr` or `rtl` to set the global reading direction of your app:
```vue [App.vue]
<script setup lang="ts">
import { fr } from '@nuxt/ui/locale'
</script>
<template>
<UApp dir="rtl" :locale="fr">
<RouterView />
</UApp>
</template>
```
### Custom locale
You also have the option to add your locale using `defineLocale`:
@@ -40,8 +60,12 @@ You also have the option to add your locale using `defineLocale`:
<script setup lang="ts">
import { defineLocale } from '@nuxt/ui/runtime/composables/defineLocale'
const locale = defineLocale('My custom locale', 'en', {
// implement pairs
const locale = defineLocale({
name: 'My custom locale',
code: 'en',
messages: {
// implement pairs
}
})
</script>
@@ -53,7 +77,7 @@ const locale = defineLocale('My custom locale', 'en', {
```
::tip
Look at the second parameter, there you need to pass the iso code of the language. Example:
Look at the `code` parameter, there you need to pass the iso code of the language. Example:
* `hi` Hindi (language)
* `de-AT`: German (language) as used in Austria (region)
::
@@ -138,61 +162,3 @@ const { locale } = useI18n()
## Supported languages
:supported-languages
## Direction
Use the `dir` prop with `ltr` or `rtl` to set the global reading direction of your app:
```vue [App.vue]
<template>
<UApp dir="rtl">
<NuxtPage />
</UApp>
</template>
```
### Dynamic direction
To dynamically change the global reading direction of your app, you can use VueUse's [useTextDirection](https://vueuse.org/core/useTextDirection/) composable to detect and switch between LTR and RTL text directions.
::steps{level="4"}
#### Install the `@vueuse/core` package
::code-group{sync="pm"}
```bash [pnpm]
pnpm add @vueuse/core
```
```bash [yarn]
yarn add @vueuse/core
```
```bash [npm]
npm install @vueuse/core
```
```bash [bun]
bun add @vueuse/core
```
::
#### Set the `dir` prop using `useTextDirection`
```vue [App.vue]
<script setup lang="ts">
import { computed } from 'vue'
import { useTextDirection } from '@vueuse/core'
const textDirection = useTextDirection()
const dir = computed(() => textDirection.value === 'rtl' ? 'rtl' : 'ltr')
</script>
<template>
<UApp :dir="dir">
<RouterView />
</UApp>
</template>
```

View File

@@ -125,7 +125,7 @@ const ui = computed(() => alert({
size="md"
color="neutral"
variant="link"
:aria-label="t('ui.alert.close')"
:aria-label="t('alert.close')"
v-bind="typeof close === 'object' ? close : undefined"
:class="ui.close({ class: props.ui?.close })"
@click="emits('update:open', false)"

View File

@@ -3,6 +3,7 @@ import type { ConfigProviderProps, TooltipProviderProps } from 'radix-vue'
import { localeContextInjectionKey } from '../composables/useLocale'
import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
import type { ToasterProps, Locale } from '../types'
import { en } from '../locale'
export interface AppProps extends Omit<ConfigProviderProps, 'useId'> {
tooltip?: TooltipProviderProps
@@ -32,15 +33,16 @@ import USlideoverProvider from './SlideoverProvider.vue'
const props = defineProps<AppProps>()
defineSlots<AppSlots>()
const configProviderProps = useForwardProps(reactivePick(props, 'dir', 'scrollBody'))
const configProviderProps = useForwardProps(reactivePick(props, 'scrollBody'))
const tooltipProps = toRef(() => props.tooltip)
const toasterProps = toRef(() => props.toaster)
provide(localeContextInjectionKey, computed(() => props.locale))
const locale = computed(() => props.locale || en)
provide(localeContextInjectionKey, locale)
</script>
<template>
<ConfigProvider :use-id="() => (useId() as string)" v-bind="configProviderProps">
<ConfigProvider :use-id="() => (useId() as string)" :dir="dir || locale.dir" v-bind="configProviderProps">
<TooltipProvider v-bind="tooltipProps">
<UToaster v-if="toaster !== null" v-bind="toasterProps">
<slot />

View File

@@ -281,7 +281,7 @@ defineExpose({
size="md"
color="neutral"
variant="outline"
:aria-label="t('ui.carousel.prev')"
:aria-label="t('carousel.prev')"
v-bind="typeof prev === 'object' ? prev : undefined"
:class="ui.prev({ class: props.ui?.prev })"
@click="scrollPrev"
@@ -292,7 +292,7 @@ defineExpose({
size="md"
color="neutral"
variant="outline"
:aria-label="t('ui.carousel.next')"
:aria-label="t('carousel.next')"
v-bind="typeof next === 'object' ? next : undefined"
:class="ui.next({ class: props.ui?.next })"
@click="scrollNext"
@@ -302,7 +302,7 @@ defineExpose({
<div v-if="dots" :class="ui.dots({ class: props.ui?.dots })">
<template v-for="(_, index) in scrollSnaps" :key="index">
<button
:aria-label="t('ui.carousel.goto', { slide: index + 1 })"
:aria-label="t('carousel.goto', { slide: index + 1 })"
:class="ui.dot({ class: props.ui?.dot, active: selectedIndex === index })"
@click="scrollTo(index)"
/>

View File

@@ -247,7 +247,7 @@ const groups = computed(() => {
size="md"
color="neutral"
variant="ghost"
:aria-label="t('ui.commandPalette.close')"
:aria-label="t('commandPalette.close')"
v-bind="typeof close === 'object' ? close : undefined"
:class="ui.close({ class: props.ui?.close })"
@click="emits('update:open', false)"
@@ -261,7 +261,7 @@ const groups = computed(() => {
<ComboboxContent :class="ui.content({ class: props.ui?.content })" :dismissable="false">
<ComboboxEmpty :class="ui.empty({ class: props.ui?.empty })">
<slot name="empty" :search-term="searchTerm">
{{ searchTerm ? t('ui.commandPalette.noMatch', { searchTerm }) : t('ui.commandPalette.noData') }}
{{ searchTerm ? t('commandPalette.noMatch', { searchTerm }) : t('commandPalette.noData') }}
</slot>
</ComboboxEmpty>

View File

@@ -322,7 +322,7 @@ defineExpose({
>
<span :class="ui.itemLabel({ class: props.ui?.itemLabel })">
<slot name="create-item-label" :item="(creatable.item as T)">
{{ t('ui.inputMenu.create', { label: typeof creatable.item === 'object' ? get(creatable.item, props.labelKey as string) : creatable.item }) }}
{{ t('inputMenu.create', { label: typeof creatable.item === 'object' ? get(creatable.item, props.labelKey as string) : creatable.item }) }}
</slot>
</span>
</ComboboxItem>
@@ -412,7 +412,7 @@ defineExpose({
<ComboboxContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
<ComboboxEmpty :class="ui.empty({ class: props.ui?.empty })">
<slot name="empty" :search-term="searchTerm">
{{ searchTerm ? t('ui.inputMenu.noMatch', { searchTerm }) : t('ui.inputMenu.noData') }}
{{ searchTerm ? t('inputMenu.noMatch', { searchTerm }) : t('inputMenu.noData') }}
</slot>
</ComboboxEmpty>

View File

@@ -146,7 +146,7 @@ const ui = computed(() => modal({
size="md"
color="neutral"
variant="ghost"
:aria-label="t('ui.modal.close')"
:aria-label="t('modal.close')"
v-bind="typeof close === 'object' ? close : undefined"
:class="ui.close({ class: props.ui?.close })"
/>

View File

@@ -292,7 +292,7 @@ function onUpdateOpen(value: boolean) {
>
<span :class="ui.itemLabel({ class: props.ui?.itemLabel })">
<slot name="create-item-label" :item="(creatable.item as T)">
{{ t('ui.selectMenu.create', { label: typeof creatable.item === 'object' ? get(creatable.item, props.labelKey as string) : creatable.item }) }}
{{ t('selectMenu.create', { label: typeof creatable.item === 'object' ? get(creatable.item, props.labelKey as string) : creatable.item }) }}
</slot>
</span>
</ComboboxItem>
@@ -349,7 +349,7 @@ function onUpdateOpen(value: boolean) {
<ComboboxEmpty :class="ui.empty({ class: props.ui?.empty })">
<slot name="empty" :search-term="searchTerm">
{{ searchTerm ? t('ui.selectMenu.noMatch', { searchTerm }) : t('ui.selectMenu.noData') }}
{{ searchTerm ? t('selectMenu.noMatch', { searchTerm }) : t('selectMenu.noData') }}
</slot>
</ComboboxEmpty>

View File

@@ -145,7 +145,7 @@ const ui = computed(() => slideover({
size="md"
color="neutral"
variant="ghost"
:aria-label="t('ui.slideover.close')"
:aria-label="t('slideover.close')"
v-bind="typeof close === 'object' ? close : undefined"
:class="ui.close({ class: props.ui?.close })"
/>

View File

@@ -241,7 +241,7 @@ defineExpose({
<tr v-else :class="ui.tr({ class: [props.ui?.tr] })">
<td :colspan="columns?.length" :class="ui.empty({ class: props.ui?.empty })">
<slot name="empty">
{{ t('ui.table.noData') }}
{{ t('table.noData') }}
</slot>
</td>
</tr>

View File

@@ -153,7 +153,7 @@ defineExpose({
size="md"
color="neutral"
variant="link"
:aria-label="t('ui.toast.close')"
:aria-label="t('toast.close')"
v-bind="typeof close === 'object' ? close : undefined"
:class="ui.close({ class: props.ui?.close })"
@click.stop

View File

@@ -1,9 +1,13 @@
import type { Locale, LocalePair } from '../types/locale'
import type { Locale, Direction, Messages } from '../types/locale'
import { defu } from 'defu'
export function defineLocale(name: string, code: string, pair: LocalePair): Locale {
return {
name,
code,
ui: pair
}
interface DefineLocaleOptions {
name: string
code: string
dir?: Direction
messages: Messages
}
export function defineLocale(options: DefineLocaleOptions): Locale {
return defu<DefineLocaleOptions, [{ dir: Direction }]>(options, { dir: 'ltr' })
}

View File

@@ -3,11 +3,17 @@ import type { InjectionKey, Ref } from 'vue'
import type { Locale } from '../types/locale'
import { buildLocaleContext } from '../utils/locale'
import { en } from '../locale'
import { createSharedComposable } from '@vueuse/core'
export const localeContextInjectionKey: InjectionKey<Ref<Locale | undefined>> = Symbol('nuxt-ui.locale-context')
export const useLocale = (localeOverrides?: Ref<Locale | undefined>) => {
const _useLocale = (localeOverrides?: Ref<Locale | undefined>) => {
const locale = localeOverrides || inject(localeContextInjectionKey, ref())!
/**
* If for some reason the developer does not use `UApp`, we get the language back just in case.
*/
return buildLocaleContext(computed(() => locale.value || en))
}
export const useLocale = createSharedComposable(_useLocale)

View File

@@ -1,39 +1,44 @@
import { defineLocale } from '../composables/defineLocale'
export default defineLocale('العربية', 'ar', {
inputMenu: {
noMatch: 'لا توجد نتائج مطابقة',
noData: 'لا توجد بيانات',
create: 'إنشاء "{label}"'
},
commandPalette: {
noMatch: 'لا توجد نتائج مطابقة',
noData: 'لا توجد بيانات',
close: 'إغلاق'
},
selectMenu: {
noMatch: 'لا توجد نتائج مطابقة',
noData: 'لا توجد بيانات',
create: 'إنشاء "{label}"'
},
toast: {
close: 'إغلاق'
},
carousel: {
prev: 'السابق',
next: 'التالي',
goto: 'الذهاب إلي شريحة {slide}'
},
modal: {
close: 'إغلاق'
},
slideover: {
close: 'إغلاق'
},
alert: {
close: 'إغلاق'
},
table: {
noData: 'لا توجد بيانات'
export default defineLocale({
name: 'العربية',
code: 'ar',
dir: 'rtl',
messages: {
inputMenu: {
noMatch: 'لا توجد نتائج مطابقة',
noData: 'لا توجد بيانات',
create: 'إنشاء "{label}"'
},
commandPalette: {
noMatch: 'لا توجد نتائج مطابقة',
noData: 'لا توجد بيانات',
close: 'إغلاق'
},
selectMenu: {
noMatch: 'لا توجد نتائج مطابقة',
noData: 'لا توجد بيانات',
create: 'إنشاء "{label}"'
},
toast: {
close: 'إغلاق'
},
carousel: {
prev: 'السابق',
next: 'التالي',
goto: 'الذهاب إلي شريحة {slide}'
},
modal: {
close: 'إغلاق'
},
slideover: {
close: 'إغلاق'
},
alert: {
close: 'إغلاق'
},
table: {
noData: 'لا توجد بيانات'
}
}
})

View File

@@ -1,39 +1,43 @@
import { defineLocale } from '../composables/defineLocale'
export default defineLocale('Čeština', 'cs', {
inputMenu: {
noMatch: 'Žádná shoda',
noData: 'Žádná data',
create: 'Vytvořit "{label}"'
},
commandPalette: {
noMatch: 'Žádná shoda',
noData: 'Žádná data',
close: 'Zavřít'
},
selectMenu: {
noMatch: 'Žádná shoda',
noData: 'Žádná data',
create: 'Vytvořit "{label}"'
},
toast: {
close: 'Zavřít'
},
carousel: {
prev: 'Předchozí',
next: 'Další',
goto: 'Přejít na {slide}'
},
modal: {
close: 'Zavřít'
},
slideover: {
close: 'Zavřít'
},
alert: {
close: 'Zavřít'
},
table: {
noData: 'Žádná data'
export default defineLocale({
name: 'Čeština',
code: 'cs',
messages: {
inputMenu: {
noMatch: 'Žádná shoda',
noData: 'Žádná data',
create: 'Vytvořit "{label}"'
},
commandPalette: {
noMatch: 'Žádná shoda',
noData: 'Žádná data',
close: 'Zavřít'
},
selectMenu: {
noMatch: 'Žádná shoda',
noData: 'Žádná data',
create: 'Vytvořit "{label}"'
},
toast: {
close: 'Zavřít'
},
carousel: {
prev: 'Předchozí',
next: 'Další',
goto: 'Přejít na {slide}'
},
modal: {
close: 'Zavřít'
},
slideover: {
close: 'Zavřít'
},
alert: {
close: 'Zavřít'
},
table: {
noData: 'Žádná data'
}
}
})

View File

@@ -1,39 +1,43 @@
import { defineLocale } from '../composables/defineLocale'
export default defineLocale('Deutsch', 'de', {
inputMenu: {
noMatch: 'Nichts gefunden',
noData: 'Keine Daten',
create: 'Erstellen "{label}"'
},
commandPalette: {
noMatch: 'Nichts gefunden',
noData: 'Keine Daten',
close: 'Schließen'
},
selectMenu: {
noMatch: 'Nichts gefunden',
noData: 'Keine Daten',
create: 'Erstellen "{label}"'
},
toast: {
close: 'Schließen'
},
carousel: {
prev: 'Weiter',
next: 'Zurück',
goto: 'Gehe zu {slide}'
},
modal: {
close: 'Schließen'
},
slideover: {
close: 'Schließen'
},
alert: {
close: 'Schließen'
},
table: {
noData: 'Keine Daten'
export default defineLocale({
name: 'Deutsch',
code: 'de',
messages: {
inputMenu: {
noMatch: 'Nichts gefunden',
noData: 'Keine Daten',
create: 'Erstellen "{label}"'
},
commandPalette: {
noMatch: 'Nichts gefunden',
noData: 'Keine Daten',
close: 'Schließen'
},
selectMenu: {
noMatch: 'Nichts gefunden',
noData: 'Keine Daten',
create: 'Erstellen "{label}"'
},
toast: {
close: 'Schließen'
},
carousel: {
prev: 'Weiter',
next: 'Zurück',
goto: 'Gehe zu {slide}'
},
modal: {
close: 'Schließen'
},
slideover: {
close: 'Schließen'
},
alert: {
close: 'Schließen'
},
table: {
noData: 'Keine Daten'
}
}
})

View File

@@ -1,39 +1,43 @@
import { defineLocale } from '../composables/defineLocale'
export default defineLocale('English', 'en', {
inputMenu: {
noMatch: 'No matching data',
noData: 'No data',
create: 'Create "{label}"'
},
commandPalette: {
noMatch: 'No matching data',
noData: 'No data',
close: 'Close'
},
selectMenu: {
noMatch: 'No matching data',
noData: 'No data',
create: 'Create "{label}"'
},
toast: {
close: 'Close'
},
carousel: {
prev: 'Prev',
next: 'Next',
goto: 'Go to slide {slide}'
},
modal: {
close: 'Close'
},
slideover: {
close: 'Close'
},
alert: {
close: 'Close'
},
table: {
noData: 'No data'
export default defineLocale({
name: 'English',
code: 'en',
messages: {
inputMenu: {
noMatch: 'No matching data',
noData: 'No data',
create: 'Create "{label}"'
},
commandPalette: {
noMatch: 'No matching data',
noData: 'No data',
close: 'Close'
},
selectMenu: {
noMatch: 'No matching data',
noData: 'No data',
create: 'Create "{label}"'
},
toast: {
close: 'Close'
},
carousel: {
prev: 'Prev',
next: 'Next',
goto: 'Go to slide {slide}'
},
modal: {
close: 'Close'
},
slideover: {
close: 'Close'
},
alert: {
close: 'Close'
},
table: {
noData: 'No data'
}
}
})

View File

@@ -1,39 +1,43 @@
import { defineLocale } from '../composables/defineLocale'
export default defineLocale('Français', 'fr', {
inputMenu: {
noMatch: 'Aucune donnée correspondante',
noData: 'Aucune donnée',
create: 'Créer "{label}"'
},
commandPalette: {
noMatch: 'Aucune donnée correspondante',
noData: 'Aucune donnée',
close: 'Fermer'
},
selectMenu: {
noMatch: 'Aucune donnée correspondante',
noData: 'Aucune donnée',
create: 'Créer "{label}"'
},
toast: {
close: 'Fermer'
},
carousel: {
prev: 'Précédent',
next: 'Suivant',
goto: 'Aller à {slide}'
},
modal: {
close: 'Fermer'
},
slideover: {
close: 'Fermer'
},
alert: {
close: 'Fermer'
},
table: {
noData: 'Aucune donnée'
export default defineLocale({
name: 'Français',
code: 'fr',
messages: {
inputMenu: {
noMatch: 'Aucune donnée correspondante',
noData: 'Aucune donnée',
create: 'Créer "{label}"'
},
commandPalette: {
noMatch: 'Aucune donnée correspondante',
noData: 'Aucune donnée',
close: 'Fermer'
},
selectMenu: {
noMatch: 'Aucune donnée correspondante',
noData: 'Aucune donnée',
create: 'Créer "{label}"'
},
toast: {
close: 'Fermer'
},
carousel: {
prev: 'Précédent',
next: 'Suivant',
goto: 'Aller à {slide}'
},
modal: {
close: 'Fermer'
},
slideover: {
close: 'Fermer'
},
alert: {
close: 'Fermer'
},
table: {
noData: 'Aucune donnée'
}
}
})

View File

@@ -1,39 +1,43 @@
import { defineLocale } from '../composables/defineLocale'
export default defineLocale('Italiano', 'it', {
inputMenu: {
noMatch: 'Nessun dato corrispondente',
noData: 'Nessun dato',
create: 'Crea "{label}"'
},
commandPalette: {
noMatch: 'Nessun dato corrispondente',
noData: 'Nessun dato',
close: 'Chiudi'
},
selectMenu: {
noMatch: 'Nessun dato corrispondente',
noData: 'Nessun dato',
create: 'Crea "{label}"'
},
toast: {
close: 'Chiudi'
},
carousel: {
prev: 'Precedente',
next: 'Successiva',
goto: 'Vai alla slide {slide}'
},
modal: {
close: 'Chiudi'
},
slideover: {
close: 'Chiudi'
},
alert: {
close: 'Chiudi'
},
table: {
noData: 'Nessun dato'
export default defineLocale({
name: 'Italiano',
code: 'it',
messages: {
inputMenu: {
noMatch: 'Nessun dato corrispondente',
noData: 'Nessun dato',
create: 'Crea "{label}"'
},
commandPalette: {
noMatch: 'Nessun dato corrispondente',
noData: 'Nessun dato',
close: 'Chiudi'
},
selectMenu: {
noMatch: 'Nessun dato corrispondente',
noData: 'Nessun dato',
create: 'Crea "{label}"'
},
toast: {
close: 'Chiudi'
},
carousel: {
prev: 'Precedente',
next: 'Successiva',
goto: 'Vai alla slide {slide}'
},
modal: {
close: 'Chiudi'
},
slideover: {
close: 'Chiudi'
},
alert: {
close: 'Chiudi'
},
table: {
noData: 'Nessun dato'
}
}
})

View File

@@ -1,39 +1,43 @@
import { defineLocale } from '../composables/defineLocale'
export default defineLocale('Русский', 'ru', {
inputMenu: {
noMatch: 'Совпадений не найдено',
noData: 'Нет данных',
create: 'Создать "{label}"'
},
commandPalette: {
noMatch: 'Совпадений не найдено',
noData: 'Нет данных',
close: 'Закрыть'
},
selectMenu: {
noMatch: 'Совпадений не найдено',
noData: 'Нет данных',
create: 'Создать "{label}"'
},
toast: {
close: 'Закрыть'
},
carousel: {
prev: 'Назад',
next: 'Далее',
goto: 'Перейти к {slide}'
},
modal: {
close: 'Закрыть'
},
slideover: {
close: 'Закрыть'
},
alert: {
close: 'Закрыть'
},
table: {
noData: 'Нет данных'
export default defineLocale({
name: 'Русский',
code: 'ru',
messages: {
inputMenu: {
noMatch: 'Совпадений не найдено',
noData: 'Нет данных',
create: 'Создать "{label}"'
},
commandPalette: {
noMatch: 'Совпадений не найдено',
noData: 'Нет данных',
close: 'Закрыть'
},
selectMenu: {
noMatch: 'Совпадений не найдено',
noData: 'Нет данных',
create: 'Создать "{label}"'
},
toast: {
close: 'Закрыть'
},
carousel: {
prev: 'Назад',
next: 'Далее',
goto: 'Перейти к {slide}'
},
modal: {
close: 'Закрыть'
},
slideover: {
close: 'Закрыть'
},
alert: {
close: 'Закрыть'
},
table: {
noData: 'Нет данных'
}
}
})

View File

@@ -1,39 +1,43 @@
import { defineLocale } from '../composables/defineLocale'
export default defineLocale('简体中文', 'zh-Hans', {
inputMenu: {
noMatch: '没有匹配的数据',
noData: '没有数据',
create: '创建 "{label}"'
},
commandPalette: {
noMatch: '没有匹配的数据',
noData: '没有数据',
close: '关闭'
},
selectMenu: {
noMatch: '没有匹配的数据',
noData: '没有数据',
create: '创建 "{label}"'
},
toast: {
close: '关闭'
},
carousel: {
prev: '上一页',
next: '下一页',
goto: '跳转到第 {slide} 页'
},
modal: {
close: '关闭'
},
slideover: {
close: '关闭'
},
alert: {
close: '关闭'
},
table: {
noData: '没有数据'
export default defineLocale({
name: '简体中文',
code: 'zh-Hans',
messages: {
inputMenu: {
noMatch: '没有匹配的数据',
noData: '没有数据',
create: '创建 "{label}"'
},
commandPalette: {
noMatch: '没有匹配的数据',
noData: '没有数据',
close: '关闭'
},
selectMenu: {
noMatch: '没有匹配的数据',
noData: '没有数据',
create: '创建 "{label}"'
},
toast: {
close: '关闭'
},
carousel: {
prev: '上一页',
next: '下一页',
goto: '跳转到第 {slide} 页'
},
modal: {
close: '关闭'
},
slideover: {
close: '关闭'
},
alert: {
close: '关闭'
},
table: {
noData: '没有数据'
}
}
})

View File

@@ -1,39 +1,43 @@
import { defineLocale } from '../composables/defineLocale'
export default defineLocale('繁体中文', 'zh-Hant', {
inputMenu: {
noMatch: '沒有匹配的資料',
noData: '沒有資料',
create: '創建 "{label}"'
},
commandPalette: {
noMatch: '沒有匹配的資料',
noData: '沒有資料',
close: '關閉'
},
selectMenu: {
noMatch: '沒有匹配的資料',
noData: '沒有資料',
create: '創建 "{label}"'
},
toast: {
close: '關閉'
},
carousel: {
prev: '上一頁',
next: '下一頁',
goto: '跳轉到第 {slide} 頁'
},
modal: {
close: '關閉'
},
slideover: {
close: '關閉'
},
alert: {
close: '關閉'
},
table: {
noData: '沒有資料'
export default defineLocale({
name: '繁体中文',
code: 'zh-Hant',
messages: {
inputMenu: {
noMatch: '沒有匹配的資料',
noData: '沒有資料',
create: '創建 "{label}"'
},
commandPalette: {
noMatch: '沒有匹配的資料',
noData: '沒有資料',
close: '關閉'
},
selectMenu: {
noMatch: '沒有匹配的資料',
noData: '沒有資料',
create: '創建 "{label}"'
},
toast: {
close: '關閉'
},
carousel: {
prev: '上一頁',
next: '下一頁',
goto: '跳轉到第 {slide} 頁'
},
modal: {
close: '關閉'
},
slideover: {
close: '關閉'
},
alert: {
close: '關閉'
},
table: {
noData: '沒有資料'
}
}
})

View File

@@ -1,4 +1,4 @@
export type LocalePair = {
export type Messages = {
inputMenu: {
noMatch: string
noData: string
@@ -36,8 +36,11 @@ export type LocalePair = {
}
}
export type Direction = 'ltr' | 'rtl'
export type Locale = {
name: string
code: string
ui: LocalePair
dir: Direction
messages: Messages
}

View File

@@ -9,6 +9,7 @@ export type Translator = (path: string, option?: TranslatorOption) => string
export type LocaleContext = {
locale: Ref<Locale>
lang: Ref<string>
dir: Ref<string>
code: Ref<string>
t: Translator
}
@@ -18,7 +19,7 @@ export function buildTranslator(locale: MaybeRef<Locale>): Translator {
}
export function translate(path: string, option: undefined | TranslatorOption, locale: Locale): string {
const prop: string = get(locale, path, path)
const prop: string = get(locale, `messages.${path}`, path)
return prop.replace(
/\{(\w+)\}/g,
@@ -29,11 +30,13 @@ export function translate(path: string, option: undefined | TranslatorOption, lo
export function buildLocaleContext(locale: MaybeRef<Locale>): LocaleContext {
const lang = computed(() => unref(locale).name)
const code = computed(() => unref(locale).code)
const dir = computed(() => unref(locale).dir)
const localeRef = isRef(locale) ? locale : ref(locale)
return {
lang,
code,
dir,
locale: localeRef,
t: buildTranslator(locale)
}