mirror of
https://github.com/SrIzan10/helium.git
synced 2026-06-06 00:56:58 +00:00
feat: add internationalization and spanish locale
This commit is contained in:
46
app/components/LanguageSwitcher.vue
Normal file
46
app/components/LanguageSwitcher.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '~/components/ui/select'
|
||||
import { Languages } from 'lucide-vue-next'
|
||||
|
||||
const { locale, locales, setLocale } = useI18n()
|
||||
const { t } = useI18n()
|
||||
|
||||
const switchLocalePath = useSwitchLocalePath()
|
||||
|
||||
const availableLocales = computed(() => locales.value)
|
||||
|
||||
const currentLocale = computed({
|
||||
get: () => locale.value,
|
||||
set: (value) => {
|
||||
const path = switchLocalePath(value)
|
||||
navigateTo(path)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select v-model="currentLocale">
|
||||
<SelectTrigger class="w-[160px]">
|
||||
<Languages class="mr-2 h-4 w-4" />
|
||||
<SelectValue :placeholder="t('selectLanguage')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="loc in availableLocales"
|
||||
:key="loc.code"
|
||||
:value="loc.code"
|
||||
>
|
||||
{{ loc.name }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
@@ -2,9 +2,9 @@
|
||||
<Dialog :open="open" @update:open="$emit('update:open', $event)">
|
||||
<DialogContent class="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Preset</DialogTitle>
|
||||
<DialogTitle>{{ t('editPreset') }}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your preset here. Click save when you're done.
|
||||
{{ t('editPresetDescription') }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<PresetForm
|
||||
@@ -28,6 +28,8 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import PresetForm from "~/components/app/PresetForm.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean;
|
||||
presetUser: any;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<FieldGroup>
|
||||
<form.Field v-slot="{ field }" name="name">
|
||||
<Field :data-invalid="isInvalid(field)">
|
||||
<FieldLabel :for="`${formId}-name`"> Preset name </FieldLabel>
|
||||
<FieldLabel :for="`${formId}-name`"> {{ t('presetName') }} </FieldLabel>
|
||||
<Input
|
||||
:id="`${formId}-name`"
|
||||
:name="field.name"
|
||||
@@ -24,7 +24,7 @@
|
||||
</FieldGroup>
|
||||
<form.Field v-slot="{ field }" name="iceServers">
|
||||
<Field :data-invalid="isInvalid(field)">
|
||||
<FieldLabel>Ice Servers (JSON)</FieldLabel>
|
||||
<FieldLabel>{{ t('iceServersJson') }}</FieldLabel>
|
||||
<div
|
||||
class="h-96 w-full border rounded-md overflow-hidden focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px] transition"
|
||||
>
|
||||
@@ -47,10 +47,10 @@
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldLabel :for="`${formId}-default`">
|
||||
Set as default preset
|
||||
{{ t('setAsDefaultPreset') }}
|
||||
</FieldLabel>
|
||||
<FieldDescription>
|
||||
This preset will be selected by default on the preset selector.
|
||||
{{ t('setAsDefaultPresetDescription') }}
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<Switch
|
||||
@@ -62,7 +62,7 @@
|
||||
</Field>
|
||||
</form.Field>
|
||||
<Field orientation="horizontal">
|
||||
<Button type="submit" :form="formId">Save</Button>
|
||||
<Button type="submit" :form="formId">{{ t('save') }}</Button>
|
||||
</Field>
|
||||
</form>
|
||||
</div>
|
||||
@@ -84,6 +84,8 @@ import { Input } from "~/components/ui/input";
|
||||
import { Switch } from "~/components/ui/switch";
|
||||
import { schema } from "~/lib/schema/new-preset";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
initialValues?: {
|
||||
name: string;
|
||||
@@ -153,14 +155,14 @@ const form = useForm({
|
||||
if (request.success) {
|
||||
toast.success(
|
||||
props.isEdit
|
||||
? "Preset updated successfully!"
|
||||
: "Preset created successfully!",
|
||||
? t('presetUpdatedSuccessfully')
|
||||
: t('presetCreatedSuccessfully'),
|
||||
);
|
||||
emit("success");
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(
|
||||
props.isEdit ? "Failed to update preset." : "Failed to create preset.",
|
||||
props.isEdit ? t('failedToUpdatePreset') : t('failedToCreatePreset'),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Plus } from "lucide-vue-next";
|
||||
import type { ApiResponse, PresetUser } from "~/lib/types/PresetGetResponse";
|
||||
import { useStreamerStore } from "~/state/streamer";
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const selectedValue = ref("");
|
||||
const presets = ref<PresetUser[]>([]);
|
||||
@@ -70,7 +71,7 @@ watch(selectedValue, (newValue) => {
|
||||
<Select v-model="selectedValue" :disabled="loading">
|
||||
<SelectTrigger class="w-[180px]">
|
||||
<SelectValue
|
||||
:placeholder="loading ? 'Loading presets...' : 'Select a preset'"
|
||||
:placeholder="loading ? t('loadingPresets') : t('selectAPreset')"
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -78,7 +79,7 @@ watch(selectedValue, (newValue) => {
|
||||
v-if="presets.length === 0 && !loading"
|
||||
class="px-2 py-1.5 text-sm text-muted-foreground"
|
||||
>
|
||||
No presets available
|
||||
{{ t('noPresetsAvailable') }}
|
||||
</div>
|
||||
|
||||
<div v-else-if="!loading">
|
||||
@@ -90,7 +91,7 @@ watch(selectedValue, (newValue) => {
|
||||
<span
|
||||
v-if="preset.isDefault"
|
||||
class="ml-2 text-xs text-muted-foreground"
|
||||
>(default)</span
|
||||
>({{ t('default') }})</span
|
||||
>
|
||||
</SelectItem>
|
||||
</div>
|
||||
@@ -100,7 +101,7 @@ watch(selectedValue, (newValue) => {
|
||||
<SelectItem value="create-new">
|
||||
<div class="font-bold flex gap-2 items-center">
|
||||
<Plus class="size-4" />
|
||||
Create New Preset
|
||||
{{ t('createNewPreset') }}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import SignInDialog from "~/components/app/SignInDialog.vue";
|
||||
import ThemeDropdown from "~/components/ui/ThemeDropdown.vue";
|
||||
import LanguageSwitcher from "~/components/LanguageSwitcher.vue";
|
||||
import "vue-sonner/style.css";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -18,14 +21,14 @@ import { Toaster } from "@/components/ui/sonner";
|
||||
class="text-sm font-medium hover:text-primary transition-colors"
|
||||
active-class="text-primary"
|
||||
>
|
||||
Home
|
||||
{{ t('home') }}
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/stream"
|
||||
class="text-sm font-medium hover:text-primary transition-colors"
|
||||
active-class="text-primary"
|
||||
>
|
||||
Stream
|
||||
{{ t('stream') }}
|
||||
</NuxtLink>
|
||||
<ClientOnly>
|
||||
<SignedIn>
|
||||
@@ -34,13 +37,14 @@ import { Toaster } from "@/components/ui/sonner";
|
||||
class="text-sm font-medium hover:text-primary transition-colors"
|
||||
active-class="text-primary"
|
||||
>
|
||||
Presets
|
||||
{{ t('presets') }}
|
||||
</NuxtLink>
|
||||
</SignedIn>
|
||||
</ClientOnly>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex space-x-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<LanguageSwitcher />
|
||||
<ThemeDropdown />
|
||||
<ClientOnly>
|
||||
<SignedOut>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col md:flex-row items-center justify-center gap-6 mt-10 px-4 min-h-[80vh]"
|
||||
class="flex flex-col md:flex-row items-center justify-center gap-6 md:gap-20 mt-10 px-4 min-h-[80vh]"
|
||||
>
|
||||
<div
|
||||
v-if="!isConnected"
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="text-center space-y-2">
|
||||
<h1 class="text-4xl font-bold tracking-tight">helium</h1>
|
||||
<p class="text-muted-foreground text-lg">
|
||||
effortless screensharing powered by webrtc
|
||||
{{ $t("effortlessScreensharing") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<NuxtLink to="/stream">
|
||||
<Button variant="link" class="text-muted-foreground hover:text-primary">
|
||||
host instead?
|
||||
{{ $t("hostInstead") }}
|
||||
</Button>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
@@ -36,9 +36,11 @@
|
||||
class="absolute inset-0 flex items-center justify-center z-10 p-4 text-center"
|
||||
>
|
||||
<div v-if="viewerStore.isDisconnected" class="space-y-4">
|
||||
<p class="text-sm font-medium text-muted-foreground">stream ended</p>
|
||||
<p class="text-sm font-medium text-muted-foreground">
|
||||
{{ $t("streamEnded") }}
|
||||
</p>
|
||||
<Button @click="handleReset" variant="outline">
|
||||
Enter another code
|
||||
{{ $t("enterAnotherCode") }}
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
@@ -53,7 +55,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<p v-else class="text-muted-foreground/50 text-sm">
|
||||
enter code to join stream
|
||||
{{ $t("enterCodeToJoinStream") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -73,7 +75,9 @@
|
||||
<div
|
||||
v-if="isConnected"
|
||||
class="absolute top-0 left-0 right-0 p-4 flex justify-between items-start transition-opacity bg-gradient-to-b from-black/50 to-transparent"
|
||||
:class="[controlsVisible ? 'opacity-100' : 'opacity-0 hover:opacity-100']"
|
||||
:class="[
|
||||
controlsVisible ? 'opacity-100' : 'opacity-0 hover:opacity-100',
|
||||
]"
|
||||
@click="resetControlsTimeout"
|
||||
@touchstart="showControls"
|
||||
>
|
||||
@@ -84,7 +88,7 @@
|
||||
@click="cleanupViewing"
|
||||
>
|
||||
<LogOut class="w-5 h-5" />
|
||||
Disconnect
|
||||
{{ $t("disconnect") }}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -94,7 +98,7 @@
|
||||
@click="toggleFullscreen"
|
||||
>
|
||||
<Maximize class="w-5 h-5" />
|
||||
Fullscreen
|
||||
{{ $t("fullscreen") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -319,7 +323,7 @@ function resetControlsTimeout() {
|
||||
if (controlsHideTimeout) {
|
||||
clearTimeout(controlsHideTimeout);
|
||||
}
|
||||
|
||||
|
||||
controlsHideTimeout = setTimeout(() => {
|
||||
controlsVisible.value = false;
|
||||
}, 3000);
|
||||
@@ -344,4 +348,3 @@ onMounted(() => {
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="px-4">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-3xl font-bold">Presets</h1>
|
||||
<h1 class="text-3xl font-bold">{{ t('presets') }}</h1>
|
||||
<Button @click="navigateTo('/presets/new')">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Create New Preset
|
||||
{{ t('createNewPreset') }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
@@ -14,19 +14,16 @@
|
||||
<CardTitle class="flex items-center justify-between">
|
||||
<span>{{ presetUser.preset.name }}</span>
|
||||
<Badge v-if="presetUser.isDefault" variant="secondary"
|
||||
>Default</Badge
|
||||
>{{ t('default') }}</Badge
|
||||
>
|
||||
</CardTitle>
|
||||
<CardDescription
|
||||
>Created by {{ presetUser.preset.createdBy }}</CardDescription
|
||||
>{{ t('createdBy') }} {{ presetUser.preset.createdBy }}</CardDescription
|
||||
>
|
||||
</CardHeader>
|
||||
<CardContent class="grow">
|
||||
<div class="text-sm text-muted-foreground truncate">
|
||||
{{ presetUser.preset.iceServers.length }} ICE Server{{
|
||||
presetUser.preset.iceServers.length === 1 ? "" : "s"
|
||||
}}
|
||||
configured
|
||||
{{ presetUser.preset.iceServers.length }} {{ presetUser.preset.iceServers.length === 1 ? t('iceServerConfigured') : t('iceServersConfigured') }} {{ t('configured') }}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter class="flex justify-between gap-2 ml-auto">
|
||||
@@ -91,6 +88,7 @@ import EditPresetDialog from "~/components/app/EditPresetDialog.vue";
|
||||
import { Edit, Share2, Trash, Plus } from "lucide-vue-next";
|
||||
import type { ApiResponse } from "~/lib/types/PresetGetResponse";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { user } = useUser();
|
||||
const { data, refresh } = await useFetch<ApiResponse>("/api/presets", {
|
||||
cache: "no-cache",
|
||||
@@ -120,34 +118,34 @@ function editPreset(presetUser: any) {
|
||||
}
|
||||
|
||||
async function deletePreset(id: string) {
|
||||
if (!confirm("Are you sure you want to delete this preset?")) return;
|
||||
if (!confirm(t('deletePresetConfirm'))) return;
|
||||
|
||||
try {
|
||||
await $fetch(`/api/presets/${id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
toast.success("Preset deleted successfully");
|
||||
toast.success(t('presetDeletedSuccessfully'));
|
||||
refresh();
|
||||
} catch (error) {
|
||||
toast.error("Failed to delete preset");
|
||||
toast.error(t('failedToDeletePreset'));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleShare(preset: any) {
|
||||
if (!confirm("Do you want to share this preset?")) return;
|
||||
if (!confirm(t('sharePresetConfirm'))) return;
|
||||
try {
|
||||
const response = await $fetch(`/api/presets/${preset.id}/share`, {
|
||||
method: "POST",
|
||||
});
|
||||
if (!response.success) {
|
||||
toast.error("Failed to generate shareable link");
|
||||
toast.error(t('failedToGenerateShareableLink'));
|
||||
return;
|
||||
}
|
||||
const shareableLink = `${window.location.origin}/presets/shared/${preset.id}`;
|
||||
navigator.clipboard.writeText(shareableLink);
|
||||
toast.success("Link copied to clipboard");
|
||||
toast.success(t('linkCopiedToClipboard'));
|
||||
} catch (error) {
|
||||
toast.error("Failed to share preset");
|
||||
toast.error(t('failedToSharePreset'));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center gap-6 mt-10 px-4">
|
||||
<div class="flex space-x-4 items-center">
|
||||
<Button @click="startScreenShare"> screenshare </Button>
|
||||
<Button @click="startScreenShare"> {{ $t('screenshare') }} </Button>
|
||||
<PresetSelect />
|
||||
</div>
|
||||
<p v-if="streamerStore.code" class="font-mono">{{ streamerStore.code }}</p>
|
||||
|
||||
45
i18n/locales/en.json
Normal file
45
i18n/locales/en.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"welcome": "Welcome",
|
||||
"language": "Language",
|
||||
"selectLanguage": "Select language",
|
||||
"home": "Home",
|
||||
"about": "About",
|
||||
"contact": "Contact",
|
||||
"stream": "Stream",
|
||||
"presets": "Presets",
|
||||
"effortlessScreensharing": "effortless screensharing powered by webrtc",
|
||||
"hostInstead": "stream instead?",
|
||||
"enterCodeToJoinStream": "enter code to join stream",
|
||||
"streamEnded": "stream ended",
|
||||
"enterAnotherCode": "Enter another code",
|
||||
"disconnect": "Disconnect",
|
||||
"fullscreen": "Fullscreen",
|
||||
"screenshare": "screenshare",
|
||||
"loadingPresets": "Loading presets...",
|
||||
"selectAPreset": "Select a preset",
|
||||
"noPresetsAvailable": "No presets available",
|
||||
"default": "default",
|
||||
"createNewPreset": "Create New Preset",
|
||||
"presetName": "Preset name",
|
||||
"iceServersJson": "Ice Servers (JSON)",
|
||||
"setAsDefaultPreset": "Set as default preset",
|
||||
"setAsDefaultPresetDescription": "This preset will be selected by default on the preset selector.",
|
||||
"save": "Save",
|
||||
"editPreset": "Edit Preset",
|
||||
"editPresetDescription": "Make changes to your preset here. Click save when you're done.",
|
||||
"presetCreatedSuccessfully": "Preset created successfully!",
|
||||
"presetUpdatedSuccessfully": "Preset updated successfully!",
|
||||
"failedToCreatePreset": "Failed to create preset.",
|
||||
"failedToUpdatePreset": "Failed to update preset.",
|
||||
"createdBy": "Created by",
|
||||
"iceServerConfigured": "ICE Server",
|
||||
"iceServersConfigured": "ICE Servers",
|
||||
"configured": "configured",
|
||||
"deletePresetConfirm": "Are you sure you want to delete this preset?",
|
||||
"presetDeletedSuccessfully": "Preset deleted successfully",
|
||||
"failedToDeletePreset": "Failed to delete preset",
|
||||
"sharePresetConfirm": "Do you want to share this preset?",
|
||||
"failedToGenerateShareableLink": "Failed to generate shareable link",
|
||||
"linkCopiedToClipboard": "Link copied to clipboard",
|
||||
"failedToSharePreset": "Failed to share preset"
|
||||
}
|
||||
45
i18n/locales/es.json
Normal file
45
i18n/locales/es.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"welcome": "Bienvenido",
|
||||
"language": "Idioma",
|
||||
"selectLanguage": "Seleccionar idioma",
|
||||
"home": "Inicio",
|
||||
"about": "Acerca de",
|
||||
"contact": "Contacto",
|
||||
"stream": "Transmisión",
|
||||
"presets": "Ajustes predefinidos",
|
||||
"effortlessScreensharing": "comparte pantalla sin complicaciones",
|
||||
"hostInstead": "¿prefieres compartir pantalla?",
|
||||
"enterCodeToJoinStream": "introduce un código para unirse a la transmisión",
|
||||
"streamEnded": "transmisión finalizada",
|
||||
"enterAnotherCode": "introduce otro código",
|
||||
"disconnect": "Desconectar",
|
||||
"fullscreen": "Pantalla completa",
|
||||
"screenshare": "compartir pantalla",
|
||||
"loadingPresets": "Cargando ajustes...",
|
||||
"selectAPreset": "Seleccionar un ajuste",
|
||||
"noPresetsAvailable": "No hay ajustes disponibles",
|
||||
"default": "predeterminado",
|
||||
"createNewPreset": "Crear nuevo ajuste",
|
||||
"presetName": "Nombre del ajuste",
|
||||
"iceServersJson": "Servidores ICE (JSON)",
|
||||
"setAsDefaultPreset": "Establecer como ajuste predeterminado",
|
||||
"setAsDefaultPresetDescription": "Este ajuste se seleccionará de forma predeterminada en el selector de ajustes predefinidos.",
|
||||
"save": "Guardar",
|
||||
"editPreset": "Editar ajuste",
|
||||
"editPresetDescription": "Realiza cambios en su ajuste aquí. Haga clic en guardar cuando haya terminado.",
|
||||
"presetCreatedSuccessfully": "¡Ajuste creado correctamente!",
|
||||
"presetUpdatedSuccessfully": "¡Ajuste actualizado correctamente!",
|
||||
"failedToCreatePreset": "Error al crear el ajuste.",
|
||||
"failedToUpdatePreset": "Error al actualizar el ajuste.",
|
||||
"createdBy": "Creado por",
|
||||
"iceServerConfigured": "Servidor ICE",
|
||||
"iceServersConfigured": "Servidores ICE",
|
||||
"configured": "configurado",
|
||||
"deletePresetConfirm": "¿Está seguro de que desea eliminar este ajuste?",
|
||||
"presetDeletedSuccessfully": "Ajuste eliminado correctamente",
|
||||
"failedToDeletePreset": "Error al eliminar el ajuste",
|
||||
"sharePresetConfirm": "¿Desea compartir este ajuste?",
|
||||
"failedToGenerateShareableLink": "Error al generar el enlace para compartir",
|
||||
"linkCopiedToClipboard": "Enlace copiado al portapapeles",
|
||||
"failedToSharePreset": "Error al compartir el ajuste"
|
||||
}
|
||||
@@ -7,9 +7,6 @@ export default defineNuxtConfig({
|
||||
css: ["~/assets/css/tailwind.css"],
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
server: {
|
||||
allowedHosts: ["urods-79-145-156-36.a.free.pinggy.link"],
|
||||
},
|
||||
},
|
||||
modules: [
|
||||
"shadcn-nuxt",
|
||||
@@ -18,7 +15,32 @@ export default defineNuxtConfig({
|
||||
"nuxt-cron",
|
||||
"@clerk/nuxt",
|
||||
"nuxt-monaco-editor",
|
||||
"@nuxtjs/i18n",
|
||||
],
|
||||
i18n: {
|
||||
locales: [
|
||||
{
|
||||
code: "en",
|
||||
language: "en-US",
|
||||
name: "English",
|
||||
file: "en.json",
|
||||
},
|
||||
{
|
||||
code: "es",
|
||||
language: "es-ES",
|
||||
name: "Español",
|
||||
file: "es.json",
|
||||
},
|
||||
],
|
||||
defaultLocale: "en",
|
||||
langDir: "locales",
|
||||
strategy: "prefix_except_default",
|
||||
detectBrowserLanguage: {
|
||||
useCookie: true,
|
||||
cookieKey: "i18n_locale",
|
||||
redirectOn: "root",
|
||||
},
|
||||
},
|
||||
colorMode: {
|
||||
classSuffix: "",
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"dependencies": {
|
||||
"@clerk/nuxt": "^1.13.10",
|
||||
"@clerk/themes": "^2.4.46",
|
||||
"@nuxtjs/i18n": "^10.2.1",
|
||||
"@pinia/nuxt": "0.11.2",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@tanstack/vue-form": "^1.27.7",
|
||||
|
||||
1322
pnpm-lock.yaml
generated
1322
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user