diff --git a/backend/log4js-config.json b/backend/log4js-config.json index 451da56ab..848a4fa79 100644 --- a/backend/log4js-config.json +++ b/backend/log4js-config.json @@ -25,6 +25,14 @@ "keepFileExt" : true, "fileNameSep" : "_" }, + "klicktipp": + { + "type": "dateFile", + "filename": "../logs/backend/klicktipp.log", + "pattern": "%d{ISO8601} %p %c %X{user} %f:%l %m", + "keepFileExt" : true, + "fileNameSep" : "_" + }, "errorFile": { "type": "dateFile", @@ -90,6 +98,17 @@ "level": "debug", "enableCallStack": true }, + "klicktipp": + { + "appenders": + [ + "klicktipp", + "out", + "errors" + ], + "level": "debug", + "enableCallStack": true + }, "http": { "appenders": diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index bb722246a..934efd866 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -30,7 +30,9 @@ export enum RIGHTS { LIST_CONTRIBUTIONS = 'LIST_CONTRIBUTIONS', LIST_ALL_CONTRIBUTIONS = 'LIST_ALL_CONTRIBUTIONS', UPDATE_CONTRIBUTION = 'UPDATE_CONTRIBUTION', + LIST_CONTRIBUTION_LINKS = 'LIST_CONTRIBUTION_LINKS', SEARCH_ADMIN_USERS = 'SEARCH_ADMIN_USERS', + // Admin SEARCH_USERS = 'SEARCH_USERS', SET_USER_ROLE = 'SET_USER_ROLE', @@ -46,7 +48,6 @@ export enum RIGHTS { CREATION_TRANSACTION_LIST = 'CREATION_TRANSACTION_LIST', LIST_TRANSACTION_LINKS_ADMIN = 'LIST_TRANSACTION_LINKS_ADMIN', CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK', - LIST_CONTRIBUTION_LINKS = 'LIST_CONTRIBUTION_LINKS', DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK', UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK', } diff --git a/backend/src/auth/ROLES.ts b/backend/src/auth/ROLES.ts index 7d8025ab9..87bb46bac 100644 --- a/backend/src/auth/ROLES.ts +++ b/backend/src/auth/ROLES.ts @@ -28,7 +28,11 @@ export const ROLE_USER = new Role('user', [ RIGHTS.LIST_CONTRIBUTIONS, RIGHTS.LIST_ALL_CONTRIBUTIONS, RIGHTS.UPDATE_CONTRIBUTION, +<<<<<<< HEAD RIGHTS.SEARCH_ADMIN_USERS, +======= + RIGHTS.LIST_CONTRIBUTION_LINKS, +>>>>>>> master ]) export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index 7b1c6ffcd..f0ce064b4 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -1857,11 +1857,17 @@ describe('AdminResolver', () => { }) }) + // TODO: Set this test in new location to have datas describe('listContributionLinks', () => { - it('returns an error', async () => { + it('returns an empty object', async () => { await expect(query({ query: listContributionLinks })).resolves.toEqual( expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], + data: { + listContributionLinks: { + count: 0, + links: [], + }, + }, }), ) }) diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 84ae09cf8..b7ac63842 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -501,7 +501,7 @@ export class AdminResolver { order: { updatedAt: 'DESC' }, }) - optInCode = await checkOptInCode(optInCode, user.id) + optInCode = await checkOptInCode(optInCode, user) // eslint-disable-next-line @typescript-eslint/no-unused-vars const emailSent = await sendAccountActivationEmail({ 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 b54651a2b..189f7437f 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -47,7 +47,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) @@ -191,7 +191,7 @@ const newEmailOptIn = (userId: number): LoginEmailOptIn => { // if optIn does not exits, it is created export const checkOptInCode = async ( optInCode: LoginEmailOptIn | undefined, - userId: number, + user: DbUser, optInType: OptInType = OptInType.EMAIL_OPT_IN_REGISTER, ): Promise => { logger.info(`checkOptInCode... ${optInCode}`) @@ -211,15 +211,18 @@ export const checkOptInCode = async ( optInCode.updatedAt = new Date() optInCode.resendCount++ } else { - logger.trace('create new OptIn for userId=' + userId) - optInCode = newEmailOptIn(userId) + logger.trace('create new OptIn for userId=' + user.id) + optInCode = newEmailOptIn(user.id) + } + + if (user.emailChecked) { + optInCode.emailOptInTypeId = optInType } - optInCode.emailOptInTypeId = optInType await LoginEmailOptIn.save(optInCode).catch(() => { logger.error('Unable to save optin code= ' + optInCode) throw new Error('Unable to save optin code.') }) - logger.debug(`checkOptInCode...successful: ${optInCode} for userid=${userId}`) + logger.debug(`checkOptInCode...successful: ${optInCode} for userid=${user.id}`) return optInCode } @@ -497,7 +500,7 @@ export class UserResolver { userId: user.id, }) - optInCode = await checkOptInCode(optInCode, user.id, OptInType.EMAIL_OPT_IN_RESET_PASSWORD) + optInCode = await checkOptInCode(optInCode, user, OptInType.EMAIL_OPT_IN_RESET_PASSWORD) logger.info(`optInCode for ${email}=${optInCode}`) // eslint-disable-next-line @typescript-eslint/no-unused-vars const emailSent = await sendResetPasswordEmailMailer({ diff --git a/backend/src/middleware/klicktippMiddleware.ts b/backend/src/middleware/klicktippMiddleware.ts index b3699f29b..6bdaa63fd 100644 --- a/backend/src/middleware/klicktippMiddleware.ts +++ b/backend/src/middleware/klicktippMiddleware.ts @@ -2,6 +2,7 @@ import { MiddlewareFn } from 'type-graphql' import { /* klicktippSignIn, */ getKlickTippUser } from '@/apis/KlicktippController' import { KlickTipp } from '@model/KlickTipp' import CONFIG from '@/config' +import { klickTippLogger as logger } from '@/server/logger' // export const klicktippRegistrationMiddleware: MiddlewareFn = async ( // // Only for demo @@ -29,7 +30,9 @@ export const klicktippNewsletterStateMiddleware: MiddlewareFn = async ( if (klickTippUser) { klickTipp = new KlickTipp(klickTippUser) } - } catch (err) {} + } catch (err) { + logger.error(`There is no user for (email='${result.email}') ${err}`) + } } result.klickTipp = klickTipp return result diff --git a/backend/src/server/logger.ts b/backend/src/server/logger.ts index cbc8c9b9b..0cfa5689b 100644 --- a/backend/src/server/logger.ts +++ b/backend/src/server/logger.ts @@ -12,7 +12,8 @@ log4js.configure(options) const apolloLogger = log4js.getLogger('apollo') const backendLogger = log4js.getLogger('backend') +const klickTippLogger = log4js.getLogger('klicktipp') backendLogger.addContext('user', 'unknown') -export { apolloLogger, backendLogger } +export { apolloLogger, backendLogger, klickTippLogger } 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 07d8c4dcf..bea2b5c98 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -246,6 +246,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 e7dc8ee38..da9ae4abf 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -246,6 +246,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