Is hutopy finally trilangual

This commit is contained in:
2025-04-19 01:56:59 -04:00
parent af71c5e952
commit b1681252cc
140 changed files with 5441 additions and 1085 deletions

View File

@@ -9,6 +9,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@intlify/unplugin-vue-i18n": "^6.0.5",
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
"@stripe/stripe-js": "^3.0.10", "@stripe/stripe-js": "^3.0.10",
"@tinymce/tinymce-vue": "^6.0.1", "@tinymce/tinymce-vue": "^6.0.1",
@@ -22,12 +23,13 @@
"uuid": "^10.0.0", "uuid": "^10.0.0",
"vue": "^3.4.15", "vue": "^3.4.15",
"vue-advanced-cropper": "^2.8.9", "vue-advanced-cropper": "^2.8.9",
"vue-i18n": "^9.14.0", "vue-i18n": "^10.0.7",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"vue3-google-login": "^2.0.26", "vue3-google-login": "^2.0.26",
"vuetify": "^3.5.6" "vuetify": "^3.5.6"
}, },
"devDependencies": { "devDependencies": {
"@types/webpack-env": "^1.18.8",
"@vitejs/plugin-vue": "^5.0.3", "@vitejs/plugin-vue": "^5.0.3",
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.17",
"eslint": "^8.57.0", "eslint": "^8.57.0",

4253
frontend/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,21 @@
<script async setup> <script async setup>
import SideBar from "@/views/main/SideBar.vue"; import SideBar from "@/views/main/SideBar.vue";
import {useLanguageStore} from "@/stores/languageStore.js";
import {watch} from "vue";
import {useI18n} from "vue-i18n";
// Watch for language changes and update i18n locale
const languageStore = useLanguageStore();
const { locale } = useI18n();
// Watch for changes to the language store
watch(() => languageStore.locale, (newLocale) => {
if (newLocale) {
locale.value = newLocale;
}
}, { immediate: true });
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,18 +0,0 @@
import { createI18n } from 'vue-i18n';
import { useSessionStorage } from '@vueuse/core';
// Get the stored locale or default to 'fr'
const storedLocale = useSessionStorage('user-locale', 'fr');
const i18n = createI18n({
legacy: false,
locale: storedLocale.value,
fallbackLocale: 'en',
messages: {
en: {},
fr: {},
es: {}
}
});
export default i18n;

View File

@@ -0,0 +1,53 @@
{
// Common actions
"save": "Save",
"cancel": "Cancel",
"edit": "Edit",
"delete": "Delete",
"create": "Create",
"apply": "Apply",
"preview": "Preview",
"label": "Label",
"confirm": "Confirm",
"close": "Close",
// Common status
"loading": "Loading...",
"error": "Error",
"success": "Success",
// Common messages
"changesSaved": "Changes saved successfully",
"errorOccurred": "An error occurred",
// Common fields
"name": "Name",
"email": "Email",
"password": "Password",
"description": "Description",
"title": "Title",
"image": "Image",
"file": "File",
// Common validation
"required": "This field is required",
"invalidEmail": "Invalid email address",
"invalidPassword": "Invalid password",
// Social media
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Website",
// Errors
"errors": {
"unexpected": "An unexpected error occurred",
"imageLoad": "Error loading image",
"imageUpload": "Error uploading image"
}
}

View File

@@ -0,0 +1,40 @@
{
"save": "Guardar",
"cancel": "Cancelar",
"edit": "Editar",
"delete": "Eliminar",
"create": "Crear",
"apply": "Aplicar",
"preview": "Vista previa",
"label": "Etiqueta",
"confirm": "Confirmar",
"close": "Cerrar",
"loading": "Cargando...",
"error": "Error",
"success": "Éxito",
"changesSaved": "Cambios guardados con éxito",
"errorOccurred": "Ha ocurrido un error",
"name": "Nombre",
"email": "Correo electrónico",
"password": "Contraseña",
"description": "Descripción",
"title": "Título",
"image": "Imagen",
"file": "Archivo",
"required": "Este campo es obligatorio",
"invalidEmail": "Correo electrónico inválido",
"invalidPassword": "Contraseña inválida",
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Sitio web",
"errors": {
"unexpected": "Ha ocurrido un error inesperado",
"imageLoad": "Error al cargar la imagen",
"imageUpload": "Error al subir la imagen"
}
}

View File

@@ -0,0 +1,40 @@
{
"save": "Enregistrer",
"cancel": "Annuler",
"edit": "Modifier",
"delete": "Supprimer",
"create": "Créer",
"apply": "Appliquer",
"preview": "Aperçu",
"label": "Étiquette",
"confirm": "Confirmer",
"close": "Fermer",
"loading": "Chargement...",
"error": "Erreur",
"success": "Succès",
"changesSaved": "Modifications enregistrées avec succès",
"errorOccurred": "Une erreur est survenue",
"name": "Nom",
"email": "Email",
"password": "Mot de passe",
"description": "Description",
"title": "Titre",
"image": "Image",
"file": "Fichier",
"required": "Ce champ est requis",
"invalidEmail": "Adresse email invalide",
"invalidPassword": "Mot de passe invalide",
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Site web",
"errors": {
"unexpected": "Une erreur inattendue s'est produite",
"imageLoad": "Erreur lors du chargement de l'image",
"imageUpload": "Erreur lors du téléchargement de l'image"
}
}

View File

@@ -1,6 +1,6 @@
import {createApp} from 'vue' import {createApp} from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './router/router.js' import router from '@/router/router.js'
import {createPinia} from 'pinia' import {createPinia} from 'pinia'
import '@mdi/font/css/materialdesignicons.css' import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles' import 'vuetify/styles'
@@ -9,7 +9,6 @@ import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives' import * as directives from 'vuetify/directives'
import vueGoogleOauth from 'vue3-google-login' import vueGoogleOauth from 'vue3-google-login'
import {useAuthStore} from "@/stores/authStore.js"; import {useAuthStore} from "@/stores/authStore.js";
import i18n from './i18n.js';
import {useUserProfileStore} from "@/stores/userProfileStore.js"; import {useUserProfileStore} from "@/stores/userProfileStore.js";
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js"; import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
import './assets/main.css' import './assets/main.css'
@@ -19,21 +18,34 @@ const vuetify = createVuetify({
directives directives
}); });
import {createI18n} from 'vue-i18n'
import en from '@/locales/en.json'
import fr from '@/locales/fr.json'
import es from '@/locales/es.json'
const i18n = createI18n({
legacy: false,
fallbackLocale: 'en',
messages: {
en: en,
fr: fr,
es: es
}
})
const pinia = createPinia();
const app = createApp(App) const app = createApp(App)
.use(createPinia()) .use(pinia)
.use(vuetify) .use(vuetify)
.use(router) .use(router)
.use(i18n)
.use(vueGoogleOauth, { .use(vueGoogleOauth, {
clientId: import.meta.env.VITE_GOOGLE_CLIENT_ID, clientId: import.meta.env.VITE_GOOGLE_CLIENT_ID,
}) });
.use(i18n)
// Make $t globally available useAuthStore();
app.config.globalProperties.$t = i18n.global.t; useUserProfileStore();
useCreatorProfileStore();
// this force the creation and initialization of the stores
useAuthStore()
useUserProfileStore()
useCreatorProfileStore()
app.mount('#app'); app.mount('#app');

View File

@@ -1,23 +0,0 @@
export default class UserTransactionsModel
{
amount = "";
currency = "";
tipMessage = "";
created = "";
static createFromApiResult(apiResult){
const userTransactionModel = Object.assign(new UserTransactionsModel(), apiResult)
const date = new Date(userTransactionModel.created);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: 'America/Montreal'
};
userTransactionModel.created = new Intl.DateTimeFormat('fr-CA', options).format(date);
return userTransactionModel;
}
}

View File

