From f31e802c8c91ad3c30bcb7b04c6e3b3aec7b4acb Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 16 Jul 2021 02:50:26 +0530 Subject: [PATCH 1/7] add test for Translations keys match --- tests/unit/TranslateTest.js | 39 ++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.js index f46a93bc3ad5..5f9338f1d893 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.js @@ -1,7 +1,9 @@ +const _ = require('underscore'); const translate = require('../../src/libs/translate'); -const translations = require('../../src/languages/translations'); const CONFIG = require('../../src/CONFIG'); +const translations = require('../../src/languages/translations'); +const originalTranslations = _.clone(translations); translations.default = { en: { testKey1: 'English', @@ -52,3 +54,38 @@ describe('translate', () => { expect(translate.translate('en', ['testKeyGroup', 'testFunction'], {testVariable})).toBe(expectedValue); }); }); + +describe('Translation Keys', () => { + let activeLanguage; + let path = ''; + function matchKeys(source, target, key) { + path += key ? `${key}.` : ''; + const pathLevel = path; + if (key && !_.has(target, key)) { + console.debug(`🏹 ${path.slice(0, -1)} is missing from ${activeLanguage}.js`); + return; + } + const sourceOBJ = key ? source[key] : source; + const targetOBJ = key ? target[key] : target; + if (_.isObject(sourceOBJ) && !_.isFunction(sourceOBJ)) { + return _.every(_.keys(sourceOBJ), (subKey) => { + path = pathLevel; + return matchKeys(sourceOBJ, targetOBJ, subKey); + }); + } + if (key) { + path = path.slice(0, -(key.length - 1)); + } + return true; + } + it('Does each locale has all the keys', () => { + const excludeLanguages = ['en', 'es-ES']; + const languages = _.without(_.keys(originalTranslations.default), ...excludeLanguages); + const parentLanguage = originalTranslations.default.en; + const hasAllKeys = _.every(languages, (ln) => { + activeLanguage = ln; + return matchKeys(parentLanguage, originalTranslations.default[ln]); + }); + expect(hasAllKeys).toBeTruthy(); + }); +}); From 9e0fe2ae47fe263896db1e37e65eecc0063214f5 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sat, 17 Jul 2021 23:05:28 +0530 Subject: [PATCH 2/7] added translations --- src/languages/es.js | 123 +++++++++++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 35 deletions(-) diff --git a/src/languages/es.js b/src/languages/es.js index 678cdd8b2536..d5d3f7f35dcd 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -21,10 +21,14 @@ export default { not: 'No', signIn: 'Conectarse', continue: 'Continuar', + firstName: 'Primer nombre', + lastName: 'Apellido', phoneNumber: 'Número de teléfono', email: 'Email', and: 'y', details: 'Detalles', + privacy: 'Intimidad', + privacyPolicy: 'Política de privacidad', delete: 'Eliminar', contacts: 'Contactos', recents: 'Recientes', @@ -103,10 +107,13 @@ export default { writeSomething: 'Escribe algo...', blockedFromConcierge: 'Comunicación no permitida', youAppearToBeOffline: 'Parece que estás desconectado.', + fileUploadFailed: 'Subida fallida. El archivo no es compatible.', }, - reportActionContextMenu: { + contextMenuItem: { copyToClipboard: 'Copiar al Portapapeles', copied: '¡Copiado!', + }, + reportActionContextMenu: { copyLink: 'Copiar Enlace', markAsUnread: 'Marcar como no leído', editComment: 'Editar Commentario', @@ -135,6 +142,7 @@ export default { confirm: 'Confirmar', splitBill: 'Dividir Factura', requestMoney: 'Pedir Dinero', + sendMoney: 'Enviar Dinero', pay: 'Pagar', viewDetails: 'Ver detalles', settleExpensify: 'Pagar con Expensify', @@ -146,6 +154,7 @@ export default { owes: ({manager, owner}) => `${manager} debe a ${owner}`, paid: ({owner, manager}) => `${manager} pagó a ${owner}`, split: ({amount}) => `Dividir ${amount}`, + send: ({amount}) => `Enviar ${amount}`, choosePaymentMethod: 'Elige el método de pago:', noReimbursableExpenses: 'El monto de este informe es inválido', }, @@ -230,7 +239,8 @@ export default { enterYourUsernameToGetPaidViaPayPal: 'Escribe tu nombre de usuario para que otros puedan pagarte a través de PayPal.', payPalMe: 'PayPal.me/', yourPayPalUsername: 'Tu usuario de PayPal', - addPayPalAccount: 'Agregar Cuenta de Paypal', + addPayPalAccount: 'Agregar Cuenta de PayPal', + growlMessageOnSave: 'Su nombre de usuario de PayPal se agregó correctamente', }, paymentMethodList: { addPaymentMethod: 'Agrega método de pago', @@ -255,6 +265,14 @@ export default { expensifyIsOpenSource: 'Expensify.cash es open source', theCode: 'el código', openJobs: 'trabajos disponibles', + heroHeading: 'Dividir cuentas\ny chatear con amigos.', + heroDescription: { + phrase1: 'El dinero habla. Y ahora que el chat y los pagos están en un solo lugar, también es fácil. Sus pagos le llegan tan rápido como puede transmitir su punto.', + phrase2: 'New Expensify es de código abierto. Vista', + phrase3: 'el código', + phrase4: 'Vista', + phrase5: 'vacantes', + }, }, termsOfUse: { phrase1: 'Al usar Expensify.cash, estás aceptando los', @@ -263,9 +281,11 @@ export default { phrase4: 'política de privacidad', phrase5: 'El envío de dinero es brindado por Expensify Payments LLC (NMLS ID:2017010) de conformidad con sus', phrase6: 'licencias', + phrase7: 'licenses', }, passwordForm: { pleaseFillOutAllFields: 'Por favor completa todos los campos', + enterYourTwoFactorAuthenticationCodeToContinue: 'Ingrese su código de autenticación de dos factores para continuar', forgot: '¿Te has olvidado?', twoFactorCode: 'Autenticación de 2 factores', requiredWhen2FAEnabled: 'Obligatorio cuando A2F está habilitado', @@ -301,7 +321,10 @@ export default { }, setPasswordPage: { enterPassword: 'Escribe una contraseña', + confirmNewPassword: 'Confirma la contraseña', setPassword: 'Configura tu Contraseña', + passwordsDontMatch: 'Las contraseñas deben coincidir', + newPasswordPrompt: 'Su contraseña debe tener al menos 8 caracteres, \n1 letra mayúscula, 1 letra minúscula, 1 número.', }, bankAccount: { accountNumber: 'Número de cuenta', @@ -356,6 +379,69 @@ export default { noPhoneNumber: 'Por favor escribe un número de teléfono que incluya el código de país e.g +447814266907', maxParticipantsReached: 'Has llegado al número máximo de participantes para un grupo.', }, + onfidoStep: { + acceptTerms: 'Al continuar con la solicitud para activar su billetera Expensify, confirma que ha leído, comprende y acepta ', + facialScan: 'Política y lanzamiento de la exploración facial de Onfido', + tryAgain: 'Intentar otra vez', + verifyIdentity: 'Verificar identidad', + genericError: 'Hubo un error al procesar este paso. Inténtalo de nuevo.', + }, + additionalDetailsStep: { + headerTitle: 'Detalles adicionales', + helpText: 'Necesitamos confirmar la siguiente información antes de que podamos procesar este pago.', + helpLink: 'Obtenga más información sobre por qué necesitamos esto.', + legalFirstNameLabel: 'Primer nombre legal', + legalMiddleNameLabel: 'Segundo nombre legal', + legalLastNameLabel: 'Apellido legal', + }, + termsStep: { + headerTitle: 'Condiciones y tarifas', + haveReadAndAgree: 'He leído y acepto recibir ', + electronicDisclosures: 'divulgaciones electrónicas', + agreeToThe: 'Estoy de acuerdo con la ', + walletAgreement: 'Acuerdo de billetera', + enablePayments: 'Habilitar pagos', + termsMustBeAccepted: 'Se deben aceptar los términos', + }, + activateStep: { + headerTitle: 'Habilitar pagos', + activated: 'Su billetera Expensify está lista para usar.', + checkBackLater: 'Todavía estamos revisando tu información. Por favor, vuelva más tarde.', + }, + companyStep: { + headerTitle: 'Información de la Empresa', + subtitle: 'Dé más información sobre su empresa.', + legalBusinessName: 'Nombre Comercial Legal', + companyWebsite: 'Company Website', + taxIDNumber: 'Tax ID Number', + companyType: 'Página Web de la Empresa', + incorporationDate: 'Fecha de Incorporación', + industryClassificationCode: 'Código de Clasificación Industrial', + confirmCompanyIsNot: 'Confirmo que esta empresa no está en el', + listOfRestrictedBusinesses: 'lista de negocios restringidos', + incorporationDatePlaceholder: 'Fecha de inicio (aaaa-mm-dd)', + companyPhonePlaceholder: '10 dígitos, sin guiones', + }, + requestorStep: { + headerTitle: 'Información del solicitante', + financialRegulations: 'Las leyes fiscales y el reglamento bancario nos obliga a verificar la identidad de todo individuo que desee añadir una cuenta bancaria representando a una compañía. ', + learnMore: 'Más información', + isMyDataSafe: '¿Están seguros mis datos?', + onFidoConditions: 'Al continuar con la solicitud de añadir esta cuenta bancaria, confirma que ha leído, entiende y acepta ', + onFidoFacialScan: 'Onfido’s Facial Scan Policy and Release', + facialScan: 'la política de reconocimiento facial y la exención de Onfido', + isControllingOfficer: 'Estoy autorizado a utilizar la cuenta bancaria de mi compañía para gastos de empresa', + isControllingOfficerError: 'Debe ser un oficial controlador con autorización para operar la cuenta bancaria de la compañía', + }, + validationStep: { + headerTitle: 'Validar', + buttonText: 'Finalizar Configuración', + maxAttemptError: 'Se ha inhabilitado la validación de esta cuenta bancaria, debido a demasiados intentos incorrectos. Por favor contáctenos.', + description: 'Uno o dos días después de agregar su cuenta a Expensify, enviamos tres (3) transacciones a su cuenta. Tienen una línea comercial como "Expensify, Inc. Validation"', + descriptionCTA: 'Ingrese el monto de cada transacción en los campos a continuación. Ejemplo: 1.51', + reviewingInfo: '¡Gracias! Estamos revisando tu información y nos comunicaremos contigo en breve. Consulte su chat con Concierge ', + forNextSteps: ' para conocer los próximos pasos para terminar de configurar su cuenta bancaria.', + }, beneficialOwnersStep: { beneficialOwners: 'Beneficial Owners', additionalInformation: 'Additional Information', @@ -416,39 +502,6 @@ export default { welcomeNote: ({workspaceName}) => `¡Has sido invitado a la ${workspaceName} Espacio de trabajo! Descargue la aplicación móvil Expensify para comenzar a rastrear sus gastos.`, }, }, - companyStep: { - headerTitle: 'Información de la Empresa', - subtitle: 'Dé más información sobre su empresa.', - legalBusinessName: 'Nombre Comercial Legal', - companyWebsite: 'Company Website', - taxIDNumber: 'Tax ID Number', - companyType: 'Página Web de la Empresa', - incorporationDate: 'Fecha de Incorporación', - industryClassificationCode: 'Código de Clasificación Industrial', - confirmCompanyIsNot: 'Confirmo que esta empresa no está en el', - listOfRestrictedBusinesses: 'lista de negocios restringidos', - incorporationDatePlaceholder: 'Fecha de inicio (aaaa-mm-dd)', - companyPhonePlaceholder: '10 dígitos, sin guiones', - }, - validationStep: { - headerTitle: 'Validar', - buttonText: 'Finalizar Configuración', - maxAttemptError: 'Se ha inhabilitado la validación de esta cuenta bancaria, debido a demasiados intentos incorrectos. Por favor contáctenos.', - description: 'Uno o dos días después de agregar su cuenta a Expensify, enviamos tres (3) transacciones a su cuenta. Tienen una línea comercial como "Expensify, Inc. Validation"', - descriptionCTA: 'Ingrese el monto de cada transacción en los campos a continuación. Ejemplo: 1.51', - reviewingInfo: '¡Gracias! Estamos revisando tu información y nos comunicaremos contigo en breve. Consulte su chat con Concierge ', - forNextSteps: ' para conocer los próximos pasos para terminar de configurar su cuenta bancaria.', - }, - requestorStep: { - headerTitle: 'Información del solicitante', - financialRegulations: 'Las leyes fiscales y el reglamento bancario nos obliga a verificar la identidad de todo individuo que desee añadir una cuenta bancaria representando a una compañía. ', - learnMore: 'Más información', - isMyDataSafe: '¿Están seguros mis datos?', - onFidoConditions: 'Al continuar con la solicitud de añadir esta cuenta bancaria, confirma que ha leído, entiende y acepta ', - facialScan: 'la política de reconocimiento facial y la exención de Onfido', - isControllingOfficer: 'Estoy autorizado a utilizar la cuenta bancaria de mi compañía para gastos de empresa', - isControllingOfficerError: 'Debe ser un oficial controlador con autorización para operar la cuenta bancaria de la compañía', - }, requestCallPage: { requestACall: 'Llámame por teléfono', description: '¿Necesitas ayuda configurando tu cuenta? Nuestro equipo de guías puede ayudarte.', From 6ad65b7580e837514bf6f06875b7375ca77e067a Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 12:57:15 +0530 Subject: [PATCH 3/7] fix: translation tests --- tests/unit/TranslateTest.js | 62 ++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.js index 5f9338f1d893..3913a5f38bc1 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.js @@ -56,36 +56,40 @@ describe('translate', () => { }); describe('Translation Keys', () => { - let activeLanguage; - let path = ''; - function matchKeys(source, target, key) { - path += key ? `${key}.` : ''; - const pathLevel = path; - if (key && !_.has(target, key)) { - console.debug(`🏹 ${path.slice(0, -1)} is missing from ${activeLanguage}.js`); - return; - } - const sourceOBJ = key ? source[key] : source; - const targetOBJ = key ? target[key] : target; - if (_.isObject(sourceOBJ) && !_.isFunction(sourceOBJ)) { - return _.every(_.keys(sourceOBJ), (subKey) => { - path = pathLevel; - return matchKeys(sourceOBJ, targetOBJ, subKey); - }); - } - if (key) { - path = path.slice(0, -(key.length - 1)); - } - return true; + function traverseKeyPath(source, path, keyPaths) { + const pathArray = keyPaths || []; + const keyPath = path ? `${path}.` : ''; + _.each(_.keys(source), (key) => { + if (_.isObject(source[key]) && !_.isFunction(source[key])) { + traverseKeyPath(source[key], keyPath + key, pathArray); + } else { + pathArray.push(keyPath + key); + } + }); + return pathArray; } - it('Does each locale has all the keys', () => { - const excludeLanguages = ['en', 'es-ES']; - const languages = _.without(_.keys(originalTranslations.default), ...excludeLanguages); - const parentLanguage = originalTranslations.default.en; - const hasAllKeys = _.every(languages, (ln) => { - activeLanguage = ln; - return matchKeys(parentLanguage, originalTranslations.default[ln]); + const excludeLanguages = ['en', 'es-ES']; + const languages = _.without(_.keys(originalTranslations.default), ...excludeLanguages); + const parentLanguage = originalTranslations.default.en; + const parentLanguageKeys = traverseKeyPath(parentLanguage); + + _.every(languages, (ln) => { + const languageKeys = traverseKeyPath(originalTranslations.default[ln]); + + it(`Does ${ln} locale has all the keys`, () => { + const hasAllKeys = _.difference(parentLanguageKeys, languageKeys); + if (hasAllKeys.length) { + console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); + } + expect(hasAllKeys.length).toBe(0); + }); + + it(`Does ${ln} locale has unused keys`, () => { + const hasAllKeys = _.difference(languageKeys, parentLanguageKeys); + if (hasAllKeys.length) { + console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); + } + expect(hasAllKeys.length).toBe(0); }); - expect(hasAllKeys).toBeTruthy(); }); }); From 26626ec2a6f4f2d3eec361df931ef40009d69b85 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 13:00:35 +0530 Subject: [PATCH 4/7] fix translation files --- src/languages/es.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/languages/es.js b/src/languages/es.js index d5d3f7f35dcd..4fc61228a13f 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -178,9 +178,7 @@ export default { profilePage: { profile: 'Perfil', tellUsAboutYourself: '¡Cuéntanos algo sobre tí, nos encantaría conocerte!', - firstName: 'Nombre', john: 'Juan', - lastName: 'Apellidos', doe: 'Nadie', preferredPronouns: 'Pronombres preferidos', selectYourPronouns: 'Selecciona tus pronombres', @@ -429,7 +427,6 @@ export default { isMyDataSafe: '¿Están seguros mis datos?', onFidoConditions: 'Al continuar con la solicitud de añadir esta cuenta bancaria, confirma que ha leído, entiende y acepta ', onFidoFacialScan: 'Onfido’s Facial Scan Policy and Release', - facialScan: 'la política de reconocimiento facial y la exención de Onfido', isControllingOfficer: 'Estoy autorizado a utilizar la cuenta bancaria de mi compañía para gastos de empresa', isControllingOfficerError: 'Debe ser un oficial controlador con autorización para operar la cuenta bancaria de la compañía', }, From 555192661257cfb05dcf0a53ee309d82553af2a0 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 13:08:38 +0530 Subject: [PATCH 5/7] added missing key --- src/languages/es.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/languages/es.js b/src/languages/es.js index 5df4823cd798..3f6087eb6d3f 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -4,6 +4,7 @@ export default { cancel: 'Cancelar', yes: 'Si', no: 'No', + ok: 'OK', attachment: 'Archivo Adjunto', to: 'A', optional: 'Opcional', From 158dfb37581d8529889f96c47145e944afc22a8a Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 18:52:34 +0530 Subject: [PATCH 6/7] refactor tests --- tests/unit/TranslateTest.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.js index 3913a5f38bc1..ba9168b5efbf 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.js @@ -70,14 +70,14 @@ describe('Translation Keys', () => { } const excludeLanguages = ['en', 'es-ES']; const languages = _.without(_.keys(originalTranslations.default), ...excludeLanguages); - const parentLanguage = originalTranslations.default.en; - const parentLanguageKeys = traverseKeyPath(parentLanguage); + const mainLanguage = originalTranslations.default.en; + const mainLanguageKeys = traverseKeyPath(mainLanguage); - _.every(languages, (ln) => { + _.each(languages, (ln) => { const languageKeys = traverseKeyPath(originalTranslations.default[ln]); it(`Does ${ln} locale has all the keys`, () => { - const hasAllKeys = _.difference(parentLanguageKeys, languageKeys); + const hasAllKeys = _.difference(mainLanguageKeys, languageKeys); if (hasAllKeys.length) { console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); } @@ -85,7 +85,7 @@ describe('Translation Keys', () => { }); it(`Does ${ln} locale has unused keys`, () => { - const hasAllKeys = _.difference(languageKeys, parentLanguageKeys); + const hasAllKeys = _.difference(languageKeys, mainLanguageKeys); if (hasAllKeys.length) { console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); } From f9baac6621b859ecec0769a858fce0d52e1e40b2 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 23:44:49 +0530 Subject: [PATCH 7/7] fix: tests --- src/languages/es.js | 8 ++------ tests/unit/TranslateTest.js | 7 +++++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/languages/es.js b/src/languages/es.js index 53948bf239a6..ac0378dd024f 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -218,12 +218,6 @@ export default { desktop: { label: 'Desktop', }, - window: { - label: 'Desktop', - }, - Rajat: { - label: 'Desktop', - }, }, signOut: 'Desconectar', versionLetter: 'v', @@ -261,6 +255,7 @@ export default { addFirstPaymentMethod: 'Añade un método de pago para enviar y recibir pagos directamente desde la aplicación', }, preferencesPage: { + mostRecent: 'Más Recientes', mostRecentModeDescription: 'Esta opción muestra por defecto todos los chats, ordenados a partir del más reciente, con los chats destacados arriba de todo', focus: '#concentración', focusModeDescription: '#concentración – Muestra sólo los chats no leídos y destacados ordenados alfabéticamente.', @@ -302,6 +297,7 @@ export default { twoFactorCode: 'Autenticación de 2 factores', requiredWhen2FAEnabled: 'Obligatorio cuando A2F está habilitado', error: { + incorrectLoginOrPassword: 'Usuario o clave incorrectos. Por favor inténtalo de nuevo', twoFactorAuthenticationEnabled: 'Tienes autenticación de 2 factores activada en esta cuenta. Por favor conéctate usando su email o número de teléfono', invalidLoginOrPassword: 'Usuario o clave incorrectos. Por favor inténtalo de nuevo o resetea tu clave', unableToResetPassword: 'No pudimos cambiar tu clave. Probablemente porque el enlace para resetear la clave ha expirado. Te hemos enviado un nuevo enlace. Chequea tu bandeja de entrada y tu carpeta de Spam', diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.js index ba9168b5efbf..5d85bcd3f333 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.js @@ -1,4 +1,5 @@ const _ = require('underscore'); +const {error: AnnotationError} = require('@actions/core'); const translate = require('../../src/libs/translate'); const CONFIG = require('../../src/CONFIG'); const translations = require('../../src/languages/translations'); @@ -80,16 +81,18 @@ describe('Translation Keys', () => { const hasAllKeys = _.difference(mainLanguageKeys, languageKeys); if (hasAllKeys.length) { console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); + AnnotationError(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); } - expect(hasAllKeys.length).toBe(0); + expect(hasAllKeys).toEqual([]); }); it(`Does ${ln} locale has unused keys`, () => { const hasAllKeys = _.difference(languageKeys, mainLanguageKeys); if (hasAllKeys.length) { console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); + AnnotationError(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); } - expect(hasAllKeys.length).toBe(0); + expect(hasAllKeys).toEqual([]); }); }); });