Merge pull request #36 from AlexisNP/features/i18n

This commit is contained in:
AlexisNP
2024-09-08 18:00:23 +02:00
committed by GitHub
32 changed files with 1312 additions and 268 deletions

View File

@@ -5,14 +5,10 @@ useHead({
titleTemplate: (titleChunk) => {
return titleChunk ? `${titleChunk} — TTTools` : "TTTools";
},
htmlAttrs: {
lang: "fr"
},
meta: [
{ name: "charset", content: "UTF-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1.0" },
{ name: "author", content: "Alexis Pelé" },
{ name: "description", content: "Tools destined to players and game master, helping them visualize their worlds better." },
{ name: "generator", content: "Nuxt" },
{ name: "msapplication-TileColor", content: "00aba9" },
{ name: "theme-color", content: "00aba9" },

View File

@@ -29,10 +29,8 @@ const eventDetails = ref<HTMLElement>()
const emit = defineEmits(["query:close-popover"])
const dateDifference: string = getRelativeString(defaultDate, props.event.startDate)
const dateDuration: string | null = props.event.endDate
? getRelativeString(props.event.startDate, props.event.endDate, "compact")
: null
const dateDifference = computed<string>(() => getRelativeString(defaultDate, props.event.startDate))
const dateDuration = computed<string | null>(() => props.event.endDate ? getRelativeString(props.event.startDate, props.event.endDate, "compact") : null)
function handleJumpToDate(date: RPGDate) {
jumpToDate(date)
@@ -87,8 +85,14 @@ function deployDeleteModal() {
</template>
<template v-else>
<p class="font-semibold">
Du {{ getFormattedDateTitle(event.startDate, true) }} au
{{ getFormattedDateTitle(event.endDate, true) }}
{{
$t('entity.calendar.date.fromTo',
{
startDate: getFormattedDateTitle(event.startDate, true),
endDate: getFormattedDateTitle(event.endDate, true)
}
)
}}
</p>
</template>
</div>
@@ -105,7 +109,8 @@ function deployDeleteModal() {
</p>
<template v-if="dateDuration">
<p class="text-sm italic dark:opacity-75 flex items-center gap-1">
<PhHourglassMedium size="16" weight="fill" /> Pendant {{ dateDuration }}
<PhHourglassMedium size="16" weight="fill" />
{{ $t('entity.calendar.date.while', { duration: dateDuration } )}}
</p>
</template>
</div>
@@ -146,8 +151,8 @@ function deployDeleteModal() {
<UiCommand>
<UiCommandList>
<UiCommandGroup>
<UiCommandItem value="edit-event" @select="deployEditModal"> Modifier </UiCommandItem>
<UiCommandItem value="delete-event" @select="deployDeleteModal"> Supprimer </UiCommandItem>
<UiCommandItem value="edit-event" @select="deployEditModal"> {{ $t('ui.action.edit') }} </UiCommandItem>
<UiCommandItem value="delete-event" @select="deployDeleteModal"> {{ $t('ui.action.delete') }} </UiCommandItem>
</UiCommandGroup>
</UiCommandList>
</UiCommand>
@@ -162,7 +167,7 @@ function deployDeleteModal() {
title="Naviguer au début"
@click="handleJumpToDate(event.startDate!)"
>
<PhHourglassHigh size="16" weight="fill" /> Début
<PhHourglassHigh size="16" weight="fill" /> {{ $t('entity.calendar.event.isStart') }}
</button>
</UiBadge>
<UiBadge class="hover:bg-indigo-400 dark:hover:bg-slate-300" as-child title="Naviguer à la fin">
@@ -171,7 +176,7 @@ function deployDeleteModal() {
title="Naviguer à la fin"
@click="handleJumpToDate(event.endDate!)"
>
<PhHourglassLow size="16" weight="fill" /> Fin
<PhHourglassLow size="16" weight="fill" /> {{ $t('entity.calendar.event.isEnd') }}
</button>
</UiBadge>
</nav>
@@ -180,11 +185,13 @@ function deployDeleteModal() {
<UiTooltip>
<UiTooltipTrigger as-child>
<UiBadge class="absolute -top-2 right-2 flex gap-1">
<PhEye size="16" weight="fill" /> Évènement privé
<PhEye size="16" weight="fill" /> {{ $t('entity.calendar.event.isHidden') }}
</UiBadge>
</UiTooltipTrigger>
<UiTooltipContent>
<p>Cet évènement est uniquement visible pour vous</p>
<p>
{{ $t('entity.calendar.event.hiddenTooltip') }}
</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>

View File

@@ -25,7 +25,7 @@ const { revealAdvancedSearch } = useCalendar()
<li>
<UiButton search-slash @click="revealAdvancedSearch()">
<PhMagnifyingGlass size="20" weight="light" />
Recherche avancée
{{ $t('entity.advancedSearch.title') }}
</UiButton>
</li>
<li>

View File

@@ -9,6 +9,8 @@ import {
PhCaretRight
} from "@phosphor-icons/vue"
const { t } = useI18n()
interface DirectionLabels {
pastFar: string
pastNear: string
@@ -23,35 +25,35 @@ const activeDirectionLabels: ComputedRef<DirectionLabels> = computed(() => {
switch (currentConfig.viewType) {
case "month":
return {
pastFar: "Année précédente",
pastNear: "Mois précédent",
futureNear: "Mois suivant",
futureFar: "Année suivante"
pastFar: t("entity.calendar.years.prevSingular"),
pastNear: t("entity.calendar.months.prevSingular"),
futureNear: t("entity.calendar.months.nextSingular"),
futureFar: t("entity.calendar.years.nextSingular")
}
case "year":
return {
pastFar: "Décennie précédente",
pastNear: "Année précédente",
futureNear: "Année suivante",
futureFar: "Décennie suivante"
pastFar: t("entity.calendar.decades.prevSingular"),
pastNear: t("entity.calendar.years.prevSingular"),
futureNear: t("entity.calendar.years.nextSingular"),
futureFar: t("entity.calendar.decades.nextSingular")
}
case "decade":
return {
pastFar: "Siècle précédent",
pastNear: "Décennie précédente",
futureNear: "Décennie suivante",
futureFar: "Siècle suivant"
pastFar: t("entity.calendar.centuries.prevSingular"),
pastNear: t("entity.calendar.decades.prevSingular"),
futureNear: t("entity.calendar.decades.nextSingular"),
futureFar: t("entity.calendar.centuries.nextSingular")
}
case "century":
default:
return {
pastFar: "Millénaire précédent",
pastNear: "Siècle précédent",
futureNear: "Siècle suivant",
futureFar: "Millénaire suivant"
pastFar: t("entity.calendar.millenias.prevSingular"),
pastNear: t("entity.calendar.centuries.prevSingular"),
futureNear: t("entity.calendar.centuries.nextSingular"),
futureFar: t("entity.calendar.millenias.nextSingular")
}
}
})

View File

@@ -87,7 +87,9 @@ function handleGotoPreviousEventPage(position: "next" | "prev" = "next") {
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>
<p>Précédente page à évènements</p>
<p>
{{ $t('entity.calendar.event.prevPage') }}
</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
@@ -106,7 +108,9 @@ function handleGotoPreviousEventPage(position: "next" | "prev" = "next") {
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>
<p>Prochaine page à évènements</p>
<p>
{{ $t('entity.calendar.event.nextPage') }}
</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>

View File

@@ -14,17 +14,9 @@ const isDefaultDate: ComputedRef<boolean> = computed<boolean>(() => areDatesIden
<UiTooltipProvider :delay-duration="250">
<UiTooltip>
<UiTooltipTrigger as-child>
<ClientOnly>
<UiButton size="sm" :disabled="isDefaultDate" @click="jumpToDefaultDate">
Aujourd'hui
</UiButton>
<template #fallback>
<UiButton size="sm">
Aujourd'hui
</UiButton>
</template>
</ClientOnly>
<UiButton size="sm" :disabled="isDefaultDate" @click="jumpToDefaultDate">
{{ $t('entity.calendar.date.today') }}
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>
<p>{{ defaultDateFormatted }}</p>

View File

@@ -18,7 +18,9 @@ const viewTypeTitle = computed(() => getViewTypeTitle(currentConfig.viewType))
</UiButton>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent :side="'bottom'" :collision-padding="30">
<UiDropdownMenuLabel>Mode d'affichage</UiDropdownMenuLabel>
<UiDropdownMenuLabel>
{{ $t('ui.displayMode') }}
</UiDropdownMenuLabel>
<UiDropdownMenuSeparator />
<UiDropdownMenuItem
v-for="option in viewTypeOptions"

View File

@@ -30,7 +30,9 @@ function handleClose() {
<span v-if="calendarSkeletonName">
{{ calendarSkeletonName }}
</span>
<span v-else>Nouveau calendrier</span>
<span v-else>
{{ $t('entity.calendar.createDialog.title') }}
</span>
</span>
</UiAlertDialogTitle>

View File

@@ -53,10 +53,12 @@ function handleClosing() {
@interact-outside="handleClosing"
@pointer-down-outside="handleClosing"
>
<UiAlertDialogTitle> Êtes-vous sûr de supprimer ce calendrier ?</UiAlertDialogTitle>
<UiAlertDialogTitle>
{{ $t('entity.calendar.deleteDialog.title') }}
</UiAlertDialogTitle>
<UiAlertDialogDescription>
Les évènements ne seront plus accessibles et vous ne pourrez plus récupérer les données !
{{ $t('entity.calendar.deleteDialog.subtitle') }}
</UiAlertDialogDescription>
<form @submit.prevent="handleAction">
@@ -70,13 +72,13 @@ function handleClosing() {
<footer class="flex gap-2 justify-between">
<UiButton type="button" size="sm" variant="outline" @click="handleClosing">
Retour
{{ $t('ui.action.back') }}
</UiButton>
<div class="flex gap-2 justify-end">
<Transition name="fade-delay">
<UiButton v-if="isLoading" type="button" size="sm" variant="destructive">
Annuler
{{ $t('ui.action.cancel') }}
</UiButton>
</Transition>
@@ -85,7 +87,7 @@ function handleClosing() {
<PhCircleNotch v-if="isLoading" size="20" class="animate-spin"/>
</Transition>
Supprimer "{{ calendar?.name }}"
{{ $t('entity.deleteOne', { entity: calendar?.name }) }}
</UiButton>
</div>
</footer>

View File

@@ -78,19 +78,22 @@ function handleFormCancel() {
<UiTabsTrigger value="global" class="font-bold">
<div class="flex items-center gap-1">
<PhWrench size="18" weight="fill" />
Général
{{ $t('entity.calendar.createDialog.tabs.general.title') }}
</div>
</UiTabsTrigger>
<UiTabsTrigger value="months" class="font-bold">
<div class="flex items-center gap-1">
<PhCalendarDots size="18" weight="fill" />
Mois de l'année
{{ $t('entity.calendar.createDialog.tabs.months.title') }}
</div>
</UiTabsTrigger>
<UiTabsTrigger value="today" class="font-bold">
<div class="flex items-center gap-1">
<PhAlarm size="18" weight="fill" />
Aujourd'hui
{{ $t('entity.calendar.createDialog.tabs.today.title') }}
</div>
</UiTabsTrigger>
</UiTabsList>
@@ -116,7 +119,7 @@ function handleFormCancel() {
<footer class="flex justify-end gap-2 mt-6">
<UiButton type="button" variant="destructive" @click="handleFormCancel">
Annuler
{{ $t('ui.action.cancel') }}
</UiButton>
<UiButton type="submit" :disabled="!validSkeleton || isCreatingCalendar">
@@ -124,7 +127,7 @@ function handleFormCancel() {
<PhCircleNotch v-if="isCreatingCalendar" size="20" class="opacity-50 animate-spin"/>
</Transition>
Créer
{{ $t('ui.action.save') }}
</UiButton>
</footer>
</form>

View File

@@ -103,7 +103,7 @@ function handleCancel() {
type="text"
name="new-event-title"
required
placeholder="Titre de l'évènement"
:placeholder="$t('entity.calendar.event.title')"
class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600"
>
</div>
@@ -113,8 +113,7 @@ function handleCancel() {
id="new-event-description"
v-model="eventSkeleton.description"
name="new-event-description"
placeholder="Ajouter une description"
class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600"
:placeholder="$t('entity.addDescription')" class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600"
/>
</div>
@@ -124,7 +123,7 @@ function handleCancel() {
<CalendarInputRPGDate
v-model:model-value="eventSkeleton.startDate"
placeholder="Date de début"
:placeholder="$t('entity.calendar.date.start')"
:initial-date="props.date"
:required="true"
/>
@@ -133,7 +132,7 @@ function handleCancel() {
<CalendarInputRPGDate
v-model:model-value="eventSkeleton.endDate"
placeholder="Date de fin"
:placeholder="$t('entity.calendar.date.end')"
:initial-date="props.date"
/>
</div>
@@ -143,7 +142,7 @@ function handleCancel() {
<div class="flex items-center gap-4">
<PhTag size="18" weight="fill" />
<CalendarInputEventCategory v-model="eventSkeleton.category" placeholder="Ajouter une catégorie principale" />
<CalendarInputEventCategory v-model="eventSkeleton.category" :placeholder="$t('entity.category.addPrimary')" />
</div>
</div>
@@ -151,7 +150,7 @@ function handleCancel() {
<div class="flex items-center gap-4">
<PhTag size="18" weight="fill" />
<CalendarInputEventCategories v-model="eventSkeleton.secondaryCategories" placeholder="Ajouter des catégories secondaires" />
<CalendarInputEventCategories v-model="eventSkeleton.secondaryCategories" :placeholder="$t('entity.category.addSecondaries')" />
</div>
</div> -->
@@ -164,7 +163,7 @@ function handleCancel() {
v-model="eventSkeleton.location"
type="text"
name="new-event-location"
placeholder="Ajouter un endroit"
:placeholder="$t('entity.calendar.event.addLocation')"
class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600">
</div>
</div>
@@ -178,10 +177,10 @@ function handleCancel() {
<UiSwitch id="new-event-visibility" v-model:checked="eventSkeleton.hidden" />
<UiLabel for="new-event-visibility">
<template v-if="!eventSkeleton.hidden">
Évènement visible
{{ $t('entity.calendar.event.isPublic') }}
</template>
<template v-else>
Évènement caché
{{ $t('entity.calendar.event.isHidden') }}
</template>
</UiLabel>
</div>
@@ -197,7 +196,7 @@ function handleCancel() {
<div class="flex gap-2 justify-end">
<Transition name="fade-delay">
<UiButton v-if="isLoading" type="button" size="sm" variant="destructive" @click.prevent="handleCancel">
Annuler
{{ $t('ui.action.cancel') }}
</UiButton>
</Transition>
@@ -206,7 +205,7 @@ function handleCancel() {
<PhCircleNotch v-if="isLoading" size="20" class="opacity-50 animate-spin"/>
</Transition>
Sauvegarder
{{ $t('ui.action.save') }}
</UiButton>
</div>
</div>

View File

@@ -69,10 +69,10 @@ function handleCancel(): void {
@interact-outside="handleClosing"
@pointer-down-outside="handleClosing"
>
<UiAlertDialogTitle> Supprimer l'évènement</UiAlertDialogTitle>
<UiAlertDialogTitle>{{ $t('entity.calendar.event.deleteDialog.title') }}</UiAlertDialogTitle>
<UiAlertDialogDescription>
Les données associés à cet évènement seront supprimées et vous ne pourrez plus les récupérer !
{{ $t('entity.calendar.event.deleteDialog.subtitle') }}
</UiAlertDialogDescription>
<form @submit.prevent="handleAction">
@@ -86,13 +86,13 @@ function handleCancel(): void {
<footer class="flex gap-2 justify-between">
<UiButton type="button" size="sm" variant="outline" @click="() => isDeleteEventModalOpen = false">
Retour
{{ $t('ui.action.back') }}
</UiButton>
<div class="flex gap-2 justify-end">
<Transition name="fade-delay">
<UiButton v-if="isLoading" type="button" size="sm" variant="destructive" @click.prevent="handleCancel">
Annuler
{{ $t('ui.action.cancel') }}
</UiButton>
</Transition>
@@ -101,7 +101,7 @@ function handleCancel(): void {
<PhCircleNotch v-if="isLoading" size="20" class="animate-spin"/>
</Transition>
Supprimer
{{ $t('ui.action.delete') }}
</UiButton>
</div>
</footer>

View File

@@ -70,10 +70,12 @@ function handleCancel() {
@pointer-down-outside="(e) => e.preventDefault()"
>
<header class="pl-8 grid gap-y-2">
<UiDialogTitle> Modifier l'évènement</UiDialogTitle>
<UiDialogTitle>
{{ $t('entity.calendar.event.editDialog.title') }}
</UiDialogTitle>
<UiDialogDescription>
Mettre à jour les données de l'évènement
{{ $t('entity.calendar.event.editDialog.subtitle') }}
</UiDialogDescription>
</header>
@@ -89,7 +91,7 @@ function handleCancel() {
type="text"
name="new-event-title"
required
placeholder="Titre de l'évènement"
:placeholder="$t('entity.calendar.event.title')"
class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600"
>
</div>
@@ -100,7 +102,7 @@ function handleCancel() {
id="new-event-description"
v-model="eventSkeleton.description"
name="new-event-description"
placeholder="Ajouter une description"
:placeholder="$t('entity.addDescription')"
class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600"
/>
</div>
@@ -111,7 +113,7 @@ function handleCancel() {
<CalendarInputRPGDate
v-model:model-value="eventSkeleton.startDate"
placeholder="Date de début"
:placeholder="$t('entity.calendar.date.start')"
:initial-date="lastActiveEvent?.startDate"
:required="true"
/>
@@ -120,7 +122,7 @@ function handleCancel() {
<CalendarInputRPGDate
v-model:model-value="eventSkeleton.endDate"
placeholder="Date de fin"
:placeholder="$t('entity.calendar.date.end')"
:initial-date="lastActiveEvent?.endDate"
/>
</div>
@@ -141,7 +143,7 @@ function handleCancel() {
<PhTag size="18" weight="fill" />
<div class="w-1/2">
<CalendarInputEventCategories v-model="eventSkeleton.secondaryCategories" placeholder="Ajouter des catégories secondaires" />
<CalendarInputEventCategories v-model="eventSkeleton.secondaryCategories" :placeholder="Ajouter des catégories secondaires" />
</div>
</div>
</div> -->
@@ -155,7 +157,7 @@ function handleCancel() {
v-model="eventSkeleton.location"
type="text"
name="new-event-location"
placeholder="Ajouter un endroit"
:placeholder="$t('entity.calendar.event.addLocation')"
class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600">
</div>
</div>
@@ -169,10 +171,10 @@ function handleCancel() {
<UiSwitch id="new-event-visibility" v-model:checked="eventSkeleton.hidden" />
<UiLabel for="new-event-visibility">
<template v-if="!eventSkeleton.hidden">
Évènement visible par tous
{{ $t('entity.calendar.event.isPublic') }}
</template>
<template v-else>
Évènement caché
{{ $t('entity.calendar.event.isHidden') }}
</template>
</UiLabel>
</div>
@@ -189,7 +191,7 @@ function handleCancel() {
<footer class="flex gap-2 justify-end">
<Transition name="fade-delay">
<UiButton v-if="isLoading" type="button" size="sm" variant="destructive" @click.prevent="handleCancel">
Annuler
{{ $t('ui.action.cancel') }}
</UiButton>
</Transition>
@@ -198,7 +200,7 @@ function handleCancel() {
<PhCircleNotch v-if="isLoading" size="20" class="animate-spin"/>
</Transition>
Enregistrer
{{ $t('ui.action.save') }}
</UiButton>
</footer>
</form>

View File

@@ -60,8 +60,8 @@ const filteredCategories = computed(() =>
class="w-fit h-[33vh] p-0"
>
<UiCommand v-model="modelBuffer" v-model:searchTerm="searchTerm" :multiple="true">
<UiCommandInput placeholder="Rechercher les catégories" />
<UiCommandEmpty>Aucune catégorie trouvée.</UiCommandEmpty>
<UiCommandInput :placeholder="$t('entity.category.search')" />
<UiCommandEmpty>{{ $t('entity.category.notFoundAny') }}</UiCommandEmpty>
<UiCommandList>
<UiCommandGroup>
<UiCommandItem

View File

@@ -52,8 +52,8 @@ const filteredCategories = computed(() =>
class="w-fit h-[33vh] p-0"
>
<UiCommand v-model="model" v-model:searchTerm="searchTerm">
<UiCommandInput placeholder="Rechercher les catégories" />
<UiCommandEmpty>Aucune catégorie trouvée.</UiCommandEmpty>
<UiCommandInput :placeholder="$t('entity.category.search')" />
<UiCommandEmpty>{{ $t('entity.category.notFoundAny') }}</UiCommandEmpty>
<UiCommandList>
<UiCommandGroup>
<UiCommandItem

View File

@@ -67,10 +67,28 @@ useSortable(monthSortableList, model.value, { animation: 150, handle: ".handle"
<template>
<div class="grid md:grid-cols-12 gap-4 items-center">
<div class="md:col-start-2 md:col-span-5">
<UiInput id="new-month-name" ref="monthNameRef" v-model="monthName" type="text" name="newMonthName" placeholder="Nom du mois" :class="cn({ 'border-red-600': monthNameIsTaken })"/>
<UiInput
id="new-month-name"
ref="monthNameRef"
v-model="monthName"
type="text"
name="newMonthName"
:placeholder="$t('entity.calendar.months.inputName')"
:class="cn({ 'border-red-600': monthNameIsTaken })"
/>
</div>
<div class="md:col-span-5">
<UiInput id="new-month-days" ref="monthDaysRef" v-model="monthDays" type="number" name="newMonthName" placeholder="Nombre de jours" min="0" step="1" class="invalid:border-red-600" />
<UiInput
id="new-month-days"
ref="monthDaysRef"
v-model="monthDays"
type="number"
name="newMonthName"
:placeholder="$t('entity.calendar.months.daysNb')"
min="0"
step="1"
class="invalid:border-red-600"
/>
</div>
<div class="md:col-span-1">
<UiButton size="icon" class="rounded-full h-8 w-8" :disabled="!validNewMonth" @click.prevent="addMonthToModel">
@@ -78,7 +96,10 @@ useSortable(monthSortableList, model.value, { animation: 150, handle: ".handle"
</UiButton>
</div>
<div class="md:col-span-full">
<div class="border-[1px] dark:border-slate-800 p-4 rounded-sm max-h-80 overflow-y-auto" :class="model.length ? 'md:grid md:grid-cols-12 md:gap-4 md:items-center' : ''">
<div
class="border-[1px] dark:border-slate-800 p-4 rounded-sm max-h-80 overflow-y-auto"
:class="model.length ? 'md:grid md:grid-cols-12 md:gap-4 md:items-center' : ''"
>
<div v-if="model.length" class="hidden md:block col-span-1">
<ul class="grid gap-y-4 justify-center">
<li v-for="(m, i) in model" :key="`num-${m.name}`">
@@ -88,6 +109,7 @@ useSortable(monthSortableList, model.value, { animation: 150, handle: ".handle"
</li>
</ul>
</div>
<div class="md:col-span-11">
<ul ref="monthSortableList" class="grid gap-y-2" :class="model.length ? 'visible' : 'absolute invisible'">
<template v-if="model.length">
@@ -102,12 +124,23 @@ useSortable(monthSortableList, model.value, { animation: 150, handle: ".handle"
{{ m.name }}
</div>
</div>
<div class="md:col-span-2">
<UiInput :id="`month-days-n${i}`" v-model="m.days" class="bg-transparent border-none" type="number" :name="`monthDays-n${i}`" placeholder="Nombre de jours" min="0" />
<UiInput
:id="`month-days-n${i}`"
v-model="m.days"
class="bg-transparent border-none"
type="number"
:name="`monthDays-n${i}`"
:placeholder="$t('entity.calendar.months.daysNb')"
min="0"
/>
</div>
<div class="md:col-span-1">
jour(s)
{{ $t('entity.calendar.months.daysMaybePlural') }}
</div>
<div class="md:col-start-12">
<UiTooltipProvider>
<UiTooltip>
@@ -117,7 +150,9 @@ useSortable(monthSortableList, model.value, { animation: 150, handle: ".handle"
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>
<p>Supprimer {{ m.name }} du calendrier</p>
<p>
{{ $t('entity.calendar.months.deleteOne', { month: m.name }) }}
</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
@@ -127,7 +162,9 @@ useSortable(monthSortableList, model.value, { animation: 150, handle: ".handle"
</ul>
</div>
<template v-if="!model.length">
<p class="col-span-12 text-lg text-center italic opacity-50">Aucun mois pour l'instant</p>
<p class="col-span-12 text-lg text-center italic opacity-50">
{{ $t('entity.calendar.months.none') }}
</p>
</template>
</div>
</div>

View File

@@ -33,7 +33,7 @@ watch(model.value, (n, _o) => {
<template>
<div class="flex gap-2 items-stretch">
<UiInput id="new-month-current-day" v-model="model.day" type="number" name="newMonthCurrentDay" placeholder="Jour" min="1" step="1" class="invalid:border-red-600" />
<UiInput id="new-month-current-day" v-model="model.day" type="number" name="newMonthCurrentDay" :placeholder="$t('entity.calendar.months.daysNameSingular')" min="1" step="1" class="invalid:border-red-600" />
<UiDropdownMenu>
<UiDropdownMenuTrigger as-child :disabled="props.availableMonths.length < 1">
@@ -41,18 +41,20 @@ watch(model.value, (n, _o) => {
<PhCalendarBlank size="18" weight="fill" />
<template v-if="props.availableMonths.length < 1">
Aucun mois disponible
{{ $t('entity.calendar.months.noneAvailable') }}
</template>
<template v-else-if="model.month && typeof model.month === 'number'">
{{ props.availableMonths[model.month - 1].name }}
</template>
<template v-else>
Choisir le mois
{{ $t('entity.calendar.months.chooseOne') }}
</template>
</UiButton>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent :side="'bottom'" :collision-padding="30">
<UiDropdownMenuLabel>Mois disponibles</UiDropdownMenuLabel>
<UiDropdownMenuLabel>
{{ $t('entity.calendar.months.available') }}
</UiDropdownMenuLabel>
<UiDropdownMenuSeparator />
<UiDropdownMenuItem
v-for="m in props.availableMonths"
@@ -64,6 +66,6 @@ watch(model.value, (n, _o) => {
</UiDropdownMenuContent>
</UiDropdownMenu>
<UiInput id="new-month-current-day" v-model="model.year" type="number" name="newMonthCurrentYear" placeholder="Année" step="1" class="invalid:border-red-600" />
<UiInput id="new-month-current-day" v-model="model.year" type="number" name="newMonthCurrentYear" :placeholder="$t('entity.calendar.years.nameSingular')" step="1" class="invalid:border-red-600" />
</div>
</template>

View File

@@ -241,11 +241,11 @@ function handleCategorySelect(e: (Category)) {
}"
>
<VisuallyHidden>
<UiDialogTitle> Recherche avancée </UiDialogTitle>
<UiDialogTitle> {{ $t('entity.advancedSearch.title') }} </UiDialogTitle>
</VisuallyHidden>
<VisuallyHidden>
<UiDialogDescription>
Rechercher les données disponibles sur le calendrier
{{ $t('entity.advancedSearch.title') }}
</UiDialogDescription>
</VisuallyHidden>
@@ -256,7 +256,7 @@ function handleCategorySelect(e: (Category)) {
id="search"
v-model:model-value="searchQuery"
type="text"
placeholder="Rechercher le calendrier"
:placeholder="$t('entity.advancedSearch.ctaPlaceholder')"
class="pl-10 py-6 text-lg"
autocomplete="off"
/>
@@ -274,10 +274,10 @@ function handleCategorySelect(e: (Category)) {
@update:model-value="handleEntitySwitch()"
>
<UiToggleGroupItem value="events" aria-label="Uniquement les évènements">
Évènements
{{ $t('entity.calendar.event.namePlural') }}
</UiToggleGroupItem>
<UiToggleGroupItem value="characters" aria-label="Uniquement les personnages">
Personnages
{{ $t('entity.character.namePlural') }}
</UiToggleGroupItem>
</UiToggleGroup>
</div>
@@ -298,7 +298,7 @@ function handleCategorySelect(e: (Category)) {
class="w-full"
>
<ComboboxAnchor as-child>
<ComboboxInput placeholder="Catégories" as-child>
<ComboboxInput :placeholder="$t('entity.category.namePlural')" as-child>
<UiTagsInputInput
class="w-full px-3"
:class="selectedCategories.length > 0 ? 'mt-2' : ''"
@@ -341,7 +341,7 @@ function handleCategorySelect(e: (Category)) {
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>
<p>Plus ancien</p>
<p>{{ $t('entity.advancedSearch.older') }}</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
@@ -358,7 +358,7 @@ function handleCategorySelect(e: (Category)) {
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>
<p>Plus récent</p>
<p>{{ $t('entity.advancedSearch.newer') }}</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>

View File

@@ -16,9 +16,7 @@ defineEmits<{
const { getRelativeString, defaultDate, getFormattedDateTitle } = useCalendar()
const dateDifference: string = getRelativeString(defaultDate, props.event.startDate)
const dateDuration: string | null = props.event.endDate
? getRelativeString(props.event.startDate, props.event.endDate, "compact")
: null
const dateDuration = computed<string | null>(() => props.event.endDate ? getRelativeString(props.event.startDate, props.event.endDate, "compact") : null)
</script>
<template>
@@ -53,8 +51,14 @@ const dateDuration: string | null = props.event.endDate
</template>
<template v-else>
<p class="font-semibold text-sm opacity-75">
Du {{ getFormattedDateTitle(event.startDate, true) }} au
{{ getFormattedDateTitle(event.endDate, true) }}
{{
$t('entity.calendar.date.fromTo',
{
startDate: getFormattedDateTitle(event.startDate, true),
endDate: getFormattedDateTitle(event.endDate, true)
}
)
}}
</p>
</template>
<div v-if="event.hidden" class="flex justify-end">
@@ -62,11 +66,13 @@ const dateDuration: string | null = props.event.endDate
<UiTooltip>
<UiTooltipTrigger as-child>
<UiBadge class="flex gap-1">
<PhEye size="16" weight="fill" /> Évènement privé
<PhEye size="16" weight="fill" /> {{ $t('entity.calendar.event.isHidden') }}
</UiBadge>
</UiTooltipTrigger>
<UiTooltipContent>
<p>Cet évènement est uniquement visible pour vous</p>
<p>
{{ $t('entity.calendar.event.hiddenTooltip') }}
</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
@@ -84,7 +90,7 @@ const dateDuration: string | null = props.event.endDate
</p>
<template v-if="dateDuration">
<p class="w-fit text-sm italic opacity-75 flex items-center gap-1">
<PhHourglassMedium size="16" weight="fill" /> Pendant {{ dateDuration }}
<PhHourglassMedium size="16" weight="fill" /> {{ $t('entity.calendar.date.while', { duration: dateDuration } )}}
</p>
</template>
</div>

View File

@@ -34,7 +34,9 @@ function handleMenuItemAction(actionType: SidebarMenuActionType) {
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent :side="'right'" :side-offset="6">
<p>Mondes</p>
<p>
{{ $t('entity.world.namePlural') }}
</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>

View File

@@ -11,6 +11,8 @@ const user = useSupabaseUser()
const userMeta = computed(() => user.value?.user_metadata)
const profileUrl: string = `${useRequestURL().origin}/my/`
const { locale, setLocale } = useI18n()
const menuOpened = ref<boolean>(false)
function closeMenu() {
@@ -64,103 +66,140 @@ function pushRoute(to: AvailableRoutes) {
:alt="userMeta?.full_name"
referrerpolicy="no-referrer"
/>
<UiAvatarFallback>Me</UiAvatarFallback>
<UiAvatarFallback>
{{ $t('ui.sidebarMenu.avatarFallback') }}
</UiAvatarFallback>
</UiAvatar>
<UiButton v-else variant="outline" size="icon">
<PhUserCircle size="18" />
</UiButton>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent class="w-fit p-0" :align="'start'" :side="'top'" :side-offset="10" :align-offset="25" :collision-padding="40">
<UiDropdownMenuContent class="w-72 p-0" :align="'start'" :side="'top'" :side-offset="10" :align-offset="25" :collision-padding="40">
<template v-if="user">
<p class="p-2 text-xs opacity-75">Connecté en tant que {{ user?.email }}</p>
<p class="p-2 text-[.7em] opacity-75">
{{ $t('ui.greeting', { user: user?.email }) }}
</p>
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="pushRoute('/my')">
<PhGlobeHemisphereWest size="20" weight="fill" />
<span>Mondes</span>
<span>
{{ $t('entity.world.namePlural') }}
</span>
</UiDropdownMenuItem>
<UiDropdownMenuSeparator />
</template>
<UiDropdownMenuSub>
<UiDropdownMenuSubTrigger class="p-0 rounded-none">
<UiDropdownMenuItem class="flex gap-[.5ch] items-center pointer-events-none">
<PhPalette size="20" weight="fill" />
<span>Apparence</span>
<UiDropdownMenuSub>
<UiDropdownMenuSubTrigger class="p-0 rounded-none">
<UiDropdownMenuItem class="flex gap-[.5ch] items-center pointer-events-none">
<PhPalette size="20" weight="fill" />
<span>
{{ $t('ui.sidebarMenu.appearance') }}
</span>
</UiDropdownMenuItem>
</UiDropdownMenuSubTrigger>
<UiDropdownMenuPortal>
<UiDropdownMenuSubContent>
<UiDropdownMenuItem
class="flex gap-[.5ch] items-center rounded-none transition-colors"
:class="cn({ 'text-emerald-600': $colorMode.preference === 'dark' })"
@select.prevent="$colorMode.preference = 'dark'"
>
<PhCheckCircle v-if="$colorMode.preference === 'dark'" size="20" weight="fill" />
<PhMoon v-else size="20" />
<span>
{{ $t('ui.dark') }}
</span>
</UiDropdownMenuItem>
</UiDropdownMenuSubTrigger>
<UiDropdownMenuPortal>
<UiDropdownMenuItem
class="flex gap-[.5ch] items-center rounded-none transition-colors"
:class="cn({ 'text-emerald-600': $colorMode.preference === 'light' })"
@select.prevent="$colorMode.preference = 'light'"
>
<PhCheckCircle v-if="$colorMode.preference === 'light'" size="20" weight="fill" />
<PhSun v-else size="20" />
<span>
{{ $t('ui.light') }}
</span>
</UiDropdownMenuItem>
<UiDropdownMenuItem
class="flex gap-[.5ch] items-center rounded-none transition-colors"
:class="cn({ 'text-emerald-600': $colorMode.preference === 'system' })"
@select.prevent="$colorMode.preference = 'system'"
>
<PhCheckCircle v-if="$colorMode.preference === 'system'" size="20" weight="fill" />
<PhLaptop v-else size="20" />
<span>
{{ $t('ui.system') }}
</span>
</UiDropdownMenuItem>
</UiDropdownMenuSubContent>
</UiDropdownMenuPortal>
</UiDropdownMenuSub>
<UiDropdownMenuSub>
<UiDropdownMenuSubTrigger class="p-0 rounded-none">
<UiDropdownMenuItem class="flex gap-[.5ch] items-center pointer-events-none">
<PhTranslate size="20" />
<span>
{{ $t('ui.sidebarMenu.language') }}
</span>
</UiDropdownMenuItem>
</UiDropdownMenuSubTrigger>
<UiDropdownMenuPortal>
<UiDropdownMenuSubContent>
<UiDropdownMenuSubContent>
<UiDropdownMenuItem
class="flex gap-[.5ch] items-center rounded-none"
:class="cn({ 'text-emerald-600': $colorMode.preference === 'dark' })"
@select.prevent="$colorMode.preference = 'dark'"
class="flex gap-[.5ch] items-center rounded-none transition-colors"
:class="cn({ 'text-emerald-600': locale === 'fr' })"
@select.prevent="setLocale('fr')"
>
<PhCheckCircle v-if="$colorMode.preference === 'dark'" size="20" weight="fill" />
<PhMoon v-else size="20" />
<span>Sombre</span>
<PhCheckCircle v-if="locale === 'fr'" size="20" weight="fill" />
<img v-else src="/images/flag-fr.png" width="20" alt="" loading="eager">
<span>Français</span>
</UiDropdownMenuItem>
<UiDropdownMenuItem
class="flex gap-[.5ch] items-center rounded-none"
:class="cn({ 'text-emerald-600': $colorMode.preference === 'light' })"
@select.prevent="$colorMode.preference = 'light'"
class="flex gap-[.5ch] items-center rounded-none transition-colors"
:class="cn({ 'text-emerald-600': locale === 'en' })"
@select.prevent="setLocale('en')"
>
<PhCheckCircle v-if="$colorMode.preference === 'light'" size="20" weight="fill" />
<PhSun v-else size="20" />
<span>Clair</span>
</UiDropdownMenuItem>
<UiDropdownMenuItem
class="flex gap-[.5ch] items-center rounded-none"
:class="cn({ 'text-emerald-600': $colorMode.preference === 'system' })"
@select.prevent="$colorMode.preference = 'system'"
>
<PhCheckCircle v-if="$colorMode.preference === 'system'" size="20" weight="fill" />
<PhLaptop v-else size="20" />
<span>Système</span>
<PhCheckCircle v-if="locale === 'en'" size="20" weight="fill" />
<img v-else src="/images/flag-uk.png" width="20" alt="" loading="eager">
<span>English</span>
</UiDropdownMenuItem>
</UiDropdownMenuSubContent>
</UiDropdownMenuPortal>
</UiDropdownMenuSub>
</UiDropdownMenuSubContent>
</UiDropdownMenuPortal>
</UiDropdownMenuSub>
<UiDropdownMenuSub>
<UiDropdownMenuSubTrigger class="p-0 rounded-none">
<UiDropdownMenuItem class="flex gap-[.5ch] items-center pointer-events-none">
<PhTranslate size="20" />
<span>Langage</span>
</UiDropdownMenuItem>
</UiDropdownMenuSubTrigger>
<UiDropdownMenuPortal>
<UiDropdownMenuSubContent>
<UiDropdownMenuSubContent>
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none">
<span>Français</span>
</UiDropdownMenuItem>
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none">
<span>English</span>
</UiDropdownMenuItem>
</UiDropdownMenuSubContent>
</UiDropdownMenuSubContent>
</UiDropdownMenuPortal>
</UiDropdownMenuSub>
<UiDropdownMenuSeparator />
<UiDropdownMenuSeparator />
<template v-if="user">
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="pushRoute('/my/settings')">
<PhGear size="20" weight="fill" />
<span>Compte</span>
<span>
{{ $t('ui.sidebarMenu.account') }}
</span>
</UiDropdownMenuItem>
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="handleLogout">
<PhSignOut size="20" weight="fill" />
<span>Déconnexion</span>
<span>
{{ $t('ui.sidebarMenu.logout') }}
</span>
</UiDropdownMenuItem>
</template>
<template v-else>
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="handleGoogleLogin">
<PhGear size="20" weight="fill" />
<span>Connexion</span>
</UiDropdownMenuItem>
</template>
<UiDropdownMenuItem v-if="!user" class="flex gap-[.5ch] items-center rounded-none" @click="handleGoogleLogin">
<PhGear size="20" weight="fill" />
<span>
{{ $t('ui.sidebarMenu.login') }}
</span>
</UiDropdownMenuItem>
</UiDropdownMenuContent>
</UiDropdownMenu>
</ClientOnly>

340
i18n.config.ts Normal file
View File

@@ -0,0 +1,340 @@
export default defineI18nConfig(() => ({
legacy: false,
availableLocales: ["fr", "en"],
fallbackLocale: "fr",
messages: {
en: {
head: {
title: "Fantasy calendars for TTRPGs",
description: "Tools for players and game masters to help them visualize fantasy worlds better.",
},
ui: {
action: {
back: "Back",
cancel: "Cancel",
save: "Save",
delete: "Delete",
edit: "Edit",
},
greeting: "Connected as {user}",
sidebarMenu: {
appearance: "Appearance",
language: "Language",
account: "Account",
login: "Log in",
logout: "Log out",
avatarFallback: "Profile",
},
dark: "Dark",
light: "Light",
system: "System",
displayMode: "Display mode"
},
common: {
title: "Title"
},
entity: {
category: {
nameSingular: "Category",
namePlural: "Categories",
search: "Search categories",
notFoundAny: "No categories found.",
addPrimary: "Add a primary category",
addSecondaries: "Add secondary categories"
},
addDescription: "Add a description",
deleteOne: "Delete \"{entity}\"",
advancedSearch: {
title: "Advanced search",
subtitle: "Search through calendar and world data",
older: "Older",
newer: "Newer",
ctaPlaceholder: "Search an event, characters…"
},
world: {
nameSingular: "World",
namePlural: "Worlds",
},
calendar: {
nameSingular: "Calendar",
namePlural: "Calendars",
addSingle: "Add a calendar",
notFoundForWorld: "No calendar for this world… yet !",
isLoading: "Calendar is loading…",
date: {
start: "Start date",
end: "End date",
afterTomorrow: "In 2 days",
tomorrow: "Tomorrow",
today: "Today",
yesterday: "Yesterday",
beforeYesterday: "2 days ago",
fromTo: "From {startDate} to {endDate}",
while: "During {duration}",
xDaysNext: "In {days} days",
xMonthsNext: "In {months} month(s)",
xYearsNext: "In {years} year(s)",
xYearsAndMonthsNext: "In {years} year(s) and {months} month(s)",
xDaysNextCompact: "{days} days",
xMonthsNextCompact: "{months} month(s)",
xYearsNextCompact: "{years} year(s)",
xYearsAndMonthsNextCompact: "{years} year(s) and {months} month(s)",
xDaysAgo: "{days} days ago",
xMonthsAgo: "{months} month(s) ago",
xYearsAgo: "{years} year(s) ago",
xYearsAndMonthsAgo: "{years} year(s) and {months} month(s) ago",
xDaysAgoCompact: "{days} days",
xMonthsAgoCompact: "{months} month(s)",
xYearsAgoCompact: "{years} year(s)",
xYearsAndMonthsAgoCompact: "{years} year(s) and {months} month(s)",
},
event: {
nameSingular: "Event",
namePlural: "Events",
title: "Event title",
isStart: "Start",
isEnd: "End",
isHidden: "Hidden event",
isPublic: "Public event",
hiddenTooltip: "This event is visible only to game masters.",
addLocation: "Add a place",
prevPage: "Previous page with events",
nextPage: "Next page with events",
editDialog: {
title: "Edit event",
subtitle: "Update event data",
},
deleteDialog: {
title: "Delete this event",
subtitle: "Data associated with this event will be lost and you won't be able to retrieve it !",
}
},
createDialog: {
title: "New calendar",
tabs: {
general: {
title: "General",
},
months: {
title: "Months",
},
today: {
title: "Today",
},
}
},
deleteDialog: {
title: "Are you sure you want to delete this calendar ?",
subtitle: "Its events won't be accessible anymore and you won't be able to retrieve the deleted data !",
},
millennia: {
nameSingular: "Millennia",
nextSingular: "Next millennia",
prevSingular: "Last millennia",
},
centuries: {
nameSingular: "Century",
nextSingular: "Next century",
prevSingular: "Last century",
},
decades: {
nameSingular: "Decade",
nextSingular: "Next decade",
prevSingular: "Last decade",
},
years: {
nameSingular: "Year",
nextSingular: "Next year",
prevSingular: "Last year",
},
months: {
nameSingular: "Month",
nextSingular: "Next month",
prevSingular: "Last month",
inputName: "Month's name",
daysNb: "Number of days",
daysNameSingular: "Day",
daysNamePlural: "Days",
daysMaybePlural: "day(s)",
available: "Months available",
none: "No months for now",
noneAvailable: "No months available",
chooseOne: "Choose a month",
deleteOne: "Remove {month} from this calendar"
}
},
character: {
nameSingular: "Character",
namePlural: "Characters",
}
}
},
fr: {
head: {
title: "Calendriers fantasies pour JDR",
description: "Outils destinés aux joueurs et maîtres de jeux pour visualiser plus facilement leurs univers.",
},
ui: {
action: {
back: "Retour",
cancel: "Annuler",
save: "Sauvegarder",
delete: "Supprimer",
edit: "Modifier",
},
greeting: "Connecté en tant que {user}",
sidebarMenu: {
appearance: "Apparence",
language: "Langue",
account: "Compte",
login: "Connexion",
logout: "Déconnexion",
avatarFallback: "Profil",
},
dark: "Sombre",
light: "Clair",
system: "Système",
displayMode: "Mode d'affichage",
},
common: {
title: "Titre"
},
entity: {
category: {
nameSingular: "Catégorie",
namePlural: "Catégories",
search: "Rechercher les catégories",
notFoundAny: "Aucune catégorie trouvée.",
addPrimary: "Ajouter une catégorie principale",
addSecondaries: "Ajouter des catégories secondaires"
},
addDescription: "Ajouter une description",
deleteOne: "Supprimer \"{entity}\"",
advancedSearch: {
title: "Recherche avancée",
subtitle: "Rechercher les données disponibles sur le calendrier",
older: "Plus ancien",
newer: "Plus récent",
ctaPlaceholder: "Rechercher un évènement, personnage…"
},
world: {
nameSingular: "Monde",
namePlural: "Mondes",
},
calendar: {
nameSingular: "Calendriers",
namePlural: "Calendrier",
addSingle: "Ajouter un calendrier",
notFoundForWorld: "Aucun calendrier pour ce monde… pour l'instant !",
isLoading: "Chargement du calendrier…",
date: {
start: "Date de début",
end: "Date de fin",
afterTomorrow: "Après-demain",
tomorrow: "Demain",
today: "Aujourd'hui",
yesterday: "Hier",
beforeYesterday: "Avant-hier",
fromTo: "Du {startDate} au {endDate}",
while: "Pendant {duration}",
xDaysNext: "Dans {days} jours",
xMonthsNext: "Dans {months} mois",
xYearsNext: "Dans {years} an(s)",
xYearsAndMonthsNext: "Dans {years} an(s) et {months} mois",
xDaysNextCompact: "{days} jours",
xMonthsNextCompact: "{months} mois",
xYearsNextCompact: "{years} an(s)",
xYearsAndMonthsNextCompact: "{years} an(s) et {months} mois",
xDaysAgo: "Il y a {days} jours",
xMonthsAgo: "Il y a {months} mois",
xYearsAgo: "Il y a {years} an(s)",
xYearsAndMonthsAgo: "Il y a {years} an(s) et {months} mois",
xDaysAgoCompact: "{days} jours",
xMonthsAgoCompact: "{months} mois",
xYearsAgoCompact: "{years} an(s)",
xYearsAndMonthsAgoCompact: "{years} an(s) et {months} mois",
},
event: {
nameSingular: "Évènement",
namePlural: "Évènements",
title: "Titre de l'évènement",
isStart: "Début",
isEnd: "Fin",
isHidden: "Évènement privé",
isPublic: "Évènement public",
hiddenTooltip: "Cet évènement est uniquement visible pour les maîtres du jeu.",
addLocation: "Ajouter un endroit",
prevPage: "Précédente page à évènements",
nextPage: "Prochaine page à évènements",
editDialog: {
title: "Modifier l'évènement",
subtitle: "Mettre à jour les données de l'évènement",
},
deleteDialog: {
title: "Supprimer l'évènement",
subtitle: "Les données associés à cet évènement seront supprimées et vous ne pourrez plus les récupérer !",
}
},
createDialog: {
title: "Nouveau calendrier",
tabs: {
general: {
title: "Général",
},
months: {
title: "Mois de l'année",
},
today: {
title: "Aujourd'hui",
},
}
},
deleteDialog: {
title: "Êtes-vous sûr de supprimer ce calendrier ?",
subtitle: "Les évènements ne seront plus accessibles et vous ne pourrez plus récupérer les données !",
},
millennia: {
nameSingular: "Millénaire",
nextSingular: "Millénaire suivant",
prevSingular: "Millénaire précédent",
},
centuries: {
nameSingular: "Siècle",
nextSingular: "Siècle suivant",
prevSingular: "Siècle précédent",
},
decades: {
nameSingular: "Décennie",
nextSingular: "Décennie suivante",
prevSingular: "Décennie précédente",
},
years: {
nameSingular: "Année",
nextSingular: "Année suivante",
prevSingular: "Année précédente",
},
months: {
nameSingular: "Mois",
nextSingular: "Mois suivant",
prevSingular: "Mois précédent",
inputName: "Nom du mois",
daysNb: "Nombre de jours",
daysNameSingular: "Jour",
daysNamePlural: "Jours",
daysMaybePlural: "jour(s)",
Singular: "Année",
available: "Mois disponibles",
none: "Aucun mois pour l'instant",
noneAvailable: "Aucun mois disponible",
chooseOne: "Choisir un mois",
deleteOne: "Supprimer {month} du calendrier"
}
},
character: {
nameSingular: "Personnage",
namePlural: "Personnages",
}
}
}
}
}))

View File

@@ -9,10 +9,21 @@ export default defineNuxtConfig({
"@nuxtjs/tailwindcss",
"@nuxtjs/color-mode",
"@nuxt/eslint",
"shadcn-nuxt"
"shadcn-nuxt",
"@nuxtjs/i18n"
],
css: ["~/assets/main.css"],
i18n: {
vueI18n: "./i18n.config.ts",
detectBrowserLanguage: {
useCookie: true,
cookieKey: "tttools_lang",
redirectOn: "all",
alwaysRedirect: true,
}
},
supabase: {
redirectOptions: {
login: "/",

View File

@@ -14,6 +14,7 @@
},
"dependencies": {
"@nuxt/eslint": "^0.3.12",
"@nuxtjs/i18n": "^8.5.1",
"@phosphor-icons/vue": "^2.2.1",
"@pinia/nuxt": "^0.5.1",
"@vueuse/core": "^10.9.0",

View File

@@ -1,12 +1,18 @@
<script lang="ts" setup>
useHead({
title: "TTTools — Calendriers de JDR",
titleTemplate: null
})
</script>
<template>
<main class="p-8">
<Head>
<Title>
TTTools {{ $t('head.title') }}
</Title>
<Meta name="description" :content="$t('head.description')" />
</Head>
<Heading>TTTools</Heading>
</main>
</template>

View File

@@ -23,27 +23,27 @@ const { data: catData, pending: catPending } = await useLazyFetch("/api/calendar
const cal = computed<Calendar>(() => calendarData?.value?.data as Calendar)
const categories = computed<Category[]>(() => catData?.value?.data as Category[])
if (cal.value) {
useHead({
title: cal.value.name
})
}
watch(cal, (n) => {
useHead({
title: n.name
})
})
</script>
<template>
<div v-if="calPending || catPending" class="h-full w-full grid place-items-center">
<Head>
<Title>{{ $t("entity.calendar.nameSingular") }}</Title>
</Head>
<div class="grid gap-2 justify-items-center opacity-50">
<p>Chargement du calendrier</p>
<p>
{{ $t('entity.calendar.isLoading') }}
</p>
<PhCircleNotch size="50" class="animate-spin"/>
</div>
</div>
<Calendar v-else-if="cal && categories" :calendar-data="cal" :categories />
<div v-else-if="cal && categories" class="h-full w-full">
<Head>
<Title>{{ cal.name }}</Title>
</Head>
<Calendar :calendar-data="cal" :categories />
</div>
</template>

View File

@@ -2,10 +2,6 @@
import type { RealtimeChannel } from "@supabase/supabase-js"
import type { World } from "~/models/World";
useHead({
title: "Mondes"
})
const supabase = useSupabaseClient()
const { data: res } = await useFetch("/api/worlds/query")
@@ -85,12 +81,16 @@ onUnmounted(() => {
</script>
<template>
<main class="p-8 after:fill-red-400">
<main class="p-8 after:fill-red-400">
<Head>
<Title>{{ $t("entity.world.namePlural") }}</Title>
</Head>
<Heading>{{ user?.user_metadata.full_name }}</Heading>
<section v-if="worlds" class="mt-4">
<h2 class="mb-4 text-lg font-bold">
Mondes
{{ $t('entity.world.namePlural') }}
</h2>
<ul class="grid lg:grid-cols-2 gap-2">

View File

@@ -12,20 +12,6 @@ const { data: res, pending } = await useFetch("/api/worlds/query", { query: { id
const world = ref<World>(res.value?.data as World)
if (world.value) {
useHead({
title: world.value.name
})
}
watch(world, (n) => {
if (n) {
useHead({
title: n.name
})
}
}, { deep: true })
definePageMeta({
middleware: ["auth-guard"]
})
@@ -116,9 +102,17 @@ function hideDeleteModal() {
<template>
<main class="p-8">
<template v-if="pending">
<Head>
<Title>{{ $t("entity.world.namePlural") }}</Title>
</Head>
<Heading>Chargement...</Heading>
</template>
<template v-else-if="world">
<Head>
<Title>{{ world.name }}</Title>
</Head>
<header class="lg:w-1/2 mb-8">
<Spacing>
<Heading>{{ world.name }}</Heading>
@@ -130,7 +124,9 @@ function hideDeleteModal() {
<section>
<Spacing size="lg">
<div class="flex items-center gap-3">
<Heading>Calendriers</Heading>
<Heading>
{{ $t('entity.calendar.namePlural') }}
</Heading>
<UiTooltipProvider :delay-duration="250">
<UiTooltip>
@@ -140,7 +136,9 @@ function hideDeleteModal() {
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent :side-offset="10">
<p>Ajouter un calendrier</p>
<p>
{{ $t('entity.calendar.addSingle') }}
</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
@@ -168,7 +166,7 @@ function hideDeleteModal() {
</ul>
<template v-else>
<p class="pl-6 opacity-75 italic">
Aucun calendrier pour ce monde
{{ $t('entity.calendar.notFoundForWorld') }}
</p>
</template>
</Spacing>

599
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
public/images/flag-fr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
public/images/flag-uk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -24,7 +24,16 @@ type CalendarCurrentDate = {
currentDateTitle: ComputedRef<string>
}
type DateDirectionTranslationKeys = {
days: string
months: string
years: string
yearsAndMonths: string
}
export const useCalendar = defineStore("calendar", () => {
const { t } = useI18n()
/**
* Static calendar config
*/
@@ -326,7 +335,7 @@ export const useCalendar = defineStore("calendar", () => {
function getViewTypeTitle(viewType: CalendarViewType): string {
switch (viewType) {
case "year":
return "Année"
return t("entity.calendar.years.nameSingular")
case "decade":
return "Décennie"
@@ -336,7 +345,7 @@ export const useCalendar = defineStore("calendar", () => {
case "month":
default:
return "Mois"
return t("entity.calendar.months.nameSingular")
}
}
@@ -481,7 +490,31 @@ export const useCalendar = defineStore("calendar", () => {
const differenceInDays: number = getDifferenceInDays(baseDate, relativeDate)
let output: string = ""
let direction: "past" | "present" | "future" = "present"
let directionPrefix: string = ""
const futureKeys: DateDirectionTranslationKeys = {
days: "xDaysNext",
months: "xMonthsNext",
years: "xYearsNext",
yearsAndMonths: "xYearsAndMonthsNext",
}
const futureKeysCompact: DateDirectionTranslationKeys = {
days: "xDaysNextCompact",
months: "xMonthsNextCompact",
years: "xYearsNextCompact",
yearsAndMonths: "xYearsAndMonthsNextCompact",
}
const pastKeys: DateDirectionTranslationKeys = {
days: "xDaysAgo",
months: "xMonthsAgo",
years: "xYearsAgo",
yearsAndMonths: "xYearsAndMonthsAgo",
}
const pastKeysCompact: DateDirectionTranslationKeys = {
days: "xDaysAgoCompact",
months: "xMonthsAgoCompact",
years: "xYearsAgoCompact",
yearsAndMonths: "xYearsAndMonthsAgoCompact",
}
let directionKeys: DateDirectionTranslationKeys = pastKeys
// Check whether it's a past or future date
if (differenceInDays > 0) {
@@ -491,31 +524,36 @@ export const useCalendar = defineStore("calendar", () => {
}
if (formatting === "complex") {
// Handle if it's the same date
// Handle if it's the same date
if (direction === "present") {
return "Aujourd'hui"
return t("entity.calendar.date.today")
}
if (differenceInDays === -2) {
return "Avant-hier"
return t("entity.calendar.date.beforeYesterday")
}
if (differenceInDays === -1) {
return "Hier"
return t("entity.calendar.date.yesterday")
}
if (differenceInDays === 1) {
return "Demain"
return t("entity.calendar.date.tomorrow")
}
if (differenceInDays === 2) {
return "Après-demain"
return t("entity.calendar.date.afterTomorrow")
}
// Get relevant prefix for the string
if (direction === "future") {
directionPrefix = "Dans "
} else if (direction === "past") {
directionPrefix = "Il y a "
directionKeys = futureKeys
} else {
directionKeys = pastKeys
}
} else {
// Get relevant prefix for the string (without the prefix)
if (direction === "future") {
directionKeys = futureKeysCompact
} else {
directionKeys = pastKeysCompact
}
output += directionPrefix
}
const isSameMonth = baseDate.month === relativeDate.month
@@ -544,11 +582,7 @@ export const useCalendar = defineStore("calendar", () => {
if (isSameMonth && isSameYear) {
dateAcc.day = futureDate.day - datePivot.day
if (dateAcc.day === 1) {
output += ` ${dateAcc.day} jour`
} else {
output += ` ${dateAcc.day} jours`
}
output = t(`entity.calendar.date.${directionKeys.days}`, { days: dateAcc.day })
return output
}
@@ -557,7 +591,7 @@ export const useCalendar = defineStore("calendar", () => {
else if (isSameYear) {
dateAcc.month = futureDate.month - datePivot.month
output += ` ${dateAcc.month} mois`
output = t(`entity.calendar.date.${directionKeys.months}`, { months: dateAcc.month })
return output
}
@@ -600,13 +634,13 @@ export const useCalendar = defineStore("calendar", () => {
const remainderMonths = dateAcc.month % monthsPerYear.value
if (computedYear >= 1 && remainderMonths) {
output += ` ${computedYear} an(s) et ${remainderMonths} mois`
output = t(`entity.calendar.date.${directionKeys.yearsAndMonths}`, { years: computedYear, months: remainderMonths })
}
else if (computedYear >= 1 && !remainderMonths) {
output += ` ${computedYear} an(s)`;
output = t(`entity.calendar.date.${directionKeys.years}`, { years: computedYear })
}
else {
output += ` ${remainderMonths} mois`
output = t(`entity.calendar.date.${directionKeys.months}`, { months: remainderMonths })
}
return output