@@ -5,16 +5,21 @@ import { useI18n } from 'vue-i18n'
export const useLanguageStore = defineStore( export const useLanguageStore = defineStore(
'language', 'language',
() => { () => {
const { locale } = useI18n()
// Initialize with the stored value or default to 'fr' // Initialize with the stored value or default to 'fr'
const storedLocale = useSessionStorage('user-locale', 'fr') const storedLocale = useSessionStorage('user-locale', 'fr')
// Get i18n instance
const { locale } = useI18n()
// Set the initial locale from storage // Set the initial locale from storage
locale.value = storedLocale.value if (locale && storedLocale.value) {
locale.value = storedLocale.value
}
function setLocale(newLocale) { function setLocale(newLocale) {
locale.value = newLocale if (locale) {
locale.value = newLocale
}
storedLocale.value = newLocale storedLocale.value = newLocale
} }

View File

@@ -1,15 +0,0 @@
{
"save": "Save",
"cancel": "Cancel",
"edit": "Edit",
"delete": "Delete",
"create": "Create",
"apply": "Apply",
"preview": "Preview",
"label": "Label",
"errors": {
"unexpected": "An unexpected error occurred",
"imageLoad": "Error loading image",
"imageUpload": "Error uploading image"
}
}

View File

@@ -1,15 +0,0 @@
{
"save": "Guardar",
"cancel": "Cancelar",
"edit": "Editar",
"delete": "Eliminar",
"create": "Crear",
"apply": "Aplicar",
"preview": "Vista previa",
"label": "Etiqueta",
"errors": {
"unexpected": "Se produjo un error inesperado",
"imageLoad": "Error al cargar la imagen",
"imageUpload": "Error al subir la imagen"
}
}

View File

@@ -1,15 +0,0 @@
{
"save": "Enregistrer",
"cancel": "Annuler",
"edit": "Modifier",
"delete": "Supprimer",
"create": "Créer",
"apply": "Appliquer",
"preview": "Aperçu",
"label": "Étiquette",
"errors": {
"unexpected": "Une erreur inattendue s'est produite",
"imageLoad": "Erreur lors du chargement de l'image",
"imageUpload": "Erreur lors du téléchargement de l'image"
}
}

View File

@@ -1,95 +0,0 @@
import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
// Helper function to get component path from stack trace
function getComponentPath() {
const stack = new Error().stack
const stackLines = stack.split('\n')
// Find the first line that contains a .vue file
const vueFileLine = stackLines.find(line => line.includes('.vue'))
if (!vueFileLine) return null
// Extract the file path
const match = vueFileLine.match(/\/src\/(.*?\.vue)/)
if (!match) return null
// Return the full path without the .vue extension
return match[1].replace(/\.vue$/, '')
}
// Helper function to get nested value from an object using dot notation
function getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => {
return current && current[key] !== undefined ? current[key] : null
}, obj)
}
export function useTranslations() {
const { locale } = useI18n()
const componentPath = ref(getComponentPath())
const componentTranslations = ref({})
const commonTranslations = ref({})
// Load translations when locale changes
const loadTranslations = async () => {
// Load component translations
if (componentPath.value) {
try {
// The translation files are in the same directory as the Vue components
const importPath = `../${componentPath.value}.${locale.value}.json`
const translations = await import(importPath)
componentTranslations.value = translations.default
} catch (e) {
try {
const importPath = `../${componentPath.value}.en.json`
const translations = await import(importPath)
componentTranslations.value = translations.default
} catch (e) {
componentTranslations.value = {}
}
}
}
// Load common translations
try {
const importPath = `./common.${locale.value}.json`
const translations = await import(importPath)
commonTranslations.value = translations.default
} catch (e) {
try {
const importPath = './common.en.json'
const translations = await import(importPath)
commonTranslations.value = translations.default
} catch (e) {
commonTranslations.value = {}
}
}
}
// Load translations immediately
loadTranslations()
// Watch for locale changes
watch(locale, () => {
loadTranslations()
})
// Return synchronous translation function
return (key) => {
// Try to get the translation from component translations first
const componentValue = getNestedValue(componentTranslations.value, key)
if (componentValue !== null) {
return componentValue
}
// Then try common translations
const commonValue = getNestedValue(commonTranslations.value, key)
if (commonValue !== null) {
return commonValue
}
// If not found, return the key with MISSING prefix
return `MISSING: ${key}`
}
}

View File

@@ -1,4 +0,0 @@
{
"title": "Login",
"alt": "hutopy login"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Iniciar sesión",
"alt": "iniciar sesión hutopy"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Connexion",
"alt": "connexion hutopy"
}

View File

@@ -2,9 +2,9 @@
import {ref} from 'vue'; import {ref} from 'vue';
import {GoogleLogin} from "vue3-google-login"; import {GoogleLogin} from "vue3-google-login";
import {useAuthStore} from '@/stores/authStore.js'; import {useAuthStore} from '@/stores/authStore.js';
import {useTranslations} from '@/translations/translations'; import {useI18n} from 'vue-i18n';
const t = useTranslations(); const {t} = useI18n();
const authStore = useAuthStore(); const authStore = useAuthStore();
@@ -44,3 +44,20 @@ async function googleCallback(token) {
<style scoped> <style scoped>
</style> </style>
<i18n>
{
"en": {
"title": "Sign in to Hutopy",
"alt": "Hutopy Login"
},
"fr": {
"title": "Connectez-vous à Hutopy",
"alt": "Connexion Hutopy"
},
"es": {
"title": "Iniciar sesión en Hutopy",
"alt": "Inicio de sesión Hutopy"
}
}
</i18n>

View File

@@ -1,7 +0,0 @@
{
"title": "Payment Successful!",
"message": "Your payment has been processed successfully.",
"usernameDefault": "the creator",
"receipt": "A receipt has been sent to your email.",
"continue": "Continue to"
}

View File

@@ -1,7 +0,0 @@
{
"title": "¡Pago exitoso!",
"message": "Su pago ha sido procesado con éxito.",
"usernameDefault": "el creador",
"receipt": "Se ha enviado un recibo a su correo electrónico.",
"continue": "Continuar a"
}

View File

@@ -1,7 +0,0 @@
{
"title": "Paiement réussi !",
"message": "Votre paiement a été traité avec succès.",
"usernameDefault": "le créateur",
"receipt": "Un reçu a été envoyé à votre adresse e-mail.",
"continue": "Continuer vers"
}

View File

@@ -45,11 +45,11 @@
<script setup> <script setup>
import {useRouter, useRoute} from 'vue-router'; import {useRouter, useRoute} from 'vue-router';
import {useTranslations} from '@/translations/translations'; import { useI18n } from 'vue-i18n';
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const t = useTranslations(); const { t } = useI18n();
function goBack() { function goBack() {
const returnUrl = route.query.returnUrl; const returnUrl = route.query.returnUrl;
@@ -62,6 +62,32 @@ function goBack() {
</script> </script>
<i18n>
{
"en": {
"title": "Payment Successful!",
"message": "Your payment has been processed successfully.",
"usernameDefault": "the creator",
"receipt": "A receipt has been sent to your email.",
"continue": "Continue to"
},
"fr": {
"title": "Paiement réussi !",
"message": "Votre paiement a été traité avec succès.",
"usernameDefault": "le créateur",
"receipt": "Un reçu a été envoyé à votre email.",
"continue": "Continuer vers"
},
"es": {
"title": "¡Pago exitoso!",
"message": "Su pago ha sido procesado con éxito.",
"usernameDefault": "el creador",
"receipt": "Se ha enviado un recibo a su correo electrónico.",
"continue": "Continuar a"
}
}
</i18n>
<style scoped> <style scoped>
.container { .container {
@apply min-h-screen; @apply min-h-screen;

View File

@@ -1,5 +0,0 @@
{
"title": "Payment Failed",
"message": "We couldn't process your payment. Please try again or contact support if the problem persists.",
"continue": "Return to"
}

View File

@@ -1,5 +0,0 @@
{
"title": "Pago fallido",
"message": "No pudimos procesar su pago. Por favor, inténtelo de nuevo o contacte con soporte si el problema persiste.",
"continue": "Volver a"
}

View File

@@ -1,5 +0,0 @@
{
"title": "Échec du paiement",
"message": "Nous n'avons pas pu traiter votre paiement. Veuillez réessayer ou contacter le support si le problème persiste.",
"continue": "Retourner à"
}

View File

@@ -5,7 +5,7 @@
<p>{{ t('message') }}</p> <p>{{ t('message') }}</p>
<div class="card-actions"> <div class="card-actions">
<button class="action-button" @click="goBack()"> <button class="action-button" @click="goBack()">
{{ t('continue') }} {{ t('retry') }}
</button> </button>
</div> </div>
</div> </div>
@@ -14,11 +14,11 @@
<script setup> <script setup>
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { useTranslations } from '@/translations/translations'; import { useI18n } from 'vue-i18n';
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const t = useTranslations(); const { t } = useI18n();
function goBack() { function goBack() {
const returnUrl = route.query.returnUrl; const returnUrl = route.query.returnUrl;
@@ -30,6 +30,26 @@ function goBack() {
} }
</script> </script>
<i18n>
{
"en": {
"title": "Payment Failed",
"message": "We couldn't process your payment.",
"retry": "Try Again"
},
"fr": {
"title": "Échec du paiement",
"message": "Nous n'avons pas pu traiter votre paiement.",
"retry": "Réessayer"
},
"es": {
"title": "Pago fallido",
"message": "No pudimos procesar su pago.",
"retry": "Intentar de nuevo"
}
}
</i18n>
<style scoped> <style scoped>
.container { .container {
@apply min-h-screen; @apply min-h-screen;

View File

@@ -1,5 +0,0 @@
{
"message": "Message (optional)",
"amount": "Amount ($)",
"send": "Send"
}

View File

@@ -1,5 +0,0 @@
{
"message": "Mensaje (opcional)",
"amount": "Cantidad ($)",
"send": "Enviar"
}

View File

@@ -1,5 +0,0 @@
{
"message": "Message (facultatif)",
"amount": "Montant ($)",
"send": "Envoyez"
}

View File

@@ -1,94 +0,0 @@
<template>
<v-container>
<v-row>
<v-text-field :label="t('message')" v-model="tipMessage"
style="border-radius: 10px; margin-top: 10px; margin-bottom: 10px; color: #a30e79; background-color: #f4f4f4">
</v-text-field>
</v-row>
<v-row>
<v-text-field :label="t('amount')" v-model="price"
style="border-radius: 10px; margin-bottom: 10px; color: #a30e79; background-color: #f4f4f4">
</v-text-field>
</v-row>
<v-row justify="center">
<v-btn @click="goPay()"
style="margin-bottom: 10px; width: 200px; background-color: #6b0065; color: white; font-weight: bold;">
<v-icon left style="margin-right: 10px;">
mdi-gift
</v-icon>
{{ t('send') }}
</v-btn>
</v-row>
<v-dialog v-model="isPaymentDialogActive" max-width="720" persistent>
<template v-slot:default>
<v-card>
<div id="checkout">
<!-- Checkout will insert the payment form here -->
</div>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn block class="ma-auto" style="width: 200px;" :text="t('cancel')" @click="closeDialog()"></v-btn>
</v-card-actions>
</v-card>
</template>
</v-dialog>
</v-container>
</template>
<script setup>
import { useClient } from '@/plugins/api.js';
import { loadStripe } from '@stripe/stripe-js';
import { onMounted, ref } from "vue";
import { useTranslations } from '@/translations/translations';
const props = defineProps(['creatorId'])
const t = useTranslations();
let stripe = null;
const client = useClient();
const price = ref(0);
const tipMessage = ref("");
const isPaymentDialogActive = ref(false);
var checkout;
onMounted(async () => {
stripe = await loadStripe(import.meta.env.VITE_STRIPE_API_KEY);
})
const fetchClientSecret = async () => {
const clientSecret = await createCheckoutSession();
return clientSecret;
};
async function createCheckoutSession() {
let clientSecret = await client.post('/api/Stripe', {
amount: (price.value * 100),
tipMessage: tipMessage.value,
creatorId: props.creatorId
});
let secret = clientSecret["data"];
return secret;
}
function closeDialog() {
isPaymentDialogActive.value = false;
checkout.destroy();
}
async function goPay() {
isPaymentDialogActive.value = true;
checkout = await stripe.initEmbeddedCheckout({
fetchClientSecret,
});
await checkout.mount('#checkout');
}
</script>

View File

@@ -1,3 +0,0 @@
{
"alt": "Creator banner"
}

View File

@@ -1,3 +0,0 @@
{
"alt": "Banner del creador"
}

View File

@@ -1,3 +0,0 @@
{
"alt": "Bannière du créateur"
}

View File

@@ -1,5 +1,4 @@
<template> <template>
<div class="relative"> <div class="relative">
<!-- Banner Container with mouse events --> <!-- Banner Container with mouse events -->
<div <div
@@ -35,7 +34,6 @@
></banner-editor> ></banner-editor>
</template> </template>
</v-dialog> </v-dialog>
</template> </template>
<script setup> <script setup>
@@ -43,11 +41,11 @@ import BannerEditor from "@/views/creators/BannerEditor.vue";
import {computed, ref} from "vue"; import {computed, ref} from "vue";
import {useBrandingStore} from "@/stores/brandingStore.js"; import {useBrandingStore} from "@/stores/brandingStore.js";
import {useAuthStore} from "@/stores/authStore.js"; import {useAuthStore} from "@/stores/authStore.js";
import { useTranslations } from '@/translations/translations'; import { useI18n } from 'vue-i18n';
const authStore = useAuthStore(); const authStore = useAuthStore();
const brandingStore = useBrandingStore(); const brandingStore = useBrandingStore();
const t = useTranslations(); const { t } = useI18n();
// State // State
const showTint = ref(false); const showTint = ref(false);
@@ -61,12 +59,24 @@ const openBannerEditor = () => {
const isCurrentCreator = computed(() => { const isCurrentCreator = computed(() => {
return authStore.userId === brandingStore.value.id; return authStore.userId === brandingStore.value.id;
}); });
</script> </script>
<style scoped> <style scoped>
.banner { .banner {
@apply border-b border-solid border-hTertiary; @apply border-b border-solid border-hTertiary;
} }
</style>
</style> <i18n>
{
"en": {
"alt": "Creator banner"
},
"fr": {
"alt": "Bannière du créateur"
},
"es": {
"alt": "Banner del creador"
}
}
</i18n>

View File

@@ -1,12 +0,0 @@
{
"social": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Website"
}
}

View File

@@ -1,12 +0,0 @@
{
"social": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Sitio Web"
}
}

View File

@@ -1,12 +0,0 @@
{
"social": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Site Web"
}
}

View File

@@ -12,11 +12,11 @@ import Tiktok from "@/views/svg/Tiktok.vue";
import Reddit from "@/views/svg/Reddit.vue"; import Reddit from "@/views/svg/Reddit.vue";
import Youtube from "@/views/svg/Youtube.vue"; import Youtube from "@/views/svg/Youtube.vue";
import Web from "@/views/svg/Web.vue"; import Web from "@/views/svg/Web.vue";
import { useTranslations } from '@/translations/translations' import { useI18n } from 'vue-i18n'
const brandingStore = useBrandingStore(); const brandingStore = useBrandingStore();
const baseURL = window.location.origin; const baseURL = window.location.origin;
const t = useTranslations(); const { t } = useI18n();
// Gèrer le breakpoint du block information. // Gèrer le breakpoint du block information.
// Définir un point de rupture pour "moyen" (correspondant à md: de Tailwind) // Définir un point de rupture pour "moyen" (correspondant à md: de Tailwind)
@@ -89,56 +89,56 @@ onUnmounted(() => {
<a v-if="brandingStore.value?.socials?.facebookUrl" <a v-if="brandingStore.value?.socials?.facebookUrl"
:href="brandingStore.value?.socials?.facebookUrl" :href="brandingStore.value?.socials?.facebookUrl"
target="_blank" target="_blank"
:title="t('social.facebook')"> :title="t('facebook')">
<facebook class="social-icon"></facebook> <facebook class="social-icon"></facebook>
</a> </a>
<a v-if="brandingStore.value?.socials?.instagramUrl" <a v-if="brandingStore.value?.socials?.instagramUrl"
:href="brandingStore.value?.socials?.instagramUrl" :href="brandingStore.value?.socials?.instagramUrl"
target="_blank" target="_blank"
:title="t('social.instagram')"> :title="t('instagram')">
<instagram class="social-icon"></instagram> <instagram class="social-icon"></instagram>
</a> </a>
<a v-if="brandingStore.value?.socials?.linkedInUrl" <a v-if="brandingStore.value?.socials?.linkedInUrl"
:href="brandingStore.value?.socials?.linkedInUrl" :href="brandingStore.value?.socials?.linkedInUrl"
target="_blank" target="_blank"
:title="t('social.linkedin')"> :title="t('linkedin')">
<linkedin class="social-icon"></linkedin> <linkedin class="social-icon"></linkedin>
</a> </a>
<a v-if="brandingStore.value?.socials?.redditUrl" <a v-if="brandingStore.value?.socials?.redditUrl"
:href="brandingStore.value?.socials?.redditUrl" :href="brandingStore.value?.socials?.redditUrl"
target="_blank" target="_blank"
:title="t('social.reddit')"> :title="t('reddit')">
<reddit class="social-icon"></reddit> <reddit class="social-icon"></reddit>
</a> </a>
<a v-if="brandingStore.value?.socials?.tikTokUrl" <a v-if="brandingStore.value?.socials?.tikTokUrl"
:href="brandingStore.value?.socials?.tikTokUrl" :href="brandingStore.value?.socials?.tikTokUrl"
target="_blank" target="_blank"
:title="t('social.tiktok')"> :title="t('tiktok')">
<tiktok class="social-icon"></tiktok> <tiktok class="social-icon"></tiktok>
</a> </a>
<a v-if="brandingStore.value?.socials?.xUrl" <a v-if="brandingStore.value?.socials?.xUrl"
:href="brandingStore.value?.socials?.xUrl" :href="brandingStore.value?.socials?.xUrl"
target="_blank" target="_blank"
:title="t('social.x')"> :title="t('x')">
<x class="social-icon"></x> <x class="social-icon"></x>
</a> </a>
<a v-if="brandingStore.value?.socials?.youtubeUrl" <a v-if="brandingStore.value?.socials?.youtubeUrl"
:href="brandingStore.value?.socials?.youtubeUrl" :href="brandingStore.value?.socials?.youtubeUrl"
target="_blank" target="_blank"
:title="t('social.youtube')"> :title="t('youtube')">
<youtube class="social-icon"></youtube> <youtube class="social-icon"></youtube>
</a> </a>
<a v-if="brandingStore.value?.socials?.websiteUrl" <a v-if="brandingStore.value?.socials?.websiteUrl"
:href="brandingStore.value?.socials?.websiteUrl" :href="brandingStore.value?.socials?.websiteUrl"
target="_blank" target="_blank"
:title="t('social.website')"> :title="t('website')">
<web class="social-icon"></web> <web class="social-icon"></web>
</a> </a>
@@ -166,4 +166,39 @@ onUnmounted(() => {
@apply flex flex-col gap-y-4; @apply flex flex-col gap-y-4;
} }
} }
</style> </style>
<i18n>
{
"en": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Website"
},
"fr": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Site web"
},
"es": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Sitio web"
}
}
</i18n>

View File

@@ -1,7 +0,0 @@
{
"title": "Choose your Banner",
"description": "The banner must have a 3:1 ratio. Target dimensions are 960 x 320.",
"chooseImage": "Choose an image...",
"clickToEdit": "Click to edit",
"preview": "Banner preview"
}

View File

@@ -1,7 +0,0 @@
{
"title": "Elige tu Banner",
"description": "El banner debe tener una relación de 3:1. Las dimensiones objetivo son 960 x 320.",
"chooseImage": "Elegir una imagen...",
"clickToEdit": "Clic para editar",
"preview": "Vista previa del banner"
}

View File

@@ -1,7 +0,0 @@
{
"title": "Choisissez votre Bannière",
"description": "La bannière doit avoir un ratio de 3:1. Les dimensions cibles sont 960 x 320.",
"chooseImage": "Choisir une image...",
"clickToEdit": "Cliquez pour modifier",
"preview": "Aperçu de la bannière"
}

View File

@@ -72,7 +72,7 @@ import {ref} from 'vue'
import {useClient} from '@/plugins/api.js' import {useClient} from '@/plugins/api.js'
import { Cropper } from 'vue-advanced-cropper' import { Cropper } from 'vue-advanced-cropper'
import 'vue-advanced-cropper/dist/style.css' import 'vue-advanced-cropper/dist/style.css'
import { useTranslations } from '@/translations/translations' import { useI18n } from 'vue-i18n'
const props = defineProps({ const props = defineProps({
creator: { creator: {
@@ -94,7 +94,7 @@ const TARGET_WIDTH = 960
const TARGET_HEIGHT = 320 const TARGET_HEIGHT = 320
// Get translations for this component // Get translations for this component
const t = useTranslations() const { t } = useI18n()
const triggerFileInput = () => { const triggerFileInput = () => {
fileInput.value.click() fileInput.value.click()
@@ -276,3 +276,20 @@ const cancel = () => {
@apply max-h-full; @apply max-h-full;
} }
</style> </style>
<i18n>
{
"en": {
"chooseImage": "Choose an image",
"clickToEdit": "Click to edit"
},
"fr": {
"chooseImage": "Choisir une image",
"clickToEdit": "Cliquez pour modifier"
},
"es": {
"chooseImage": "Elegir una imagen",
"clickToEdit": "Haga clic para editar"
}
}
</i18n>

View File

@@ -1,3 +0,0 @@
{
"title": "Create your Hutopy"
}

View File

@@ -1,3 +0,0 @@
{
"title": "Crea tu Hutopy"
}

View File

@@ -1,3 +0,0 @@
{
"title": "Créez votre Hutopy"
}

View File

@@ -5,7 +5,7 @@ import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
import {useClient} from "@/plugins/api.js"; import {useClient} from "@/plugins/api.js";
import {useRouter, useRoute} from "vue-router"; import {useRouter, useRoute} from "vue-router";
import NameEditor from "@/views/creators/NameEditor.vue"; import NameEditor from "@/views/creators/NameEditor.vue";
import { useTranslations } from '@/translations/translations' import { useI18n } from 'vue-i18n'
const creatorName = ref(''); const creatorName = ref('');
const creatorNameReservationId = ref(undefined); const creatorNameReservationId = ref(undefined);
@@ -18,7 +18,7 @@ const router = useRouter();
const route = useRoute(); const route = useRoute();
const creatorProfileStore = useCreatorProfileStore(); const creatorProfileStore = useCreatorProfileStore();
const userProfileStore = useUserProfileStore(); const userProfileStore = useUserProfileStore();
const t = useTranslations(); const { t } = useI18n();
function handleCreatorNameReservationIdChanged($event) { function handleCreatorNameReservationIdChanged($event) {
creatorNameReservationId.value = $event creatorNameReservationId.value = $event
@@ -112,4 +112,33 @@ async function createAccount() {
@apply flex items-center justify-center; @apply flex items-center justify-center;
} }
</style> </style>
<i18n>
{
"en": {
"title": "Create your Hutopy",
"cancel": "Cancel",
"create": "Create Creator Page",
"errors": {
"unexpected": "An unexpected error occurred"
}
},
"fr": {
"title": "Créez votre Hutopy",
"cancel": "Annuler",
"create": "Créer la page créateur",
"errors": {
"unexpected": "Une erreur inattendue s'est produite"
}
},
"es": {
"title": "Crea tu Hutopy",
"cancel": "Cancelar",
"create": "Crear página de creador",
"errors": {
"unexpected": "Se produjo un error inesperado"
}
}
}
</i18n>

View File

@@ -1,19 +0,0 @@
{
"sections": {
"about": {
"title": "About Us",
"description": "Description",
"mainImage": "Main image"
},
"support": {
"title": "Why Support Us",
"description": "Description",
"subtitle": "Subtitle"
}
},
"fields": {
"phoneNumber": "Phone Number",
"email": "Email Address",
"videoUrl": "Video URL"
}
}

View File

@@ -1,19 +0,0 @@
{
"sections": {
"about": {
"title": "Quiénes Somos",
"description": "Descripción",
"mainImage": "Imagen principal"
},
"support": {
"title": "Por Qué Apoyarnos",
"description": "Descripción",
"subtitle": "Subtítulo"
}
},
"fields": {
"phoneNumber": "Número de Teléfono",
"email": "Dirección de Correo",
"videoUrl": "URL del Video"
}
}

View File

@@ -1,19 +0,0 @@
{
"sections": {
"about": {
"title": "Qui sommes-nous",
"description": "Description",
"mainImage": "Image principale"
},
"support": {
"title": "Pourquoi nous supporter",
"description": "Description",
"subtitle": "Sous-titre"
}
},
"fields": {
"phoneNumber": "Numéro de Téléphone",
"email": "Adresse Email",
"videoUrl": "URL Vidéo"
}
}

View File

@@ -9,7 +9,7 @@
class="primary" class="primary"
@click="isEditMode ? saveChanges() : toggleEditMode()" @click="isEditMode ? saveChanges() : toggleEditMode()"
> >
{{ isEditMode ? t('save') : t('edit') }} {{ isEditMode ? t('common.save') : t('common.edit') }}
</button> </button>
<button <button
@@ -17,14 +17,14 @@
class="secondary" class="secondary"
@click="cancelEdit" @click="cancelEdit"
> >
{{ t('cancel') }} {{ t('common.cancel') }}
</button> </button>
</div> </div>
<!-- MainPage --> <!-- MainPage -->
<div class="flex flex-col mt-4"> <div class="flex flex-col mt-4">
<h1 class="flex justify-start text-2xl font-bold text-center mb-4">{{ t('sections.about.title') }}</h1> <h1 class="flex justify-start text-2xl font-bold text-center mb-4">{{ t('creator.sections.about.title') }}</h1>
<div> <div>
<!-- Main image Bloc D'information--> <!-- Main image Bloc D'information-->
@@ -35,7 +35,7 @@
{{ mainImageText }} {{ mainImageText }}
</p> </p>
</div> </div>
<v-textarea v-if="isEditMode" v-model="editableMainImageText" class="w-full p-2 py-6 " :label="t('sections.about.description')" <v-textarea v-if="isEditMode" v-model="editableMainImageText" class="w-full p-2 py-6 " :label="t('creator.sections.about.description')"
variant="outlined"></v-textarea> variant="outlined"></v-textarea>
<div class="flex flex-row items-center space-x-4"> <div class="flex flex-row items-center space-x-4">
@@ -44,26 +44,26 @@
<img <img
v-if="mainImageUrl" v-if="mainImageUrl"
:src="mainImageUrl" :src="mainImageUrl"
:alt="t('sections.about.mainImage')" :alt="t('creator.sections.about.mainImage')"
class="max-w-full h-auto cursor-pointer"/> class="max-w-full h-auto cursor-pointer"/>
</div> </div>
<div v-if="isEditMode" class="relative flex justify-center"> <div v-if="isEditMode" class="relative flex justify-center">
<label> <label>
<input class="hidden" type="file" @change="updateImage('mainImageUrl', $event)"/> <input class="hidden" type="file" @change="updateImage('mainImageUrl', $event)"/>
<img :src="mainImageUrl || fallbackImage" <img :src="mainImageUrl || fallbackImage"
:alt="t('sections.about.mainImage')" :alt="t('creator.sections.about.mainImage')"
class=" max-w-full h-auto cursor-pointer max-h-96"/> class=" max-w-full h-auto cursor-pointer max-h-96"/>
</label> </label>
<button v-if="isEditMode" <button v-if="isEditMode"
class="absolute top-10 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600" class="absolute top-10 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600"
@click="deleteImage('mainImageUrl')"> @click="deleteImage('mainImageUrl')">
{{ t('delete') }} {{ t('common.delete') }}
</button> </button>
</div> </div>
<div class="w-1/2 flex flex-col justify-center"> <div class="w-1/2 flex flex-col justify-center">
<h2 v-if="videoSubtitleMain" class="text-xl font-semibold text-center"> <h2 v-if="videoSubtitleMain" class="text-xl font-semibold text-center">
{{ t('sections.support.title') }} {{ t('creator.sections.support.title') }}
</h2> </h2>
<div v-if="!isEditMode"> <div v-if="!isEditMode">
@@ -76,7 +76,7 @@
<v-textarea <v-textarea
v-model="editableMainVideoText" v-model="editableMainVideoText"
class="p-2 rounded-md mt-4" class="p-2 rounded-md mt-4"
:label="t('sections.support.description')" :label="t('creator.sections.support.description')"
rows="10" rows="10"
variant="outlined" variant="outlined"
></v-textarea> ></v-textarea>
@@ -92,7 +92,7 @@
<v-text-field <v-text-field
v-model="editableVideoSubtitle" v-model="editableVideoSubtitle"
class="w-full p-2" class="w-full p-2"
:label="t('sections.support.subtitle')" :label="t('creator.sections.support.subtitle')"
variant="outlined" variant="outlined"
></v-text-field> ></v-text-field>
</div> </div>
@@ -100,7 +100,7 @@
<v-textarea v-if="isEditMode" <v-textarea v-if="isEditMode"
v-model="editableVideoText" v-model="editableVideoText"
class="w-full p-2" class="w-full p-2"
:label="t('sections.support.description')" :label="t('creator.sections.support.description')"
variant="outlined" variant="outlined"
></v-textarea> ></v-textarea>
</div> </div>
@@ -123,7 +123,7 @@
<v-text-field <v-text-field
v-model="editableVideoUrlMain" v-model="editableVideoUrlMain"
class="w-full p-2 rounded-md" class="w-full p-2 rounded-md"
:label="t('fields.videoUrl')" :label="t('creator.fields.videoUrl')"
type="text" type="text"
variant="outlined" variant="outlined"
/> />
@@ -141,28 +141,28 @@
<!-- Première image --> <!-- Première image -->
<div v-if="image1Url" class="relative w-full sm:flex-1 "> <div v-if="image1Url" class="relative w-full sm:flex-1 ">
<img :src="image1Url" <img :src="image1Url"
alt="Image 1" :alt="t('creator.sections.about.image1')"
class="rounded-md max-w-full h-auto cursor-pointer"/> class="rounded-md max-w-full h-auto cursor-pointer"/>
</div> </div>
<!-- Deuxième image --> <!-- Deuxième image -->
<div v-if="image2Url" class="relative w-full sm:flex-1 "> <div v-if="image2Url" class="relative w-full sm:flex-1 ">
<img :src="image2Url" <img :src="image2Url"
alt="Image 2" :alt="t('creator.sections.about.image2')"
class="rounded-md max-w-full h-auto cursor-pointer"/> class="rounded-md max-w-full h-auto cursor-pointer"/>
</div> </div>
<!-- Troisième image --> <!-- Troisième image -->
<div v-if="image3Url" class="relative w-full sm:flex-1 "> <div v-if="image3Url" class="relative w-full sm:flex-1 ">
<img :src="image3Url" <img :src="image3Url"
alt="Image 3" :alt="t('creator.sections.about.image3')"
class="rounded-md max-w-full h-auto cursor-pointer"/> class="rounded-md max-w-full h-auto cursor-pointer"/>
</div> </div>
<!-- Quatrième image --> <!-- Quatrième image -->
<div v-if="image4Url" class="relative w-full sm:flex-1 "> <div v-if="image4Url" class="relative w-full sm:flex-1 ">
<img :src="image4Url" <img :src="image4Url"
alt="Image 4" :alt="t('creator.sections.about.image4')"
class="rounded-md max-w-full h-auto cursor-pointer"/> class="rounded-md max-w-full h-auto cursor-pointer"/>
</div> </div>
</div> </div>
@@ -173,19 +173,19 @@
<div v-if="isEditMode" class="rounded-2xl"> <div v-if="isEditMode" class="rounded-2xl">
<!--images--> <!--images-->
<div class=" text-2xl pa-2">Images</div> <div class=" text-2xl pa-2">{{ t('creator.sections.about.images') }}</div>
<div class="pa-2 grid grid-cols-1 gap-4 md:grid-cols-4"> <div class="pa-2 grid grid-cols-1 gap-4 md:grid-cols-4">
<!-- Première image --> <!-- Première image -->
<div class="relative"> <div class="relative">
<label> <label>
<input class="hidden" type="file" @change="updateImage('image1Url', $event)"/> <input class="hidden" type="file" @change="updateImage('image1Url', $event)"/>
<img :src="image1Url || fallbackImage" <img :src="image1Url || fallbackImage"
alt="Image 1" :alt="t('creator.sections.about.image1')"
class="rounded-md max-w-full h-auto cursor-pointer"/> class="rounded-md max-w-full h-auto cursor-pointer"/>
</label> </label>
<button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600" <button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600"
@click="deleteImage('image1Url')"> @click="deleteImage('image1Url')">
X {{ t('common.delete') }}
</button> </button>
</div> </div>
@@ -194,12 +194,12 @@
<label> <label>
<input class="hidden" type="file" @change="updateImage('image2Url', $event)"/> <input class="hidden" type="file" @change="updateImage('image2Url', $event)"/>
<img :src="image2Url || fallbackImage" <img :src="image2Url || fallbackImage"
alt="Image 2" :alt="t('creator.sections.about.image2')"
class="rounded-md max-w-full h-auto cursor-pointer"/> class="rounded-md max-w-full h-auto cursor-pointer"/>
</label> </label>
<button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600" <button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600"
@click="deleteImage('image2Url')"> @click="deleteImage('image2Url')">
X {{ t('common.delete') }}
</button> </button>
</div> </div>
@@ -208,12 +208,12 @@
<label> <label>
<input class="hidden" type="file" @change="updateImage('image3Url', $event)"/> <input class="hidden" type="file" @change="updateImage('image3Url', $event)"/>
<img :src="image3Url || fallbackImage" <img :src="image3Url || fallbackImage"
alt="Image 3" :alt="t('creator.sections.about.image3')"
class="rounded-md max-w-full h-auto cursor-pointer"/> class="rounded-md max-w-full h-auto cursor-pointer"/>
</label> </label>
<button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600" <button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600"
@click="deleteImage('image3Url')"> @click="deleteImage('image3Url')">
X {{ t('common.delete') }}
</button> </button>
</div> </div>
@@ -222,18 +222,18 @@
<label> <label>
<input class="hidden" type="file" @change="updateImage('image4Url', $event)"/> <input class="hidden" type="file" @change="updateImage('image4Url', $event)"/>
<img :src="image4Url || fallbackImage" <img :src="image4Url || fallbackImage"
alt="Image 4" :alt="t('creator.sections.about.image4')"
class="rounded-md max-w-full h-auto cursor-pointer"/> class="rounded-md max-w-full h-auto cursor-pointer"/>
</label> </label>
<button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600" <button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600"
@click="deleteImage('image4Url')"> @click="deleteImage('image4Url')">
X {{ t('common.delete') }}
</button> </button>
</div> </div>
</div> </div>
<!-- Description--> <!-- Description-->
<div class="text-2xl pa-2"> Description</div> <div class="text-2xl pa-2">{{ t('creator.sections.about.description') }}</div>
</div> </div>
<!--Edit--> <!--Edit-->
@@ -241,14 +241,14 @@
<v-text-field <v-text-field
v-model="editablePhoneNumber" v-model="editablePhoneNumber"
class="w-full p-2" class="w-full p-2"
:label="t('fields.phoneNumber')" :label="t('creator.fields.phoneNumber')"
variant="outlined" variant="outlined"
></v-text-field> ></v-text-field>
<v-text-field <v-text-field
v-model="editableEmail" v-model="editableEmail"
class="w-full p-2" class="w-full p-2"
:label="t('fields.email')" :label="t('creator.fields.email')"
variant="outlined" variant="outlined"
></v-text-field> ></v-text-field>
</div> </div>
@@ -284,12 +284,12 @@ import {useClient} from "@/plugins/api.js";
import {useBrandingStore} from "@/stores/brandingStore.js"; import {useBrandingStore} from "@/stores/brandingStore.js";
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js"; import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
import {useAuthStore} from "@/stores/authStore.js"; import {useAuthStore} from "@/stores/authStore.js";
import {useTranslations} from '@/translations/translations' import {useI18n} from 'vue-i18n';
const { t } = useI18n();
const authStore = useAuthStore();
const creatorProfileStore = useCreatorProfileStore(); const creatorProfileStore = useCreatorProfileStore();
const brandingStore = useBrandingStore(); const brandingStore = useBrandingStore();
const authStore = useAuthStore();
const t = useTranslations();
const client = useClient(); const client = useClient();
const isLoading = ref(true); const isLoading = ref(true);
@@ -525,4 +525,105 @@ function cancelEdit() {
border: 0; border: 0;
border-radius: 0.5rem; /* Pour les bords arrondis */ border-radius: 0.5rem; /* Pour les bords arrondis */
} }
</style> </style>
<i18n>
{
"en": {
"common": {
"save": "Save",
"edit": "Edit",
"cancel": "Cancel",
"delete": "Delete"
},
"creator": {
"sections": {
"about": {
"title": "About",
"description": "Description",
"mainImage": "Main image",
"image1": "Image 1",
"image2": "Image 2",
"image3": "Image 3",
"image4": "Image 4",
"images": "Images"
},
"support": {
"title": "Support",
"description": "Description",
"subtitle": "Subtitle"
}
},
"fields": {
"videoUrl": "Video URL",
"phoneNumber": "Phone Number",
"email": "Email"
}
}
},
"fr": {
"common": {
"save": "Enregistrer",
"edit": "Modifier",
"cancel": "Annuler",
"delete": "Supprimer"
},
"creator": {
"sections": {
"about": {
"title": "À propos",
"description": "Description",
"mainImage": "Image principale",
"image1": "Image 1",
"image2": "Image 2",
"image3": "Image 3",
"image4": "Image 4",
"images": "Images"
},
"support": {
"title": "Support",
"description": "Description",
"subtitle": "Sous-titre"
}
},
"fields": {
"videoUrl": "URL de la vidéo",
"phoneNumber": "Numéro de téléphone",
"email": "Email"
}
}
},
"es": {
"common": {
"save": "Guardar",
"edit": "Editar",
"cancel": "Cancelar",
"delete": "Eliminar"
},
"creator": {
"sections": {
"about": {
"title": "Acerca de",
"description": "Descripción",
"mainImage": "Imagen principal",
"image1": "Imagen 1",
"image2": "Imagen 2",
"image3": "Imagen 3",
"image4": "Imagen 4",
"images": "Imágenes"
},
"support": {
"title": "Soporte",
"description": "Descripción",
"subtitle": "Subtítulo"
}
},
"fields": {
"videoUrl": "URL del video",
"phoneNumber": "Número de teléfono",
"email": "Correo electrónico"
}
}
}
}
</i18n>

View File

@@ -1,5 +0,0 @@
{
"deletion": {
"pending": "This Creator page is pending deletion. You can revert the action in your profile."
}
}

View File

@@ -1,5 +0,0 @@
{
"deletion": {
"pending": "Esta página de creador está pendiente de eliminación. Puede revertir la acción en su perfil."
}
}

View File

@@ -1,5 +0,0 @@
{
"deletion": {
"pending": "Cette page de créateur est en attente de suppression. Vous pouvez annuler l'action dans votre profil."
}
}

View File

@@ -3,11 +3,11 @@ import {useBrandingStore} from "@/stores/brandingStore.js";
import {onMounted} from "vue"; import {onMounted} from "vue";
import Banner from "@/views/creators/Banner.vue"; import Banner from "@/views/creators/Banner.vue";
import Footer from "@/views/main/Footer.vue"; import Footer from "@/views/main/Footer.vue";
import { useTranslations } from '@/translations/translations' import { useI18n } from 'vue-i18n';
const brandingStore = useBrandingStore(); const brandingStore = useBrandingStore();
const creatorName = window.location.pathname.split('/@').pop(); const creatorName = window.location.pathname.split('/@').pop();
const t = useTranslations(); const { t } = useI18n();
onMounted(async () => { onMounted(async () => {
await brandingStore.updateBrand(creatorName); await brandingStore.updateBrand(creatorName);
@@ -25,7 +25,7 @@ onMounted(async () => {
<div v-else> <div v-else>
<div v-if="brandingStore.value.isDeleted" <div v-if="brandingStore.value.isDeleted"
class="bg-red-500 p-2 m-4 text-center font-semibold"> class="bg-red-500 p-2 m-4 text-center font-semibold">
{{ t('deletion.pending') }} {{ t('creator.layout.deletion.pending') }}
</div> </div>
<banner></banner> <banner></banner>
<router-view></router-view> <router-view></router-view>
@@ -35,3 +35,35 @@ onMounted(async () => {
</div> </div>
</template> </template>
<i18n>
{
"en": {
"creator": {
"layout": {
"deletion": {
"pending": "This creator page is pending deletion"
}
}
}
},
"fr": {
"creator": {
"layout": {
"deletion": {
"pending": "Cette page créateur est en attente de suppression"
}
}
}
},
"es": {
"creator": {
"layout": {
"deletion": {
"pending": "Esta página de creador está pendiente de eliminación"
}
}
}
}
}
</i18n>

View File

@@ -1,4 +0,0 @@
{
"alt": "Profile Picture",
"edit": "Edit Profile Picture"
}

View File

@@ -1,4 +0,0 @@
{
"alt": "Foto de perfil",
"edit": "Editar foto de perfil"
}

View File

@@ -1,4 +0,0 @@
{
"alt": "Photo de profil",
"edit": "Modifier la photo de profil"
}

View File

@@ -8,14 +8,14 @@
<img <img
class="shadow-2xl object-cover rounded-full border-solid border-hSecondary border-102 w-[200px] h-[200px] logo-image" class="shadow-2xl object-cover rounded-full border-solid border-hSecondary border-102 w-[200px] h-[200px] logo-image"
:src="brandingStore.value.images?.logo ?? '/images/placeholders/profile.png'" :src="brandingStore.value.images?.logo ?? '/images/placeholders/profile.png'"
:alt="t('alt')" :alt="t('logoAlt')"
/> />
<!-- Tint Effect --> <!-- Tint Effect -->
<div <div
v-if="showTint" v-if="showTint"
class="absolute rounded-full inset-0 bg-black/25 cursor-pointer" class="absolute rounded-full inset-0 bg-black/25 cursor-pointer"
:title="t('edit')" :title="t('editLogo')"
> >
<!-- Top-right Icon --> <!-- Top-right Icon -->
<div <div
@@ -43,11 +43,11 @@ import {useAuthStore} from "@/stores/authStore.js";
import {useBrandingStore} from "@/stores/brandingStore.js"; import {useBrandingStore} from "@/stores/brandingStore.js";
import CreatorLogoEditor from "@/views/creators/CreatorLogoEditor.vue"; import CreatorLogoEditor from "@/views/creators/CreatorLogoEditor.vue";
import {computed, ref} from "vue"; import {computed, ref} from "vue";
import { useTranslations } from '@/translations/translations' import { useI18n } from 'vue-i18n'
const authStore = useAuthStore(); const authStore = useAuthStore();
const brandingStore = useBrandingStore(); const brandingStore = useBrandingStore();
const t = useTranslations(); const { t } = useI18n();
// State // State
const showTint = ref(false); const showTint = ref(false);
@@ -69,4 +69,21 @@ const isCurrentCreator = computed(() => {
@apply border-b-4 border-t-4 border-solid border-hTertiary; @apply border-b-4 border-t-4 border-solid border-hTertiary;
} }
</style> </style>
<i18n>
{
"en": {
"logoAlt": "Creator logo",
"editLogo": "Edit logo"
},
"fr": {
"logoAlt": "Logo du créateur",
"editLogo": "Modifier le logo"
},
"es": {
"logoAlt": "Logo del creador",
"editLogo": "Editar logo"
}
}
</i18n>

View File

@@ -1,7 +0,0 @@
{
"title": "Choose your Logo",
"description": "The logo must be square. Recommended dimensions are 200 x 200 pixels.",
"chooseImage": "Choose an image...",
"clickToEdit": "Click to edit",
"preview": "Logo preview"
}

View File

@@ -1,7 +0,0 @@
{
"title": "Elige tu Logo",
"description": "El logo debe ser cuadrado. Las dimensiones recomendadas son 200 x 200 píxeles.",
"chooseImage": "Elegir una imagen...",
"clickToEdit": "Clic para editar",
"preview": "Vista previa del logo"
}

View File

@@ -1,7 +0,0 @@
{
"title": "Choisissez votre Logo",
"description": "Le logo doit être carré. Les dimensions recommandées sont 200 x 200 pixels.",
"chooseImage": "Choisir une image...",
"clickToEdit": "Cliquez pour modifier",
"preview": "Aperçu du logo"
}

View File

@@ -1,12 +1,12 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
{{ t('title') }} {{ t('logoTitle') }}
</div> </div>
<div class="card-content"> <div class="card-content">
<p class="card-text"> <p class="card-text">
{{ t('description') }} {{ t('logoDescription') }}
</p> </p>
<div class="file-input-container"> <div class="file-input-container">
@@ -75,7 +75,7 @@ import {ref} from 'vue'
import {useClient} from '@/plugins/api.js' import {useClient} from '@/plugins/api.js'
import { Cropper, CircleStencil } from 'vue-advanced-cropper' import { Cropper, CircleStencil } from 'vue-advanced-cropper'
import 'vue-advanced-cropper/dist/style.css' import 'vue-advanced-cropper/dist/style.css'
import { useTranslations } from '@/translations/translations' import { useI18n } from 'vue-i18n';
const props = defineProps({ const props = defineProps({
creator: { creator: {
@@ -96,8 +96,7 @@ const cropper = ref(null)
const TARGET_WIDTH = 200 const TARGET_WIDTH = 200
const TARGET_HEIGHT = 200 const TARGET_HEIGHT = 200
// Get translations for this component const { t } = useI18n();
const t = useTranslations()
const triggerFileInput = () => { const triggerFileInput = () => {
fileInput.value.click() fileInput.value.click()
@@ -292,3 +291,26 @@ const cancel = () => {
} }
</style> </style>
<i18n>
{
"en": {
"logoTitle": "Edit Logo",
"logoDescription": "Choose a logo image for your creator page. The image will be cropped to a circle.",
"chooseImage": "Choose Image",
"clickToEdit": "Click to edit"
},
"fr": {
"logoTitle": "Modifier le logo",
"logoDescription": "Choisissez une image de logo pour votre page de créateur. L'image sera recadrée en cercle.",
"chooseImage": "Choisir une image",
"clickToEdit": "Cliquez pour modifier"
},
"es": {
"logoTitle": "Editar logo",
"logoDescription": "Elige una imagen de logo para tu página de creador. La imagen se recortará en círculo.",
"chooseImage": "Elegir imagen",
"clickToEdit": "Haz clic para editar"
}
}
</i18n>

View File

@@ -1,10 +0,0 @@
{
"isupport": "I Support",
"amount": "Amount",
"message": "Message",
"send": "Send",
"cancel": "Cancel",
"errors": {
"payment": "An error occurred during payment processing"
}
}

View File

@@ -1,10 +0,0 @@
{
"isupport": "Apoyo",
"amount": "Cantidad",
"message": "Mensaje",
"send": "Enviar",
"cancel": "Cancelar",
"errors": {
"payment": "Ocurrió un error durante el procesamiento del pago"
}
}

View File

@@ -1,10 +0,0 @@
{
"isupport": "Je Soutiens",
"amount": "Montant",
"message": "Message",
"send": "Envoyer",
"cancel": "Annuler",
"errors": {
"payment": "Une erreur s'est produite lors du traitement du paiement"
}
}

View File

@@ -3,13 +3,13 @@
class="secondary donation-action" class="secondary donation-action"
@click="openDonationDialog()" @click="openDonationDialog()"
> >
{{ t('isupport') }} {{ t('creator.donation.isupport') }}
</button> </button>
<v-dialog v-model="donationModal"> <v-dialog v-model="donationModal">
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
{{ t('isupport') }} {{ t('creator.donation.isupport') }}
</div> </div>
<div class="card-content"> <div class="card-content">
@@ -20,7 +20,7 @@
placeholder="0" placeholder="0"
:min="0" :min="0"
class="p-2" class="p-2"
:label="t('amount')" :label="t('creator.donation.amount')"
density="comfortable" density="comfortable"
variant="outlined" variant="outlined"
hide-details hide-details
@@ -32,7 +32,7 @@
<v-textarea <v-textarea
v-model="tipMessage" v-model="tipMessage"
:label="t('message')" :label="t('creator.donation.message')"
class="p-2" class="p-2"
density="comfortable" density="comfortable"
variant="outlined" variant="outlined"
@@ -45,12 +45,12 @@
<button class="secondary" <button class="secondary"
@click="closeDonationDialog()"> @click="closeDonationDialog()">
{{ t('cancel') }} {{ t('common.cancel') }}
</button> </button>
<button class="primary" <button class="primary"
@click="goPay()"> @click="goPay()">
{{ t('send') }} {{ t('creator.donation.send') }}
</button> </button>
</div> </div>
@@ -71,7 +71,7 @@
class="ma-auto" class="ma-auto"
style="width: 200px" style="width: 200px"
@click="closeDialog()" @click="closeDialog()"
>{{ t('cancel') }} >{{ t('common.cancel') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@@ -84,10 +84,10 @@ import {useClient} from '@/plugins/api.js';
import {useBrandingStore} from '@/stores/brandingStore.js'; import {useBrandingStore} from '@/stores/brandingStore.js';
import {loadStripe} from '@stripe/stripe-js'; import {loadStripe} from '@stripe/stripe-js';
import {onMounted, ref} from 'vue'; import {onMounted, ref} from 'vue';
import { useTranslations } from '@/translations/translations' import { useI18n } from 'vue-i18n';
const brandingStore = useBrandingStore(); const brandingStore = useBrandingStore();
const t = useTranslations(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
creatorId: {default: 'missing-creator-id', required: true}, creatorId: {default: 'missing-creator-id', required: true},
@@ -136,7 +136,7 @@ async function createCheckoutSession() {
return clientSecret.data; return clientSecret.data;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
errorMessage.value = t('errors.payment'); errorMessage.value = t('creator.donation.errors.payment');
} }
} }
@@ -185,3 +185,56 @@ function preventNonNumeric(event) {
padding: 5px; padding: 5px;
} }
</style> </style>
<i18n>
{
"en": {
"common": {
"cancel": "Cancel"
},
"creator": {
"donation": {
"isupport": "I Support",
"amount": "Amount ($)",
"message": "Message (optional)",
"send": "Send",
"errors": {
"payment": "An error occurred during payment processing"
}
}
}
},
"fr": {
"common": {
"cancel": "Annuler"
},
"creator": {
"donation": {
"isupport": "Je Soutiens",
"amount": "Montant ($)",
"message": "Message (optionnel)",
"send": "Envoyer",
"errors": {
"payment": "Une erreur s'est produite lors du traitement du paiement"
}
}
}
},
"es": {
"common": {
"cancel": "Cancelar"
},
"creator": {
"donation": {
"isupport": "Apoyo",
"amount": "Cantidad ($)",
"message": "Mensaje (opcional)",
"send": "Enviar",
"errors": {
"payment": "Ocurrió un error durante el procesamiento del pago"
}
}
}
}
}
</i18n>

View File

@@ -1,3 +0,0 @@
{
"label": "Page name"
}

View File

@@ -1,3 +0,0 @@
{
"label": "Nombre de la página"
}

View File

@@ -1,4 +0,0 @@
{
"label": "Nom de la page"
}
}

View File

@@ -2,7 +2,7 @@
import {ref, onMounted, onUnmounted} from "vue"; import {ref, onMounted, onUnmounted} from "vue";
import {v7} from "uuid"; import {v7} from "uuid";
import {useClient} from "@/plugins/api.js"; import {useClient} from "@/plugins/api.js";
import { useTranslations } from '@/translations/translations' import { useI18n } from 'vue-i18n';
const props = defineProps({ const props = defineProps({
name: { name: {
@@ -19,7 +19,7 @@ const emits = defineEmits([
]); ]);
const name = ref(props.name); const name = ref(props.name);
const t = useTranslations(); const { t } = useI18n();
const isOperationPending = ref(false); const isOperationPending = ref(false);
const reservationState = ref(null); const reservationState = ref(null);
@@ -112,7 +112,7 @@ onUnmounted(() => {
<template> <template>
<v-text-field <v-text-field
variant="outlined" variant="outlined"
:label="t('label')" :label="t('creator.name.label')"
v-model="name" v-model="name"
outlined outlined
@input="handleInput" @input="handleInput"
@@ -134,4 +134,30 @@ onUnmounted(() => {
<style scoped> <style scoped>
</style> </style>
<i18n>
{
"en": {
"creator": {
"name": {
"label": "Page name"
}
}
},
"fr": {
"creator": {
"name": {
"label": "Nom de la page"
}
}
},
"es": {
"creator": {
"name": {
"label": "Nombre de la página"
}
}
}
}
</i18n>

View File

@@ -1,3 +0,0 @@
{
"verified": "Verified Account"
}

View File

@@ -1,3 +0,0 @@
{
"verified": "Cuenta Verificada"
}

View File

@@ -1,3 +0,0 @@
{
"verified": "Compte Vérifié"
}

View File

@@ -21,8 +21,22 @@
<script setup> <script setup>
import IconAccountVerified from "@/components/icons/IconAccountVerified.vue"; import IconAccountVerified from "@/components/icons/IconAccountVerified.vue";
import {useBrandingStore} from "@/stores/brandingStore.js"; import {useBrandingStore} from "@/stores/brandingStore.js";
import { useTranslations } from '@/translations/translations' import { useI18n } from 'vue-i18n';
const brandingStore = useBrandingStore(); const brandingStore = useBrandingStore();
const t = useTranslations(); const { t } = useI18n();
</script> </script>
<i18n>
{
"en": {
"verified": "Verified Account"
},
"fr": {
"verified": "Compte vérifié"
},
"es": {
"verified": "Cuenta verificada"
}
}
</i18n>

View File

@@ -1,10 +0,0 @@
{
"helpandcontact": "Help & Contact",
"faq": "FAQ",
"creatorguide": "Creator Guide",
"termsandconditions": "Terms & Conditions",
"contentpolicy": "Content Policy",
"about": "About",
"pricing": "Pricing",
"allRightsReserved": "All Rights Reserved"
}

View File

@@ -1,10 +0,0 @@
{
"helpandcontact": "Ayuda y Contacto",
"faq": "FAQ",
"creatorguide": "Guía del Creador",
"termsandconditions": "Términos y Condiciones",
"contentpolicy": "Política de Contenido",
"about": "Acerca de",
"pricing": "Precios",
"allRightsReserved": "Todos los Derechos Reservados"
}

View File

@@ -1,10 +0,0 @@
{
"helpandcontact": "Aide & Contact",
"faq": "FAQ",
"creatorguide": "Guide du Créateur",
"termsandconditions": "Conditions Générales",
"contentpolicy": "Politique de Contenu",
"about": "À Propos",
"pricing": "Tarifs",
"allRightsReserved": "Tous Droits Réservés"
}

View File

@@ -2,9 +2,9 @@
import Instagram from "@/views/svg/Instagram.vue"; import Instagram from "@/views/svg/Instagram.vue";
import Facebook from "@/views/svg/Facebook.vue"; import Facebook from "@/views/svg/Facebook.vue";
import X from "@/views/svg/X.vue"; import X from "@/views/svg/X.vue";
import { useTranslations } from "@/translations/translations"; import { useI18n } from 'vue-i18n';
const t = useTranslations(); const { t } = useI18n();
</script> </script>
<template> <template>
@@ -26,36 +26,36 @@ const t = useTranslations();
<div class="footer-links"> <div class="footer-links">
<router-link to="/documents/helpandcontact" <router-link to="/documents/helpandcontact"
class="link"> class="link">
{{ t('helpandcontact') }} {{ t('footer.helpandcontact') }}
</router-link> </router-link>
<router-link to="/documents/faq" <router-link to="/documents/faq"
class="link"> class="link">
{{ t('faq') }} {{ t('footer.faq') }}
</router-link> </router-link>
<router-link to="/documents/guideforcreators" <router-link to="/documents/guideforcreators"
class="link"> class="link">
{{ t('creatorguide') }} {{ t('footer.creatorguide') }}
</router-link> </router-link>
<router-link to="/documents/termsandconditions" <router-link to="/documents/termsandconditions"
class="link"> class="link">
{{ t('termsandconditions') }} {{ t('footer.termsandconditions') }}
</router-link> </router-link>
<router-link to="/documents/contentpolicy" <router-link to="/documents/contentpolicy"
class="link"> class="link">
{{ t('contentpolicy') }} {{ t('footer.contentpolicy') }}
</router-link> </router-link>
<router-link to="/documents/about" <router-link to="/documents/about"
class="link"> class="link">
{{ t('about') }} {{ t('footer.about') }}
</router-link> </router-link>
<router-link to="/documents/pricing" <router-link to="/documents/pricing"
class="link"> class="link">
{{ t('pricing') }} {{ t('footer.pricing') }}
</router-link> </router-link>
</div> </div>
<div class="footer-copyright"> <div class="footer-copyright">
Hutopy &copy;{{ new Date().getFullYear() }} - {{ t('allRightsReserved') }} Hutopy &copy;{{ new Date().getFullYear() }} - {{ t('footer.allRightsReserved') }}
</div> </div>
</footer> </footer>
@@ -90,4 +90,45 @@ const t = useTranslations();
@apply hover:text-gray-400; @apply hover:text-gray-400;
} }
</style> </style>
<i18n>
{
"en": {
"footer": {
"helpandcontact": "Help & Contact",
"faq": "FAQ",
"creatorguide": "Creator Guide",
"termsandconditions": "Terms & Conditions",
"contentpolicy": "Content Policy",
"about": "About",
"pricing": "Pricing",
"allRightsReserved": "All Rights Reserved"
}
},
"fr": {
"footer": {
"helpandcontact": "Aide & Contact",
"faq": "FAQ",
"creatorguide": "Guide du Créateur",
"termsandconditions": "Conditions Générales",
"contentpolicy": "Politique de Contenu",
"about": "À Propos",
"pricing": "Tarifs",
"allRightsReserved": "Tous Droits Réservés"
}
},
"es": {
"footer": {
"helpandcontact": "Ayuda y Contacto",
"faq": "Preguntas Frecuentes",
"creatorguide": "Guía del Creador",
"termsandconditions": "Términos y Condiciones",
"contentpolicy": "Política de Contenido",
"about": "Acerca de",
"pricing": "Precios",
"allRightsReserved": "Todos los Derechos Reservados"
}
}
}
</i18n>

View File

@@ -1,5 +1,8 @@
<script setup> <script setup>
import Footer from "@/views/main/Footer.vue"; import Footer from "@/views/main/Footer.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script> </script>
<template> <template>
@@ -17,13 +20,13 @@ import Footer from "@/views/main/Footer.vue";
<v-btn <v-btn
class="text-white w-full sm:w-auto inscription-btn-header" class="text-white w-full sm:w-auto inscription-btn-header"
to="/login"> to="/login">
Inscription {{ t('inscription') }}
</v-btn> </v-btn>
<v-btn <v-btn
class="w-full sm:w-auto inscription-btn-header-outlined" class="w-full sm:w-auto inscription-btn-header-outlined"
to="/create-creator" to="/create-creator"
variant="outlined"> variant="outlined">
Créer ma page {{ t('createPage') }}
</v-btn> </v-btn>
</div> </div>
@@ -32,10 +35,10 @@ import Footer from "@/views/main/Footer.vue";
<div class="support-container flex flex-col items-center space-y-4 md:flex-row md:space-y-0 md:space-x-6"> <div class="support-container flex flex-col items-center space-y-4 md:flex-row md:space-y-0 md:space-x-6">
<div class="support-text text-justify md:text-left"> <div class="support-text text-justify md:text-left">
<span class="text-white"> Ici, vous <span class="highlight">SOUTENEZ</span> </span><br> <span class="text-white"> {{ t('support') }} </span><br>
<span class="text-white"> les <span class="highlight2">CRÉATEURS</span>, </span><br> <span class="text-white"> {{ t('creators') }} </span><br>
<span class="text-white"> les <span class="highlight2">PROJETS</span> </span><br> <span class="text-white"> {{ t('projects') }} </span><br>
<span class="text-white"> que vous <span class="highlight">AIMEZ</span> </span> <span class="text-white"> {{ t('love') }} </span>
</div> </div>
<img alt="YourHutopy" class="w-48 h-48 md:w-48 md:h-48 object-contain" <img alt="YourHutopy" class="w-48 h-48 md:w-48 md:h-48 object-contain"
src="/images/hutopymedia/banners/heart.png"> src="/images/hutopymedia/banners/heart.png">
@@ -45,34 +48,32 @@ import Footer from "@/views/main/Footer.vue";
<div class="flex flex-col lg:flex-row justify-center items-center lg:space-x-14 space-y-6 lg:space-y-0 pa-1"> <div class="flex flex-col lg:flex-row justify-center items-center lg:space-x-14 space-y-6 lg:space-y-0 pa-1">
<div class="bg-hSurface p-4 max-w-md text-center rounded-3xl space-y-8 shadow-xl h-[520px]"> <div class="bg-hSurface p-4 max-w-md text-center rounded-3xl space-y-8 shadow-xl h-[520px]">
<div class="text-xl mb-2 box-text">Je soutiens</div> <div class="text-xl mb-2 box-text">{{ t('supportText') }}</div>
<img <img
alt="YourHutopy" alt="YourHutopy"
class="max-h-56 mx-auto" class="max-h-56 mx-auto"
src="/images/hutopymedia/homepage/hands.png" src="/images/hutopymedia/homepage/hands.png"
> >
<div class="text-md text-justify px-6 "> <div class="text-md text-justify px-6 ">
Soutenez financièrement vos créateurs préférés et participez directement à leur succès. Chaque contribution {{ t('supportDescription') }}
est un geste fort pour encourager leur talent et leur passion.
</div> </div>
<!-- <v-btn>Soutenir</v-btn> --> <!-- <v-btn>Soutenir</v-btn> -->
</div> </div>
<div class="bg-hSurface p-4 max-w-md text-center rounded-3xl space-y-8 shadow-xl h-[520px]"> <div class="bg-hSurface p-4 max-w-md text-center rounded-3xl space-y-8 shadow-xl h-[520px]">
<div class="text-xl mb-2 box-text">Je crée</div> <div class="text-xl mb-2 box-text">{{ t('create') }}</div>
<img <img
alt="YourHutopy" alt="YourHutopy"
class="max-h-56 mx-auto" class="max-h-56 mx-auto"
src="/images/hutopymedia/homepage/brain.png" src="/images/hutopymedia/homepage/brain.png"
> >
<div class="text-md text-justify px-6"> <div class="text-md text-justify px-6">
Offrez à votre communauté lopportunité de vous soutenir dans vos projets et vos passions. Transformez vos {{ t('creatorDescription') }}
idées en réalité grâce à ceux qui croient en vous.
</div> </div>
<v-btn <v-btn
class="inscription-btn" class="inscription-btn"
to="/login" to="/login"
> >
Inscription {{ t('signup') }}
</v-btn> </v-btn>
</div> </div>
</div> </div>
@@ -85,20 +86,19 @@ import Footer from "@/views/main/Footer.vue";
<div class="space-y-6"> <div class="space-y-6">
<img alt="YourHutopy" class="w-full mb-6" src="/images/hutopymedia/homepage/votrehutopy.png"> <img alt="YourHutopy" class="w-full mb-6" src="/images/hutopymedia/homepage/votrehutopy.png">
<div class="space-y-4"> <div class="space-y-4">
<p class="text-lg leading-relaxed text-justify sm:mx-5 md:mx-1 homepagetext">Hutopy, c'est quoi ?</p> <p class="text-lg leading-relaxed text-justify sm:mx-5 md:mx-1 homepagetext">{{ t('whatIsHutopy') }}</p>
<p class="text-lg leading-relaxed text-justify sm:mx-5 md:mx-1 homepagetext"> <p class="text-lg leading-relaxed text-justify sm:mx-5 md:mx-1 homepagetext">
Hutopy est une plateforme québécoise conçue pour mettre en lumière et soutenir financièrement les créateurs, les projets et les organismes dici. Que vous soyez humoriste, acteur, cuisinier, écrivain, musicien, artisan, entrepreneur social ou organisme communautaire, Hutopy vous offre un espace 100 % gratuit et accessible pour partager votre mission, bâtir votre communauté et recevoir un soutien financier direct. {{ t('hutopyDescription') }}
</p> </p>
<p class="text-lg leading-relaxed text-justify sm:mx-5 md:mx-1 homepagetext"> <p class="text-lg leading-relaxed text-justify sm:mx-5 md:mx-1 homepagetext">
Simple, accessible et humain, Hutopy valorise le talent et le soutien, car chaque geste, aussi petit {{ t('hutopyValues') }}
soit-il, peut faire une grande différence dans laccomplissement de sa propre utopie.
</p> </p>
<div class="flex justify-center"> <div class="flex justify-center">
<v-btn <v-btn
class="text-white mt-12 flex items-center justify-center round create-btn" class="text-white mt-12 flex items-center justify-center round create-btn"
to="/create-creator" to="/create-creator"
> >
Créer ma page {{ t('createPage') }}
</v-btn> </v-btn>
</div> </div>
</div> </div>
@@ -241,4 +241,57 @@ body {
font-family: "Roboto", sans-serif; font-family: "Roboto", sans-serif;
} }
</style> </style>
<i18n>
{
"en": {
"inscription": "Sign Up",
"createPage": "Create Page",
"support": "Support",
"creators": "Creators",
"projects": "Projects",
"love": "Love",
"supportText": "Support",
"supportDescription": "Support your favorite creators and help them grow. Your contributions make a real difference in their creative journey.",
"create": "Create",
"creatorDescription": "Create your own page and start your creative journey. Share your passion with the world and build your community.",
"signup": "Sign Up",
"whatIsHutopy": "What is Hutopy?",
"hutopyDescription": "Hutopy is a platform that connects creators with their audience. We provide tools and features to help creators monetize their content and build their community.",
"hutopyValues": "Our values are centered around creativity, community, and support. We believe in empowering creators to pursue their passions and build sustainable careers."
},
"fr": {
"inscription": "S'inscrire",
"createPage": "Créer une Page",
"support": "Soutenir",
"creators": "Créateurs",
"projects": "Projets",
"love": "Passion",
"supportText": "Soutenir",
"supportDescription": "Soutenez vos créateurs préférés et aidez-les à grandir. Vos contributions font une réelle différence dans leur parcours créatif.",
"create": "Créer",
"creatorDescription": "Créez votre propre page et commencez votre parcours créatif. Partagez votre passion avec le monde et construisez votre communauté.",
"signup": "S'inscrire",
"whatIsHutopy": "Qu'est-ce que Hutopy ?",
"hutopyDescription": "Hutopy est une plateforme qui connecte les créateurs avec leur audience. Nous fournissons des outils et des fonctionnalités pour aider les créateurs à monétiser leur contenu et à construire leur communauté.",
"hutopyValues": "Nos valeurs sont centrées sur la créativité, la communauté et le soutien. Nous croyons en l'autonomisation des créateurs pour poursuivre leurs passions et construire des carrières durables."
},
"es": {
"inscription": "Registrarse",
"createPage": "Crear Página",
"support": "Apoyar",
"creators": "Creadores",
"projects": "Proyectos",
"love": "Pasión",
"supportText": "Apoyar",
"supportDescription": "Apoya a tus creadores favoritos y ayúdales a crecer. Tus contribuciones hacen una diferencia real en su viaje creativo.",
"create": "Crear",
"creatorDescription": "Crea tu propia página y comienza tu viaje creativo. Comparte tu pasión con el mundo y construye tu comunidad.",
"signup": "Registrarse",
"whatIsHutopy": "¿Qué es Hutopy?",
"hutopyDescription": "Hutopy es una plataforma que conecta a los creadores con su audiencia. Proporcionamos herramientas y funciones para ayudar a los creadores a monetizar su contenido y construir su comunidad.",
"hutopyValues": "Nuestros valores se centran en la creatividad, la comunidad y el apoyo. Creemos en empoderar a los creadores para perseguir sus pasiones y construir carreras sostenibles."
}
}
</i18n>

View File

@@ -1,7 +0,0 @@
{
"myPage": "my page",
"myProfile": "my profile",
"reduce": "collapse",
"signIn": "login",
"signOut": "sign out"
}

View File

@@ -1,7 +0,0 @@
{
"myPage": "mi página",
"myProfile": "mi perfil",
"reduce": "reducir",
"signIn": "iniciar sesión",
"signOut": "cerrar sesión"
}

View File

@@ -1,7 +0,0 @@
{
"myPage": "ma page",
"myProfile": "mon profil",
"reduce": "réduire",
"signIn": "connexion",
"signOut": "se déconnecter"
}

View File

@@ -3,11 +3,9 @@ import {useI18n} from "vue-i18n";
import {useAuthStore} from "@/stores/authStore.js"; import {useAuthStore} from "@/stores/authStore.js";
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js"; import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
import {useUserProfileStore} from "@/stores/userProfileStore.js"; import {useUserProfileStore} from "@/stores/userProfileStore.js";
import { useTranslations } from '@/translations/translations'
import {useLanguageStore} from "@/stores/languageStore.js"; import {useLanguageStore} from "@/stores/languageStore.js";
const {locale} = useI18n(); const {locale, t} = useI18n();
const t = useTranslations();
const languageStore = useLanguageStore(); const languageStore = useLanguageStore();
const userProfileStore = useUserProfileStore(); const userProfileStore = useUserProfileStore();
@@ -62,13 +60,13 @@ function toggleLanguage() {
<router-link v-if="creatorProfileStore.hasCreator" :to="`/@${creatorProfileStore.creator.slug}`"> <router-link v-if="creatorProfileStore.hasCreator" :to="`/@${creatorProfileStore.creator.slug}`">
<button class="menu-item-action"> <button class="menu-item-action">
<i class="mdi mdi-file-account-outline"></i> <i class="mdi mdi-file-account-outline"></i>
<span class="label">{{ t('myPage') }}</span> <span class="label">{{ t('sidebar.myPage') }}</span>
</button> </button>
</router-link> </router-link>
<router-link v-else to="/create-creator"> <router-link v-else to="/create-creator">
<button class="menu-item-action"> <button class="menu-item-action">
<i class="mdi mdi-file-account-outline"></i> <i class="mdi mdi-file-account-outline"></i>
<span class="label">{{ t('myPage') }}</span> <span class="label">{{ t('sidebar.myPage') }}</span>
</button> </button>
</router-link> </router-link>
</template> </template>
@@ -77,7 +75,7 @@ function toggleLanguage() {
<router-link to="/profile"> <router-link to="/profile">
<button class="menu-item-action"> <button class="menu-item-action">
<i class="mdi mdi-account"></i> <i class="mdi mdi-account"></i>
<span class="label">{{ t('myProfile') }}</span> <span class="label">{{ t('sidebar.myProfile') }}</span>
</button> </button>
</router-link> </router-link>
</template> </template>
@@ -92,7 +90,7 @@ function toggleLanguage() {
<router-link to="/login"> <router-link to="/login">
<button class="menu-item-action"> <button class="menu-item-action">
<i class="mdi mdi-login"></i> <i class="mdi mdi-login"></i>
<span class="label">{{ t('signIn') }}</span> <span class="label">{{ t('sidebar.signIn') }}</span>
</button> </button>
</router-link> </router-link>
</template> </template>
@@ -100,7 +98,7 @@ function toggleLanguage() {
<button class="menu-item-action" <button class="menu-item-action"
@click="authStore.logout"> @click="authStore.logout">
<i class="mdi mdi-logout"></i> <i class="mdi mdi-logout"></i>
<span class="label">{{ t('signOut') }}</span> <span class="label">{{ t('sidebar.signOut') }}</span>
</button> </button>
</div> </div>
@@ -161,4 +159,33 @@ function toggleLanguage() {
@apply w-10 h-10 justify-center lg:w-full lg:h-auto lg:justify-normal; @apply w-10 h-10 justify-center lg:w-full lg:h-auto lg:justify-normal;
} }
</style> </style>
<i18n>
{
"en": {
"sidebar": {
"myPage": "My Page",
"myProfile": "My Profile",
"signIn": "Sign In",
"signOut": "Sign Out"
}
},
"fr": {
"sidebar": {
"myPage": "Ma Page",
"myProfile": "Mon Profil",
"signIn": "Se Connecter",
"signOut": "Se Déconnecter"
}
},
"es": {
"sidebar": {
"myPage": "Mi Página",
"myProfile": "Mi Perfil",
"signIn": "Iniciar Sesión",
"signOut": "Cerrar Sesión"
}
}
}
</i18n>

View File

@@ -1,23 +0,0 @@
{
"personal": {
"informations": "Personal Information",
"fullname": "Full Name",
"alias": "Alias",
"contact": "Contact Details",
"email": "Email"
},
"creator": {
"informations": "Creator Information",
"name": "Name",
"slug": "URL",
"title": "Title",
"stripeAccountId": "Stripe Account ID",
"socialnetwork": "Social Networks"
},
"danger": {
"title": "Danger Zone",
"warning": "WARNING: This will delete your creator page and suspend all tips and donations.",
"delete": "DELETE PAGE",
"restore": "RESTORE PAGE"
}
}

View File

@@ -1,23 +0,0 @@
{
"personal": {
"informations": "Información personal",
"fullname": "Nombre completo",
"alias": "Alias",
"contact": "Detalles de contacto",
"email": "Correo electrónico"
},
"creator": {
"informations": "Información del creador",
"name": "Nombre",
"slug": "URL",
"title": "Título",
"stripeAccountId": "ID de cuenta Stripe",
"socialnetwork": "Redes sociales"
},
"danger": {
"title": "Zona de peligro",
"warning": "PRECAUCIÓN: Esto eliminará tu página de creador y suspenderá todos los propinas y donaciones.",
"delete": "ELIMINAR PÁGINA",
"restore": "RESTAURAR PÁGINA"
}
}

View File

@@ -1,23 +0,0 @@
{
"personal": {
"informations": "Informations personnelles",
"fullname": "Nom complet",
"alias": "Pseudonyme",
"contact": "Coordonnées",
"email": "Email"
},
"creator": {
"informations": "Informations du créateur",
"name": "Nom",
"slug": "URL",
"title": "Titre",
"stripeAccountId": "ID du compte Stripe",
"socialnetwork": "Réseaux sociaux"
},
"danger": {
"title": "Zone de danger",
"warning": "ATTENTION : Cela supprimera votre page de créateur et suspendra tous les pourboires et dons.",
"delete": "SUPPRIMER LA PAGE",
"restore": "RESTAURER LA PAGE"
}
}

View File

@@ -18,9 +18,9 @@ import Linkedin from "@/views/svg/Linkedin.vue";
import Tiktok from "@/views/svg/Tiktok.vue"; import Tiktok from "@/views/svg/Tiktok.vue";
import Instagram from "@/views/svg/Instagram.vue"; import Instagram from "@/views/svg/Instagram.vue";
import Facebook from "@/views/svg/Facebook.vue"; import Facebook from "@/views/svg/Facebook.vue";
import { useTranslations } from "@/translations/translations"; import { useI18n } from 'vue-i18n';
const t = useTranslations(); const { t } = useI18n();
const userProfileStore = useUserProfileStore() const userProfileStore = useUserProfileStore()
// ### Fullname // ### Fullname
@@ -134,14 +134,14 @@ const closeDialog = () => {
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
{{ t('personal.informations') }} {{ t('personalInfo') }}
</div> </div>
<div class="content"> <div class="content">
<button <button
class="action" class="action"
@click="openEditFullname"> @click="openEditFullname">
<span class="label">{{ t('personal.fullname') }}</span> <span class="label">{{ t('fullName') }}</span>
<span class="value">{{ userProfileStore.fullname }}</span> <span class="value">{{ userProfileStore.fullname }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span> <span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button> </button>
@@ -149,7 +149,7 @@ const closeDialog = () => {
<button <button
class="action" class="action"
@click="openEditAlias"> @click="openEditAlias">
<span class="label">{{ t('personal.alias') }}</span> <span class="label">{{ t('alias') }}</span>
<span class="value">{{ userProfileStore.user.alias }}</span> <span class="value">{{ userProfileStore.user.alias }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span> <span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button> </button>
@@ -160,12 +160,12 @@ const closeDialog = () => {
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
{{ t('personal.contact') }} {{ t('contactInfo') }}
</div> </div>
<div class="content"> <div class="content">
<button class="action" @click="openDialog('EmailDialog')"> <button class="action" @click="openDialog('EmailDialog')">
<span class="label">{{ t('personal.email') }}</span> <span class="label">{{ t('email') }}</span>
<span class="value">{{ userProfileStore.user.email }}</span> <span class="value">{{ userProfileStore.user.email }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span> <span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button> </button>
@@ -176,33 +176,33 @@ const closeDialog = () => {
<template v-if="creatorProfileStore.creator !== undefined"> <template v-if="creatorProfileStore.creator !== undefined">
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
{{ t('creator.informations') }} {{ t('creatorInfo') }}
</div> </div>
<div class="content"> <div class="content">
<!-- NAME --> <!-- NAME -->
<button class="action" @click="openDialog('ChangeNameDialog')"> <button class="action" @click="openDialog('ChangeNameDialog')">
<span class="label">{{ t('creator.name') }}</span> <span class="label">{{ t('name') }}</span>
<span class="value">{{ creatorProfileStore.creator.name }}</span> <span class="value">{{ creatorProfileStore.creator.name }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span> <span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button> </button>
<button class="action" @click="openDialog('ChangeSlugDialog')"> <button class="action" @click="openDialog('ChangeSlugDialog')">
<span class="label">{{ t('creator.slug') }}</span> <span class="label">{{ t('username') }}</span>
<span class="value">@{{ creatorProfileStore.creator.slug }}</span> <span class="value">@{{ creatorProfileStore.creator.slug }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span> <span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button> </button>
<!-- TITLE --> <!-- TITLE -->
<button class="action" @click="openDialog('ChangeTitleDialog')"> <button class="action" @click="openDialog('ChangeTitleDialog')">
<span class="label">{{ t('creator.title') }}</span> <span class="label">{{ t('title') }}</span>
<span class="value">{{ creatorProfileStore.creator.title }}</span> <span class="value">{{ creatorProfileStore.creator.title }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span> <span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button> </button>
<!-- STRIPE --> <!-- STRIPE -->
<button class="action" @click="openDialog('ChangeStripeIdDialog')"> <button class="action" @click="openDialog('ChangeStripeIdDialog')">
<span class="label">{{ t('creator.stripeAccountId') }}</span> <span class="label">{{ t('stripeAccountId') }}</span>
<span class="value">{{ creatorProfileStore.creator.stripeId }}</span> <span class="value">{{ creatorProfileStore.creator.stripeId }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span> <span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button> </button>
@@ -212,7 +212,7 @@ const closeDialog = () => {
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
{{ t('creator.socialnetwork') }} {{ t('socialNetworks') }}
</div> </div>
<div> <div>
@@ -248,7 +248,7 @@ const closeDialog = () => {
<span class="value">{{ creatorProfileStore.creator.socials?.linkedInUrl }}</span> <span class="value">{{ creatorProfileStore.creator.socials?.linkedInUrl }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span> <span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button> </button>
<button class="action" @click="openDialog('SocialsDialog')"> <button class="action" @click="openDialog('SocialsDialog')">
<span class="label"> <span class="label">
<tiktok class="social-icon"></tiktok> <tiktok class="social-icon"></tiktok>
@@ -285,20 +285,20 @@ const closeDialog = () => {
</div> </div>
<div class="card"> <div class="card">
<div class="card-title">{{ t('danger.title') }}</div> <div class="card-title">{{ t('dangerZone') }}</div>
<div class="content"> <div class="content">
<p> <p>
{{ t('danger.warning') }} {{ t('deleteWarning') }}
</p> </p>
<button v-if="!creatorProfileStore.creator.isDeleted" <button v-if="!creatorProfileStore.creator.isDeleted"
class="danger-action" class="danger-action"
@click="creatorProfileStore.removeCreatorPage()"> @click="creatorProfileStore.removeCreatorPage()">
{{ t('danger.delete') }} {{ t('deleteCreatorPage') }}
</button> </button>
<button v-else <button v-else
class="safe-action" class="safe-action"
@click="creatorProfileStore.restoreCreatorPage()"> @click="creatorProfileStore.restoreCreatorPage()">
{{ t('danger.restore') }} {{ t('restoreCreatorPage') }}
</button> </button>
</div> </div>
</div> </div>
@@ -309,7 +309,6 @@ const closeDialog = () => {
</template> </template>
<style scoped> <style scoped>
.card { .card {
@apply bg-hBackground rounded-lg p-4 w-full max-w-2xl; @apply bg-hBackground rounded-lg p-4 w-full max-w-2xl;
} }
@@ -355,4 +354,42 @@ const closeDialog = () => {
@apply mt-4; @apply mt-4;
@apply bg-green-800 hover:bg-green-700 active:bg-green-600; @apply bg-green-800 hover:bg-green-700 active:bg-green-600;
} }
</style> </style>
<i18n>
{
"en": {
"personalInfo": "Personal Information",
"fullName": "Full Name",
"alias": "Alias",
"contactInfo": "Contact Information",
"email": "Email",
"creatorInfo": "Creator Information",
"dangerZone": "Danger Zone",
"deleteWarning": "Are you sure you want to delete your creator page? This action cannot be undone.",
"restoreWarning": "Are you sure you want to restore your creator page? This will make your page visible again."
},
"fr": {
"personalInfo": "Informations Personnelles",
"fullName": "Nom Complet",
"alias": "Alias",
"contactInfo": "Informations de Contact",
"email": "Email",
"creatorInfo": "Informations du Créateur",
"dangerZone": "Zone de Danger",
"deleteWarning": "Êtes-vous sûr de vouloir supprimer votre page de créateur ? Cette action est irréversible.",
"restoreWarning": "Êtes-vous sûr de vouloir restaurer votre page de créateur ? Cela rendra votre page à nouveau visible."
},
"es": {
"personalInfo": "Información Personal",
"fullName": "Nombre Completo",
"alias": "Alias",
"contactInfo": "Información de Contacto",
"email": "Correo Electrónico",
"creatorInfo": "Información del Creador",
"dangerZone": "Zona de Peligro",
"deleteWarning": "¿Estás seguro de que quieres eliminar tu página de creador? Esta acción no se puede deshacer.",
"restoreWarning": "¿Estás seguro de que quieres restaurar tu página de creador? Esto hará que tu página sea visible nuevamente."
}
}
</i18n>

View File

@@ -1,4 +0,0 @@
{
"title": "Address",
"label": "Your address"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Dirección",
"label": "Tu dirección"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Adresse",
"label": "Votre adresse"
}

View File

@@ -25,9 +25,9 @@
<script setup> <script setup>
import {ref} from 'vue'; import {ref} from 'vue';
import { useTranslations } from "@/translations/translations"; import { useI18n } from 'vue-i18n';
const t = useTranslations(); const { t } = useI18n();
const props = defineProps(['address']) const props = defineProps(['address'])
const emit = defineEmits(['close', 'save']) const emit = defineEmits(['close', 'save'])
@@ -36,3 +36,20 @@ const address = ref(props.address);
const requestClose = () => emit('close') const requestClose = () => emit('close')
const requestSave = () => emit('save', address.value) const requestSave = () => emit('save', address.value)
</script> </script>
<i18n>
{
"en": {
"title": "Address",
"label": "Your address"
},
"fr": {
"title": "Adresse",
"label": "Votre adresse"
},
"es": {
"title": "Dirección",
"label": "Tu dirección"
}
}
</i18n>

View File

@@ -1,4 +0,0 @@
{
"title": "Alias",
"label": "Your alias"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Alias",
"label": "Tu alias"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Pseudonyme",
"label": "Votre pseudonyme"
}

View File

@@ -34,9 +34,9 @@
<script setup> <script setup>
import {ref} from 'vue'; import {ref} from 'vue';
import { useTranslations } from "@/translations/translations"; import { useI18n } from 'vue-i18n';
const t = useTranslations(); const { t } = useI18n();
const props = defineProps(['alias']) const props = defineProps(['alias'])
const emit = defineEmits(['close', 'save']) const emit = defineEmits(['close', 'save'])
@@ -46,4 +46,21 @@ const requestClose = () => emit('close')
const requestSave = () => emit('save', alias.value) const requestSave = () => emit('save', alias.value)
</script> </script>
<i18n>
{
"en": {
"title": "Alias",
"label": "Your alias"
},
"fr": {
"title": "Alias",
"label": "Votre alias"
},
"es": {
"title": "Alias",
"label": "Tu alias"
}
}
</i18n>

View File

@@ -1,4 +0,0 @@
{
"title": "Birthday",
"label": "Your birthday"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Fecha de nacimiento",
"label": "Tu fecha de nacimiento"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Date de naissance",
"label": "Votre date de naissance"
}

View File

@@ -25,9 +25,9 @@
<script setup> <script setup>
import {ref} from 'vue'; import {ref} from 'vue';
import { useTranslations } from "@/translations/translations"; import {useI18n} from 'vue-i18n';
const t = useTranslations(); const {t} = useI18n();
const props = defineProps(['birthDate']) const props = defineProps(['birthDate'])
const emit = defineEmits(['close', 'save']) const emit = defineEmits(['close', 'save'])
@@ -36,3 +36,20 @@ const birthDate = ref(props.birthDate)
const requestClose = () => emit('close') const requestClose = () => emit('close')
const requestSave = () => emit('save', birthDate.value) const requestSave = () => emit('save', birthDate.value)
</script> </script>
<i18n>
{
"en": {
"title": "Birthday",
"label": "Your birthday"
},
"fr": {
"title": "Date de naissance",
"label": "Votre date de naissance"
},
"es": {
"title": "Fecha de nacimiento",
"label": "Tu fecha de nacimiento"
}
}
</i18n>

View File

@@ -1,4 +0,0 @@
{
"title": "Email",
"label": "Your email"
}

Some files were not shown because too many files have changed in this diff Show More