Implémentation complète du multilingue FR/EN
- 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>
This commit is contained in:
parent
3af95b1d20
commit
82eb8d88cc
49 changed files with 1079 additions and 295 deletions
|
|
@ -6,12 +6,12 @@
|
|||
<label
|
||||
for="project-description"
|
||||
class="flex | text-sm text-grey-700 | mb-8"
|
||||
>Description du projet</label
|
||||
>{{ t('forms.description') }}</label
|
||||
>
|
||||
<textarea
|
||||
name="project-description"
|
||||
id="project-description"
|
||||
placeholder="Ajoutez une description générale de votre projet…"
|
||||
:placeholder="t('forms.descriptionPlaceholder')"
|
||||
rows="2"
|
||||
class="border border-grey-200 | rounded-xl | p-16 | w-full"
|
||||
v-model="page.content.description"
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
></textarea>
|
||||
</div>
|
||||
<fieldset class="project-details__filters | flex-1">
|
||||
<legend class="text-sm text-grey-700 | mb-8">Filtrer par tags</legend>
|
||||
<legend class="text-sm text-grey-700 | mb-8">{{ t('forms.filterByTags') }}</legend>
|
||||
<div class="flex" style="gap: var(--space-8)">
|
||||
<button
|
||||
class="btn btn--sm btn--grey"
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
role="switch"
|
||||
@click="clearTags()"
|
||||
>
|
||||
Voir tout
|
||||
{{ t('buttons.seeAll') }}
|
||||
</button>
|
||||
<template v-for="tag in page.tags" :key="tag">
|
||||
<input
|
||||
|
|
@ -53,6 +53,9 @@ import { ref, watch } from "vue";
|
|||
import { usePageStore } from "../../../stores/page";
|
||||
import StringUtils from "../../../utils/string";
|
||||
import debounce from "lodash/debounce";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { selectedTags } = defineProps({
|
||||
selectedTags: Array,
|
||||
|
|
@ -93,7 +96,7 @@ const saveDescription = debounce(() => {
|
|||
console.log(json);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Erreur lors de la sauvegarde :", error);
|
||||
console.error(t('errors.saveFailed'), error);
|
||||
isWaitingForSave.value = false;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
v-model:visible="isOpen"
|
||||
id="image-details"
|
||||
modal
|
||||
header="Détails de l’image"
|
||||
:header="t('dialogs.imageDetails')"
|
||||
class="bg-white | text-grey-800 | rounded-2xl | overflow-hidden"
|
||||
>
|
||||
<picture :style="'--image: url('+image.url+')'">
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
</picture>
|
||||
<div class="flex flex-col | p-32" style="--row-gap: var(--space-32)">
|
||||
<fieldset class="image__tags">
|
||||
<legend class="text-sm text-grey-700 | mb-8">Tags</legend>
|
||||
<legend class="text-sm text-grey-700 | mb-8">{{ t('forms.tags') }}</legend>
|
||||
<div class="flex" style="gap: var(--space-8)">
|
||||
<template v-for="(pageTag, index) in page.tags" :key="index">
|
||||
<input
|
||||
|
|
@ -33,12 +33,12 @@
|
|||
<label
|
||||
for="image-description"
|
||||
class="flex | text-sm text-grey-700 | mb-8"
|
||||
>Description de l’image</label
|
||||
>{{ t('forms.imageDescription') }}</label
|
||||
>
|
||||
<textarea
|
||||
name="image-description"
|
||||
id="image-description"
|
||||
placeholder="Ajoutez une description à l’image…"
|
||||
:placeholder="t('forms.imageDescriptionPlaceholder')"
|
||||
class="border border-grey-200 | rounded-xl | p-16 | w-full"
|
||||
v-model="image.description"
|
||||
@input="saveDescription()"
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
class="btn btn--black-10 | ml-auto mt-auto"
|
||||
@click="remove()"
|
||||
>
|
||||
Supprimer cette image
|
||||
{{ t('buttons.deleteImage') }}
|
||||
</button>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
|
@ -61,6 +61,9 @@ import { usePageStore } from "../../../stores/page";
|
|||
import StringUtils from "../../../utils/string";
|
||||
import Dialog from "primevue/dialog";
|
||||
import debounce from "lodash/debounce";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { imageDetails } = defineProps({
|
||||
imageDetails: Object,
|
||||
|
|
@ -97,7 +100,7 @@ function saveTags() {
|
|||
console.log(json);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Erreur lors de la sauvegarde :", error);
|
||||
console.error(t('errors.saveFailed'), error);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +125,7 @@ const saveDescription = debounce(() => {
|
|||
emit("");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Erreur lors de la sauvegarde :", error);
|
||||
console.error(t('errors.saveFailed'), error);
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
|
|
@ -142,7 +145,7 @@ function remove() {
|
|||
isOpen.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Erreur lors de la suppression :", error);
|
||||
console.error(t('errors.deleteFailed'), error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
:draggable="false"
|
||||
:dismissableMask="true"
|
||||
modal
|
||||
header="Ajouter des images"
|
||||
:header="t('dialogs.addImages')"
|
||||
class="bg-white | text-grey-800 | rounded-2xl | overflow-hidden | p-32"
|
||||
>
|
||||
<div class="with-sidebar | h-full">
|
||||
|
|
@ -56,19 +56,18 @@
|
|||
id="delete-image"
|
||||
v-model:visible="deleteIsOpen"
|
||||
modal
|
||||
header="Êtes-vous sûr de vouloir supprimer cette image ?"
|
||||
:header="t('dialogs.deleteConfirm')"
|
||||
class="bg-white | text-grey-800 | rounded-2xl | overflow-hidden | text-center | w-full max-w | p-16 pt-32"
|
||||
style="--row-gap: var(--space-32); --max-w: 40rem"
|
||||
>
|
||||
<p class="text-grey-700 | px-16">
|
||||
Si vous supprimez cette image, celle-ci disparaîtra de votre brief ainsi
|
||||
que toutes les informations qui lui sont attribuées.
|
||||
{{ t('dialogs.deleteWarning') }}
|
||||
</p>
|
||||
<template #footer>
|
||||
<button class="btn btn--secondary | flex-1" @click="deleteIsOpen = false">
|
||||
Annuler
|
||||
{{ t('buttons.cancel') }}
|
||||
</button>
|
||||
<button class="btn | flex-1" @click="">Supprimer</button>
|
||||
<button class="btn | flex-1" @click="">{{ t('buttons.delete') }}</button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
|
@ -79,6 +78,9 @@ import ImagesEditPanel from './ImagesEditPanel.vue';
|
|||
import { ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useAddImagesModalStore } from '../../../../stores/addImagesModal';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { isAddImagesModalOpen } = defineProps({
|
||||
isAddImagesModalOpen: Boolean,
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
:multiple="true"
|
||||
accept="image/*"
|
||||
:maxFileSize="1000000"
|
||||
invalidFileSizeMessage="Fichier trop lourd"
|
||||
chooseLabel="Ajouter une ou plusieurs images"
|
||||
:invalidFileSizeMessage="t('errors.saveFailed')"
|
||||
:chooseLabel="t('forms.addImages')"
|
||||
class="flex flex-col justify-center | bg-white | border border-grey-200 | text-grey-800 | font-medium | rounded-xl"
|
||||
ref="uploadBtn"
|
||||
>
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
removeFileCallback,
|
||||
}"
|
||||
>
|
||||
<div v-if="files.length > 0">Fichiers importés</div>
|
||||
<div v-if="files.length > 0">{{ t('forms.uploadedFiles') }}</div>
|
||||
</template>
|
||||
</FileUpload>
|
||||
<figure
|
||||
|
|
@ -71,6 +71,9 @@ import { storeToRefs } from "pinia";
|
|||
import { computed, ref } from "vue";
|
||||
import { useAddImagesModalStore } from "../../../../stores/addImagesModal";
|
||||
import ArrayUtils from "../../../../utils/array";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { page } = storeToRefs(usePageStore());
|
||||
const toast = useToast();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue