- Installation vue-i18n v11 et création des fichiers de traduction (fr.json, en.json) - Création store locale avec détection hiérarchique (URL > localStorage > navigator) - Modification des routes avec préfixe /:locale? optionnel - Toggle FR/EN dans Menu.vue avec synchronisation immédiate - Traduction de ~200 textes dans 27 composants Vue - Suppression des labels hardcodés en français côté backend - Ajout route Kirby catch-all en/(:all?) pour /en/ URLs - Helper addLocalePrefix() pour préserver locale dans liens dialogs - Traduction pseudo-élément CSS via data attribute Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
106 lines
2.7 KiB
Vue
106 lines
2.7 KiB
Vue
<template>
|
|
<main v-if="page.inspirations" class="flex flex-col">
|
|
<header class="flex">
|
|
<h2 id="tabslist" class="sr-only">{{ t('inspirations.title') }}</h2>
|
|
<Tabs :tabs="tabs" @update:currentTab="changeTab" />
|
|
<Selector
|
|
v-if="page.inspirations.length > 1"
|
|
:all="page.inspirations"
|
|
@update:currentInspiration="changeInspiration"
|
|
/>
|
|
</header>
|
|
<section :id="currentTab" class="inspiration">
|
|
<Header
|
|
v-if="currentTab !== 'favorites'"
|
|
:inspiration="currentInspiration"
|
|
/>
|
|
<div class="masonry flow">
|
|
<template v-for="(item, index) in displayedImages" :key="item.id">
|
|
<Image
|
|
:item="item"
|
|
:inspirationUri="currentInspiration.uri"
|
|
:currentTab="currentTab"
|
|
/>
|
|
</template>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</template>
|
|
|
|
<script setup>
|
|
import Selector from "../components/inspirations/Selector.vue";
|
|
import Header from "../components/inspirations/Header.vue";
|
|
import Tabs from "../components/Tabs.vue";
|
|
import Image from "../components/inspirations/Image.vue";
|
|
import { useUserStore } from "../stores/user";
|
|
import { ref, computed } from "vue";
|
|
import { usePageStore } from "../stores/page";
|
|
import { storeToRefs } from "pinia";
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
const { t } = useI18n();
|
|
|
|
// Stores
|
|
const { page } = storeToRefs(usePageStore());
|
|
const userStore = useUserStore();
|
|
const user = computed(() => userStore.user);
|
|
|
|
// Reactive
|
|
const currentTab = ref("all");
|
|
const currentInspiration = ref(page.value.inspirations[0]);
|
|
|
|
// Computed
|
|
const allImages = computed(() => {
|
|
if (!page.value.inspirations) return [];
|
|
return page.value.inspirations.flatMap((inspiration) => inspiration.media);
|
|
});
|
|
|
|
const favoriteImages = computed(() =>
|
|
allImages.value.filter(
|
|
(image) => image.favoriteForUsers?.includes(user.value.uuid) ?? false
|
|
)
|
|
);
|
|
|
|
const tabs = computed(() => [
|
|
{
|
|
label: t('inspirations.title'),
|
|
id: "all",
|
|
icon: null,
|
|
count: currentInspiration.value.media.length,
|
|
isActive: currentTab.value === "all",
|
|
},
|
|
{
|
|
label: t('inspirations.favorites'),
|
|
id: "favorites",
|
|
icon: "favorite",
|
|
count: favoriteImages.value.length,
|
|
isActive: currentTab.value === "favorites",
|
|
},
|
|
]);
|
|
|
|
const displayedImages = computed(() =>
|
|
currentTab.value === "favorites"
|
|
? favoriteImages.value
|
|
: currentInspiration.value.media
|
|
);
|
|
|
|
// Methods
|
|
function changeTab(newValue) {
|
|
currentTab.value = newValue;
|
|
}
|
|
|
|
function changeInspiration(newValue) {
|
|
currentInspiration.value = newValue;
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Header */
|
|
main > header {
|
|
gap: var(--space-16);
|
|
}
|
|
/* Tabs */
|
|
[role="tablist"] {
|
|
margin-left: 0;
|
|
}
|
|
</style>
|