diff --git a/src/index.ts b/src/index.ts index 7932c2d..835e99b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ export { parseErrors } from './lib/errorParser'; export { errorConfig as errors } from './lib/errorConfig'; -export { importTranslations, Options } from './lib/importTranslations'; +export { importTranslations, TranslationOptions } from './lib/importTranslations'; export * from './lib/errors'; diff --git a/src/lib/errorConfig.ts b/src/lib/errorConfig.ts index 04e8d5d..84ab3b4 100644 --- a/src/lib/errorConfig.ts +++ b/src/lib/errorConfig.ts @@ -1,11 +1,11 @@ export const errorConfig = { - INTERNAL_ERROR: { code: 'INTERNAL_ERROR', message: 'An unkown error occurred' }, - INVALID_INPUT: { code: 'INVALID_INPUT', message: 'Invalid input provided' }, - AUTHENTICATION_FAILED: { code: 'AUTHENTICATION_FAILED', message: 'Authentication failed' }, - BAD_REQUEST: { code: 'BAD_REQUEST', message: 'Bad request' }, - MISSING_HEADERS: { code: 'MISSING_HEADERS', message: 'Missing headers' }, - UNAUTHORIZED: { code: 'UNAUTHORIZED', message: 'Unauthorized' }, - FORBIDDEN: { code: 'FORBIDDEN', message: 'No access' }, - RESOURCE_NOT_FOUND: { code: 'RESOURCE_NOT_FOUND', message: 'Resource not found' }, + INTERNAL_ERROR: { code: 'INTERNAL_ERROR', i18n: 'internal_error', message: 'An unkown error occurred' }, + INVALID_INPUT: { code: 'INVALID_INPUT', i18n: 'invalid_input', message: 'Invalid input provided' }, + AUTHENTICATION_FAILED: { code: 'AUTHENTICATION_FAILED', i18n: 'authentication_failed', message: 'Authentication failed' }, + BAD_REQUEST: { code: 'BAD_REQUEST', i18n: 'bad_request', message: 'Bad request' }, + MISSING_HEADERS: { code: 'MISSING_HEADERS', i18n: 'missing_headers', message: 'Missing headers' }, + UNAUTHORIZED: { code: 'UNAUTHORIZED', i18n: 'unauthorized', message: 'Unauthorized' }, + FORBIDDEN: { code: 'FORBIDDEN', i18n: 'forbidden', message: 'No access' }, + RESOURCE_NOT_FOUND: { code: 'RESOURCE_NOT_FOUND', i18n: 'resource_not_found', message: 'Resource not found' }, }; diff --git a/src/lib/errorParser.ts b/src/lib/errorParser.ts index 7f04960..b219d63 100644 --- a/src/lib/errorParser.ts +++ b/src/lib/errorParser.ts @@ -4,7 +4,7 @@ import { errorConfig as errors } from './errorConfig'; import { errorDefaults } from './constants'; import i18n from './i18nConfig'; -export function parseErrors(error: any, language: string) { +export function parseErrors(error: any, language?: string) { const metaData: any = {}; let parsedError = new ApiError(errorDefaults.DEFAULT_HTTP_CODE, errorDefaults.DEFAULT_ERROR); // Default error @@ -28,12 +28,14 @@ export function parseErrors(error: any, language: string) { if (error instanceof ApiError) { i18n.setLocale(language); - let correctMessage = i18n.__(error.code); - // if translation is not found, return default English message - if (correctMessage === error.code) { - correctMessage = error.message; + let translatedMessage = i18n.__(error.i18n); + // if the translatedMessage equals the error code or is undefined + // no translation is found + // fallback to default error message from ErrorConfig + if (translatedMessage === error.i18n || translatedMessage === undefined) { + translatedMessage = error.message; } - parsedError = Object.assign({}, error, { message: correctMessage }); + parsedError = Object.assign({}, error, { message: translatedMessage }); } // Return object easy to use for serialisation diff --git a/src/lib/errors.ts b/src/lib/errors.ts index 7cb03c5..08ecf28 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -5,6 +5,7 @@ import { errorConfig as errors } from './errorConfig'; export class ApiError extends Error { code: string; status: number; + i18n?: string; id?: string; detail?: string; @@ -14,6 +15,7 @@ export class ApiError extends Error { this.name = 'ApiError'; this.id = uuid.v1(); this.code = error.code; + this.i18n = error.i18n; this.status = status; this.detail = detail; if (stack) this.stack = stack; @@ -73,6 +75,7 @@ export class AuthenticationError extends ApiError { export interface ErrorType { code: string; message: string; + i18n?: string; } export interface ErrorArgs { diff --git a/src/lib/i18nConfig.ts b/src/lib/i18nConfig.ts index 3661e1a..c166c29 100644 --- a/src/lib/i18nConfig.ts +++ b/src/lib/i18nConfig.ts @@ -2,6 +2,8 @@ import * as i18n from 'i18n'; i18n.configure({ directory: __dirname + '/locales', + defaultLocale: 'en', + updateFiles: false, }); export default i18n; diff --git a/src/lib/importTranslations.ts b/src/lib/importTranslations.ts index 3171a5a..e9a4715 100644 --- a/src/lib/importTranslations.ts +++ b/src/lib/importTranslations.ts @@ -5,16 +5,12 @@ const defaultOptions = { destination: './lib/locales', }; -export function importTranslations(token: string, options?: Options) { - try { - const allOptions = Object.assign({}, defaultOptions, options); - return icappsTranslation.import(url, token, allOptions); - } catch (ex) { - throw ex; - } +export function importTranslations(token: string, options?: TranslationOptions) { + const allOptions = Object.assign({}, defaultOptions, options); + return icappsTranslation.import(url, token, allOptions); } -export interface Options { +export interface TranslationOptions { destination?: string; clean?: boolean; verbose?: boolean; diff --git a/tests/errorParser.test.ts b/tests/errorParser.test.ts index 17327a0..d16ce4d 100644 --- a/tests/errorParser.test.ts +++ b/tests/errorParser.test.ts @@ -2,13 +2,23 @@ import * as httpStatus from 'http-status'; import { ValidationError } from 'express-validation'; import { parseErrors, ApiError, errors } from '../src'; import { errorDefaults } from '../src/lib/constants'; +import * as i18n from 'i18n'; describe('errorParser', () => { const defaultError = new ApiError(errorDefaults.DEFAULT_HTTP_CODE, errorDefaults.DEFAULT_ERROR); + let i18nMock; + + beforeEach(() => { + i18nMock = jest.spyOn(i18n, '__'); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); describe('Unkown errors', () => { it('Should return the default error when unknown object is passed', () => { - const parsedError = parseErrors({ myProp: 'iDunno' }, 'en'); + const parsedError = parseErrors({ myProp: 'iDunno' }); expect(parsedError).toMatchObject({ id: expect.any(String), status: defaultError.status, @@ -27,7 +37,7 @@ describe('errorParser', () => { const error = new Error('Some error...'); Object.assign(error, { stack: 'myStack', detail: 'some details', schema: 'public' }); - const parsedError = parseErrors(error, 'en'); + const parsedError = parseErrors(error); expect(parsedError).toMatchObject({ id: expect.any(String), status: defaultError.status, @@ -51,7 +61,7 @@ describe('errorParser', () => { }; const expressValidationError = new ValidationError([{ name: 'myField' }], options); - const parsedError = parseErrors(expressValidationError, 'en'); + const parsedError = parseErrors(expressValidationError); expect(parsedError).toMatchObject({ id: expect.any(String), status: httpStatus.BAD_REQUEST, @@ -63,10 +73,27 @@ describe('errorParser', () => { describe('Predefined Api errors', () => { it('Should succesfully parse default ApiError', () => { + const errorTranslation = 'English translation'; + i18nMock.mockImplementation(() => errorTranslation); + try { throw new ApiError(httpStatus.BAD_REQUEST, errors.INVALID_INPUT); } catch (err) { const parsedError = parseErrors(err, 'en'); + expect(parsedError).toMatchObject({ + id: expect.any(String), + status: httpStatus.BAD_REQUEST, + code: errors.INVALID_INPUT.code, + title: errorTranslation, + detail: errorTranslation, + }); + } + }); + it('Should succesfully parse default ApiError with default message when language is not available', () => { + try { + throw new ApiError(httpStatus.BAD_REQUEST, errors.INVALID_INPUT); + } catch (err) { + const parsedError = parseErrors(err, 'du'); expect(parsedError).toMatchObject({ id: expect.any(String), status: httpStatus.BAD_REQUEST, @@ -76,6 +103,23 @@ describe('errorParser', () => { }); } }); + it('Should succesfully parse default ApiError for Dutch translation', () => { + const errorTranslation = 'Nederlands vertaling'; + i18nMock.mockImplementation(() => errorTranslation); + + try { + throw new ApiError(httpStatus.BAD_REQUEST, errors.INVALID_INPUT); + } catch (err) { + const parsedError = parseErrors(err, 'nl'); + expect(parsedError).toMatchObject({ + id: expect.any(String), + status: httpStatus.BAD_REQUEST, + code: errors.INVALID_INPUT.code, + title: errorTranslation, + detail: errorTranslation, + }); + } + }); // TODO: Custom cases }); diff --git a/tests/translations.test.ts b/tests/translations.test.ts index 92e418a..9cc44da 100644 --- a/tests/translations.test.ts +++ b/tests/translations.test.ts @@ -3,13 +3,13 @@ import { importTranslations } from '../src'; import * as icappsTranslation from 'icapps-translations'; describe('importTranslations', () => { - const mock = jest.spyOn(icappsTranslation, 'import').mockImplementation(() => { }); + const icappsTranslationMock = jest.spyOn(icappsTranslation, 'import').mockImplementation(() => { }); afterAll(() => { jest.clearAllMocks(); }); it('should import the translations', async () => { await importTranslations('randomToken'); - expect(mock).toHaveBeenCalledTimes(1); + expect(icappsTranslationMock).toHaveBeenCalledTimes(1); }); });