Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/add_i18n_functionality #7

Merged
merged 3 commits into from
Apr 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ package-lock.json
# Yarn
.yarnclean
yarn-error.log

# Exclude translation files
src/lib/locales/*.json
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ const parsedError = parseErrors(error);
serializer.serialize([parsedError]);
```

## Import translations

Import new or updated translations from the iCapps translation portal

```javascript
import { importTranslations } from 'tree-house-errors';

await importTranslations('applicationToken');
```

## Tests

- You can run `yarn test` to run all tests
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@
"dependencies": {
"express-validation": "~1.0.2",
"http-status": "~1.0.1",
"i18n": "~0.8.3",
"joi": "~13.1.2",
"uuid": "~3.2.1"
},
"devDependencies": {
"@types/http-status": "~0.2.30",
"@types/i18n": "~0.8.3",
"@types/jest": "~22.2.0",
"@types/uuid": "~3.4.3",
"coveralls": "^3.0.0",
"icapps-translations": "^0.0.15",
"jest": "^22.1.4",
"np": "^2.20.1",
"pre-commit": "^1.2.2",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { parseErrors } from './lib/errorParser';
export { errorConfig as errors } from './lib/errorConfig';
export { importTranslations, TranslationOptions } from './lib/importTranslations';
export * from './lib/errors';
16 changes: 8 additions & 8 deletions src/lib/errorConfig.ts
Original file line number Diff line number Diff line change
@@ -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' },
};
15 changes: 13 additions & 2 deletions src/lib/errorParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import * as ev from 'express-validation';
import { ApiError, ValidationError } from './errors';
import { errorConfig as errors } from './errorConfig';
import { errorDefaults } from './constants';
import i18n from './i18nConfig';

export function parseErrors(error: any) {
export function parseErrors(error: any, language?: string) {
const metaData: any = {};
let parsedError = new ApiError(errorDefaults.DEFAULT_HTTP_CODE, errorDefaults.DEFAULT_ERROR); // Default error

// Other errors
if (error instanceof Error) {
Object.assign(metaData, { stack: JSON.stringify(error.stack) });

if (error.hasOwnProperty('schema') && error.hasOwnProperty('detail')) { // knex.js specific errors
const errorData = <any>error;
Object.assign(metaData, errorData);
Expand All @@ -25,7 +27,16 @@ export function parseErrors(error: any) {

// Own thrown ApiErrors
if (error instanceof ApiError) {
parsedError = error;
i18n.setLocale(language);

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: translatedMessage });
}

// Return object easy to use for serialisation
Expand Down
3 changes: 3 additions & 0 deletions src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -73,6 +75,7 @@ export class AuthenticationError extends ApiError {
export interface ErrorType {
code: string;
message: string;
i18n?: string;
}

export interface ErrorArgs {
Expand Down
9 changes: 9 additions & 0 deletions src/lib/i18nConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as i18n from 'i18n';

i18n.configure({
directory: __dirname + '/locales',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaultLocale: 'en',

defaultLocale: 'en',
updateFiles: false,
});

export default i18n;
19 changes: 19 additions & 0 deletions src/lib/importTranslations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as icappsTranslation from 'icapps-translations';

const url = '';
const defaultOptions = {
destination: './lib/locales',
};

export function importTranslations(token: string, options?: TranslationOptions) {
const allOptions = Object.assign({}, defaultOptions, options);
return icappsTranslation.import(url, token, allOptions);
}

export interface TranslationOptions {
destination?: string;
clean?: boolean;
verbose?: boolean;
seperateCategories?: boolean;
exportType?: string;
}
Empty file added src/lib/locales/.gitkeep
Empty file.
46 changes: 45 additions & 1 deletion tests/errorParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@ 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', () => {
Expand Down Expand Up @@ -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);
const parsedError = parseErrors(err, 'du');
expect(parsedError).toMatchObject({
id: expect.any(String),
status: httpStatus.BAD_REQUEST,
Expand All @@ -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
});
Expand Down
15 changes: 15 additions & 0 deletions tests/translations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as fs from 'fs';
import { importTranslations } from '../src';
import * as icappsTranslation from 'icapps-translations';

describe('importTranslations', () => {
const icappsTranslationMock = jest.spyOn(icappsTranslation, 'import').mockImplementation(() => { });
afterAll(() => {
jest.clearAllMocks();
});

it('should import the translations', async () => {
await importTranslations('randomToken');
expect(icappsTranslationMock).toHaveBeenCalledTimes(1);
});
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
"exclude": [
"node_modules",
"**/*.test.ts",
"build"
"build",
]
}
Loading