diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index a2a499224..14e86fa1c 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -198,7 +198,7 @@ describe('UserResolver', () => { it('sets "de" as default language', async () => { await mutate({ mutation: createUser, - variables: { ...variables, email: 'bibi@bloxberg.de', language: 'es' }, + variables: { ...variables, email: 'bibi@bloxberg.de', language: 'fr' }, }) await expect(User.find()).resolves.toEqual( expect.arrayContaining([ diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index a89a8cb0b..47aefe12d 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -43,7 +43,7 @@ const isPassword = (password: string): boolean => { return !!password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9 \\t\\n\\r]).{8,}$/) } -const LANGUAGES = ['de', 'en'] +const LANGUAGES = ['de', 'en', 'es'] const DEFAULT_LANGUAGE = 'de' const isLanguage = (language: string): boolean => { return LANGUAGES.includes(language) diff --git a/docu/Locales/GRADIDO_register_page_spanish.xlsx b/docu/Locales/GRADIDO_register_page_spanish.xlsx new file mode 100644 index 000000000..6b6ec70c0 Binary files /dev/null and b/docu/Locales/GRADIDO_register_page_spanish.xlsx differ diff --git a/frontend/src/components/LanguageSwitch.spec.js b/frontend/src/components/LanguageSwitch.spec.js index cf7c4a35e..1843155e1 100644 --- a/frontend/src/components/LanguageSwitch.spec.js +++ b/frontend/src/components/LanguageSwitch.spec.js @@ -45,7 +45,7 @@ describe('LanguageSwitch', () => { expect(wrapper.find('div.language-switch').exists()).toBeTruthy() }) - describe('with locales en and de', () => { + describe('with locales en, de and es', () => { describe('empty store', () => { describe('navigator language is "en-US"', () => { const languageGetter = jest.spyOn(navigator, 'language', 'get') @@ -69,11 +69,22 @@ describe('LanguageSwitch', () => { }) }) - describe('navigator language is "es-ES" (not supported)', () => { + describe('navigator language is "es-ES"', () => { + const languageGetter = jest.spyOn(navigator, 'language', 'get') + + it('shows Español as language ', async () => { + languageGetter.mockReturnValue('es-ES') + wrapper.vm.setCurrentLanguage() + await wrapper.vm.$nextTick() + expect(wrapper.find('button.dropdown-toggle').text()).toBe('Español - es') + }) + }) + + describe('navigator language is "fr-FR" (not supported)', () => { const languageGetter = jest.spyOn(navigator, 'language', 'get') it('shows English as language ', async () => { - languageGetter.mockReturnValue('es-ES') + languageGetter.mockReturnValue('fr-FR') wrapper.vm.setCurrentLanguage() await wrapper.vm.$nextTick() expect(wrapper.find('button.dropdown-toggle').text()).toBe('English - en') @@ -101,9 +112,18 @@ describe('LanguageSwitch', () => { }) }) + describe('language "es" in store', () => { + it('shows Español as language', async () => { + wrapper.vm.$store.state.language = 'es' + wrapper.vm.setCurrentLanguage() + await wrapper.vm.$nextTick() + expect(wrapper.find('button.dropdown-toggle').text()).toBe('Español - es') + }) + }) + describe('dropdown menu', () => { it('has English and German as languages to choose', () => { - expect(wrapper.findAll('li')).toHaveLength(2) + expect(wrapper.findAll('li')).toHaveLength(3) }) it('has English as first language to choose', () => { @@ -113,6 +133,10 @@ describe('LanguageSwitch', () => { it('has German as second language to choose', () => { expect(wrapper.findAll('li').at(1).text()).toBe('Deutsch') }) + + it('has Español as second language to choose', () => { + expect(wrapper.findAll('li').at(2).text()).toBe('Español') + }) }) }) diff --git a/frontend/src/components/LanguageSwitch2.spec.js b/frontend/src/components/LanguageSwitch2.spec.js index 600e2513e..85a8afa45 100644 --- a/frontend/src/components/LanguageSwitch2.spec.js +++ b/frontend/src/components/LanguageSwitch2.spec.js @@ -66,10 +66,19 @@ describe('LanguageSwitch', () => { expect(wrapper.findAll('span.locales').at(1).text()).toBe('Deutsch') }) }) - describe('navigator language is "es-ES" (not supported)', () => { + describe('navigator language is "es-ES"', () => { + const languageGetter = jest.spyOn(navigator, 'language', 'get') + it('shows Español as language ', async () => { + languageGetter.mockReturnValue('es-ES') + wrapper.vm.setCurrentLanguage() + await wrapper.vm.$nextTick() + expect(wrapper.findAll('span.locales').at(2).text()).toBe('Español') + }) + }) + describe('navigator language is "fr-FR" (not supported)', () => { const languageGetter = jest.spyOn(navigator, 'language', 'get') it('shows English as language ', async () => { - languageGetter.mockReturnValue('es-ES') + languageGetter.mockReturnValue('fr-FR') wrapper.vm.setCurrentLanguage() await wrapper.vm.$nextTick() expect(wrapper.findAll('span.locales').at(0).text()).toBe('English') @@ -93,9 +102,17 @@ describe('LanguageSwitch', () => { expect(wrapper.findAll('span.locales').at(1).text()).toBe('Deutsch') }) }) + describe('language "es" in store', () => { + it('shows Español as language', async () => { + wrapper.vm.$store.state.language = 'es' + wrapper.vm.setCurrentLanguage() + await wrapper.vm.$nextTick() + expect(wrapper.findAll('span.locales').at(2).text()).toBe('Español') + }) + }) describe('language menu', () => { - it('has English and German as languages to choose', () => { - expect(wrapper.findAll('span.locales')).toHaveLength(2) + it('has English, German and Español as languages to choose', () => { + expect(wrapper.findAll('span.locales')).toHaveLength(3) }) it('has English as first language to choose', () => { expect(wrapper.findAll('span.locales').at(0).text()).toBe('English') @@ -103,6 +120,9 @@ describe('LanguageSwitch', () => { it('has German as second language to choose', () => { expect(wrapper.findAll('span.locales').at(1).text()).toBe('Deutsch') }) + it('has Español as third language to choose', () => { + expect(wrapper.findAll('span.locales').at(2).text()).toBe('Español') + }) }) }) diff --git a/frontend/src/components/LanguageSwitchSelect.vue b/frontend/src/components/LanguageSwitchSelect.vue index 545cef4e9..5803a6300 100644 --- a/frontend/src/components/LanguageSwitchSelect.vue +++ b/frontend/src/components/LanguageSwitchSelect.vue @@ -16,6 +16,7 @@ export default { options: [ { value: 'de', text: this.$t('settings.language.de') }, { value: 'en', text: this.$t('settings.language.en') }, + { value: 'es', text: this.$t('settings.language.es') }, ], } }, diff --git a/frontend/src/i18n.js b/frontend/src/i18n.js index 3136c6d80..488012ce2 100644 --- a/frontend/src/i18n.js +++ b/frontend/src/i18n.js @@ -3,6 +3,7 @@ import VueI18n from 'vue-i18n' import en from 'vee-validate/dist/locale/en' import de from 'vee-validate/dist/locale/de' +import es from 'vee-validate/dist/locale/es' Vue.use(VueI18n) @@ -26,6 +27,12 @@ function loadLocaleMessages() { ...messages[locale], } } + if (locale === 'es') { + messages[locale] = { + validations: es, + ...messages[locale], + } + } } }) return messages @@ -58,6 +65,19 @@ const numberFormats = { useGrouping: false, }, }, + es: { + decimal: { + style: 'decimal', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }, + ungroupedDecimal: { + style: 'decimal', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + useGrouping: false, + }, + }, } const dateTimeFormats = { @@ -117,6 +137,34 @@ const dateTimeFormats = { year: 'numeric', }, }, + es: { + short: { + day: 'numeric', + month: 'numeric', + year: 'numeric', + }, + long: { + day: 'numeric', + month: 'long', + year: 'numeric', + weekday: 'long', + hour: 'numeric', + minute: 'numeric', + }, + monthShort: { + month: 'short', + }, + month: { + month: 'long', + }, + year: { + year: 'numeric', + }, + monthAndYear: { + month: 'long', + year: 'numeric', + }, + }, } export default new VueI18n({ diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 4758ada55..7d472adc5 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -253,6 +253,7 @@ "changeLanguage": "Sprache ändern", "de": "Deutsch", "en": "English", + "es": "Español", "success": "Deine Sprache wurde erfolgreich geändert." }, "name": { diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index ae0cde33d..fa2d53a49 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -253,6 +253,7 @@ "changeLanguage": "Change language", "de": "Deutsch", "en": "English", + "es": "Español", "success": "Your language has been successfully updated." }, "name": { diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json new file mode 100644 index 000000000..fa2cd31a9 --- /dev/null +++ b/frontend/src/locales/es.json @@ -0,0 +1,324 @@ +{ + "100": "100%", + "1000thanks": "1000 Gracias, por estar con nosotros!", + "125": "125%", + "85": "85%", + "advanced-calculation": "Proyección", + "auth": { + "left": { + "dignity": "Dignidad", + "donation": "Donación", + "gratitude": "Gratitud", + "hasAccount": "Ya estas registrado?", + "hereLogin": "Regístrate aquí", + "learnMore": "Infórmate aquí …", + "oneDignity": "Damos los unos a los otros y agradecemos con Gradido.", + "oneDonation": "Eres un regalo para la comunidad. 1000 gracias por estar con nosotros.", + "oneGratitude": "Por los demás, por toda la humanidad, por la naturaleza." + }, + "navbar": { + "aboutGradido": "Sobre Gradido" + } + }, + "back": "Volver", + "community": { + "choose-another-community": "Escoger otra comunidad", + "community": "Comunidad", + "continue-to-registration": "Continuar con el registro", + "current-community": "Comunidad actual", + "myContributions": "Mis contribuciones al bien común", + "other-communities": "Otras comunidades", + "submitContribution": "Aportar una contribución", + "switch-to-this-community": "cambiar a esta comunidad" + }, + "contribution": { + "activity": "Actividad", + "alert": { + "communityNoteList": "Aquí encontrarás todas las contribuciones enviadas y confirmadas de todos los miembros de esta comunidad.", + "confirm": "confirmado", + "myContributionNoteList": "Puedes editar o eliminar las contribuciones enviadas que aún no han sido confirmadas en cualquier momento.", + "myContributionNoteSupport": "Pronto existirá la posibilidad de que puedas dialogar con los moderadores. Si tienes algún problema ahora, ponte en contacto con el equipo de asistencia.", + "pending": "Enviado y a la espera de confirmación", + "rejected": "rechazado" + }, + "date": "Contribución para:", + "delete": "Eliminar la contribución. ¿Estás seguro?", + "deleted": "¡La contribución ha sido borrada! Pero seguirá siendo visible.", + "formText": { + "bringYourTalentsTo": "¡Contribuye a la comunidad con tus talentos! Premiamos tu compromiso voluntario con 20 GDD por hora hasta un máximo de 1.000 GDD al mes.", + "describeYourCommunity": "¡Describe tu contribución al bien-común con detalles de las horas e introduce una cantidad de 20 GDD por hora! Tras la confirmación de un moderador, el importe se abonará en tu cuenta.", + "maxGDDforMonth": "Sólo puede presentar un máximo de {amount} GDD para el mes seleccionado.", + "openAmountForMonth": "Para {monthAndYear} aún puedes presentar {creation} GDD.", + "yourContribution": "Tu contribución a la comunidad." + }, + "noDateSelected": "Elige cualquier fecha del mes.", + "selectDate": "¿Cuando fue tu contribución?", + "submit": "Enviar", + "submitted": "Tu contribución ha sido enviada.", + "updated": "La contribución se modificó.", + "yourActivity": "¡Por favor, introduce una actividad!" + }, + "contribution-link": { + "thanksYouWith": "agradecidos con" + }, + "decay": { + "before_startblock_transaction": "Esta transacción no implica disminución en su valor.", + "calculation_decay": "Cálculo de la disminución gradual del valor", + "calculation_total": "Cálculo de la suma total", + "decay": "Disminución gradual del valor", + "decay_introduced": "La disminución gradual empezó el:", + "decay_since_last_transaction": "Disminución gradual", + "last_transaction": "Transacción anterior", + "past_time": "Tiempo transcurrido", + "Starting_block_decay": "Startblock disminución gradual", + "total": "Total", + "types": { + "creation": "Creado", + "noDecay": "sin disminución gradual", + "receive": "Recibido", + "send": "Enviado" + } + }, + "delete": "Eliminar", + "em-dash": "—", + "error": { + "email-already-sent": "Ya te hemos enviado un correo electrónico hace menos de 10 minutos.", + "empty-transactionlist": "Ha habido un error en la transmisión del número de sus transacciones.", + "error": "Error!", + "no-account": "Lamentablemente no hemos podido encontrar una cuenta (activada) con estos datos.", + "no-transactionlist": "Lamentablemente, hubo un error. No se ha transmitido ninguna transacción desde el servidor.", + "no-user": "No hay usuario con estas referencias.", + "session-expired": "La sesión se cerró por razones de seguridad.", + "unknown-error": "Error desconocido: " + }, + "followUs": "sigue nos:", + "footer": { + "app_version": "App versión {version}", + "copyright": { + "link": "Gradido-Akademie", + "year": "© {year}" + }, + "imprint": "Aviso legal", + "privacy_policy": "Protección de Datos", + "short_hash": "({shortHash})", + "whitepaper": "Whitepaper" + }, + "form": { + "amount": "Importe", + "at": "am", + "cancel": "Cancelar", + "change": "Cambiar", + "check_now": "Revisar", + "close": "Cerrar", + "current_balance": "Saldo de cuenta actual", + "date": "Fecha", + "description": "Descripción", + "email": "E-Mail", + "firstname": "Nombre", + "from": "De", + "generate_now": "crear ahora", + "lastname": "Apellido", + "mandatoryField": "campo obligatorio", + "memo": "Mensaje", + "message": "Noticia", + "new_balance": "Saldo de cuenta nuevo depués de confirmación", + "no_gdd_available": "No dispones de GDD para enviar.", + "password": "Contraseña", + "passwordRepeat": "Repetir contraseña", + "password_new": "contraseña nueva", + "password_new_repeat": "Repetir contraseña nueva", + "password_old": "contraseña antigua", + "recipient": "Destinatario", + "reset": "Restablecer", + "save": "Guardar", + "scann_code": "QR Code Scanner - Escanea el código QR de tu pareja", + "sender": "Remitente", + "send_check": "Confirma tu transacción. Por favor revisa toda la información nuevamente!", + "send_now": "Enviar ahora", + "send_transaction_error": "Desafortunadamente, la transacción no se pudo ejecutar!", + "send_transaction_success": "Su transacción fue ejecutada con éxito", + "sorry": "Disculpa", + "thx": "Gracias", + "time": "Tiempo", + "to": "hasta", + "to1": "para", + "validation": { + "gddSendAmount": "El campo {_field_} debe ser un número entre {min} y {max} con un máximo de dos decimales", + "is-not": "No es posible transferirte Gradidos a ti mismo", + "usernmae-regex": "El nombre de usuario debe comenzar con una letra seguida de al menos dos caracteres alfanuméricos.", + "usernmae-unique": "Este nombre de usuario ya está adjudicado." + }, + "your_amount": "Tu importe" + }, + "GDD": "GDD", + "gdd_per_link": { + "choose-amount": "Selecciona una cantidad que te gustaría enviar a través de un enlace. También puedes ingresar un mensaje. Cuando haces clic en 'Generar ahora', se crea un enlace que puedes enviar.", + "copy-link": "Copiar enlace", + "copy-link-with-text": "Copiar texto y enlace", + "created": "El enlace ha sido creado", + "credit-your-gradido": "Para que se te acrediten los Gradidos, haz clic en el enlace!", + "decay-14-day": "Disminución gradual por 14 días", + "delete-the-link": "Eliminar el enlace?", + "deleted": "El enlace ha sido eliminado!", + "expiredOn": "Vencido el:", + "has-account": "Ya tienes una cuenta Gradido?", + "header": "Transferir Gradidos por medio de un enlace", + "isFree": "Gradido es gratis en todo el mundo.", + "link-and-text-copied": "El enlace y su mensaje se han copiado en el portapapeles. Ahora puedes ponerlo en un correo electrónico o mensaje.", + "link-copied": "El enlace se ha copiado en el portapapeles. Ahora puedes pegarlo en un correo electrónico o mensaje.", + "link-deleted": "El enlace se eliminó el {date}.", + "link-expired": "El enlace ya no es válido. La validez expiró el {date}.", + "link-overview": "Resumen de enlaces", + "links_count": "Enlaces activos", + "links_sum": "Enlaces abiertos y códigos QR", + "no-account": "Aún no tienes una cuenta de Gradido?", + "no-redeem": "No puedes canjear tu propio enlace!", + "not-copied": "¡Desafortunadamente, su dispositivo no permite copiar! Copie el enlace manualmente!", + "redeem": "Canjear", + "redeem-text": "¿Quieres canjear el importe ahora?", + "redeemed": "¡Canjeado con éxito! Tu cuenta ha sido acreditada con {n} GDD.", + "redeemed-at": "El enlace ya se canjeó el {date}.", + "redeemed-title": "canjeado", + "to-login": "iniciar sesión", + "to-register": "Registre una nueva cuenta.", + "validUntil": "Válido hasta", + "validUntilDate": "El enlace es válido hasta el {date} ." + }, + "gdt": { + "calculation": "Cálculo del Gradido Transform", + "contribution": "Importe", + "conversion": "Conversión", + "conversion-gdt-euro": "Conversión Euro / Gradido Transform (GDT)", + "credit": "Abono", + "factor": "Factor", + "formula": "Formula de cálculo", + "funding": "Las donaciones", + "gdt": "Gradido Transform", + "gdt-received": "Gradido Transform (GDT) recibido", + "no-transactions": "Aún no tienes un Gradido Transform (GDT).", + "not-reachable": "No es posible acceder al servidor GDT.", + "publisher": "Tu nuevo miembro referido ha pagado la cuota", + "raise": "Aumento", + "recruited-member": "Miembro invitado" + }, + "language": "Idioma", + "link-load": "recargar el último enlace |recargar los últimos {n} enlaces | descargar más {n} enlaces", + "login": "iniciar sesión", + "math": { + "aprox": "~", + "asterisk": "*", + "equal": "=", + "minus": "−", + "pipe": "|" + }, + "message": { + "activateEmail": "Tu cuenta aún no ha sido activada. Por favor revisa tu correo electrónico y haz clic en el enlace de activación o solicita uno nuevo enlace de activación a través de la página restablecer contraseña.", + "checkEmail": "Tu correo electrónico ha sido verificado con éxito. Puedes registrarte ahora.", + "email": "Te hemos enviado un correo electrónico.", + "errorTitle": "Atención!", + "register": "Ya estás registrado, por favor revisa tu correo electrónico y haz clic en el enlace de activación.", + "reset": "Tu contraseña ha sido cambiada.", + "title": "Gracias!", + "unsetPassword": "Tu contraseña aún no ha sido configurada. Por favor reinícialo." + }, + "navigation": { + "admin_area": "Área de administración", + "community": "Comunidad", + "logout": "Salir", + "members_area": "Área de afiliados", + "overview": "Resumen", + "profile": "Mi Perfil", + "send": "Enviar", + "support": "Soporte", + "transactions": "Transacciones" + }, + "qrCode": "Código QR", + "send_gdd": "Enviar GDD", + "send_per_link": "Enviar GDD mediante un enlace", + "session": { + "extend": "Permanecer en sesión iniciada", + "lightText": "Si no has realizado ninguna acción durante más de 10 minutos, se cerrará tu sesión por razones de seguridad.", + "logoutIn": "Cerrar sesión en ", + "warningText": "Aún estas?" + }, + "settings": { + "language": { + "changeLanguage": "Cambiar idioma", + "de": "Deutsch", + "en": "English", + "es": "Español", + "success": "Tu idioma ha sido cambiado con éxito." + }, + "name": { + "change-name": "Cambiar nombre", + "change-success": "Tu nombre ha sido cambiado con éxito." + }, + "newsletter": { + "newsletter": "Informaciones por correo electrónico", + "newsletterFalse": "No recibirás informaciones por correo electrónico.", + "newsletterTrue": "Recibirás informaciones por correo electrónico." + }, + "password": { + "change-password": "Cambiar contraseña", + "forgot_pwd": "Olvide la contraseña?", + "resend_subtitle": "Su enlace de activación ha caducado. Puedes solicitar uno nuevo aquí.", + "reset": "Restablecer contraseña", + "reset-password": { + "text": "Ahora introduce una nueva contraseña con la que quieras acceder a tu cuenta de Gradido en el futuro.." + }, + "send_now": "Enviar", + "set": "Establecer contraseña", + "set-password": { + "text": "Ahora guarda tu nueva contraseña, que podrás utilizar para acceder a tu cuenta de Gradido en el futuro." + }, + "subtitle": "Si has olvidado tu contraseña, puedes restablecerla aquí." + } + }, + "signin": "Iniciar sesión", + "signup": "Registrarse", + "site": { + "forgotPassword": { + "heading": "Por favor, introduce la dirección de correo electrónico con la que estas registrado en Gradido." + }, + "login": { + "heading": "Inicia sesión con tus datos de acceso. Manténlos seguros en todo momento!" + }, + "resetPassword": { + "heading": "Por favor, introduce tu contraseña y repítela." + }, + "signup": { + "agree": "Acepto la Política de privacidad.", + "dont_match": "Las contraseñas no coinciden.", + "heading": "Regístrate introduciendo todos los datos completos y en los campos correctos.", + "lowercase": "Se requiere una letra minúscula.", + "minimum": "Al menos 8 caracteres.", + "no-whitespace": "Sin espacios ni tabulaciones.", + "one_number": "Se requiere un número.", + "special-char": "Caracteres especiales requeridos (por ejemplo, _ o &)", + "uppercase": "Letra mayúscula requerida." + } + }, + "success": "Lo lograste", + "time": { + "days": "Días", + "hours": "Horas", + "minutes": "Minutos", + "month": "Mes", + "months": "Meses", + "seconds": "Segundos", + "years": "Año" + }, + "transaction": { + "gdd-text": "Transacciones Gradido", + "gdt-text": "Transacciones GradidoTransform ", + "nullTransactions": "Todavía no tienes ninguna transacción en tu cuenta.", + "receiverDeleted": "La cuenta del destinatario ha sido eliminada.", + "receiverNotFound": "Destinatario no encontrado", + "show_all": "Ver todas las transacciones de {count}" + }, + "transaction-link": { + "send_you": "te envía" + }, + "via_link": "atraves de un enlace", + "welcome": "Bienvenido a la comunidad." +} diff --git a/frontend/src/locales/index.js b/frontend/src/locales/index.js index 4cb375b40..17d41cad2 100644 --- a/frontend/src/locales/index.js +++ b/frontend/src/locales/index.js @@ -11,6 +11,12 @@ const locales = [ iso: 'de-DE', enabled: true, }, + { + name: 'Español', + code: 'es', + iso: 'es-ES', + enabled: true, + }, ] export default locales