forked from AccelByte/justice-js-common-utils
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AB-768 add service error translator library
- Loading branch information
yanief
committed
Sep 3, 2019
1 parent
1bb20f0
commit 21a98ba
Showing
15 changed files
with
900 additions
and
778 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
* Copyright (c) 2018-2019 AccelByte Inc. All Rights Reserved. | ||
* This is licensed software from AccelByte Inc, for limitations | ||
* and restrictions contact your company contract manager. | ||
*/ | ||
|
||
const fs = require("fs"); | ||
const path = require("path"); | ||
const flatten = require("flat"); | ||
const { Parser } = require("i18next-scanner"); | ||
const JSON5 = require("json5"); | ||
|
||
const ts = require("typescript"); | ||
|
||
function getTranslationFromFile(filePath) { | ||
let error; | ||
let result; | ||
try { | ||
const parser = new Parser({ | ||
func: { list: ["i18next.t", "i18n.t", "t", "_t", "translation"] }, | ||
}); | ||
const content = fs.readFileSync(filePath, "utf8"); | ||
const compiled = ts.transpileModule(content, { | ||
compilerOptions: { | ||
target: "es2018", | ||
}, | ||
fileName: path.basename(filePath), | ||
}).outputText; | ||
parser.parseFuncFromString(compiled).parseTransFromString(compiled); | ||
result = flatten(parser.get().en.translation); | ||
} catch (err) { | ||
console.error(err); | ||
error = err; | ||
} | ||
return { error, result }; | ||
} | ||
|
||
function getAllTranslations(files) { | ||
let translations = {}; | ||
files.forEach(filePath => { | ||
const { err, result } = getTranslationFromFile(filePath); | ||
if (err) console.error(err); | ||
if (!result) console.warn(`Warning: cannot parse ${filePath}`); | ||
if (result) { | ||
translations = Object.assign(translations, flatten(result)); | ||
} | ||
}); | ||
return flatten(translations, { safe: true }); | ||
} | ||
|
||
function getOldTranslationsMap(languages, directory) { | ||
const oldTranslationsMap = {}; | ||
languages.forEach(lang => { | ||
try { | ||
const fileContent = fs.readFileSync( | ||
path.resolve(directory, `${lang}.json`), | ||
"utf8" | ||
); | ||
oldTranslationsMap[lang] = flatten(JSON5.parse(fileContent), { | ||
safe: true, | ||
}); | ||
} catch (error) { | ||
oldTranslationsMap[lang] = {}; | ||
} | ||
}); | ||
return oldTranslationsMap; | ||
} | ||
|
||
function getUnusedKeysFromOldTranslations(oldTranslation, currentTranslation) { | ||
const keys = Object.keys(currentTranslation).reduce( | ||
(keys, translationKey) => { | ||
keys.delete(translationKey); | ||
return keys; | ||
}, | ||
new Set(Object.keys(oldTranslation)) | ||
); | ||
keys.delete("_unused"); | ||
return Array.from(keys); | ||
} | ||
|
||
function sortTranslationKeys(translation) { | ||
const keys = Object.keys(translation); | ||
keys.sort(); | ||
return keys.reduce((sorted, key) => { | ||
sorted[key] = translation[key]; | ||
return sorted; | ||
}, {}); | ||
} | ||
|
||
function writeTranslationMap(oldTranslationsMap, translationsMap, directory) { | ||
Object.entries(translationsMap).forEach(([lang, translation]) => { | ||
const oldJSON = JSON.stringify( | ||
sortTranslationKeys(oldTranslationsMap[lang] || {}), | ||
null, | ||
2 | ||
); | ||
const newJSON = JSON.stringify(sortTranslationKeys(translation), null, 2); | ||
if (oldJSON !== newJSON) { | ||
fs.writeFileSync( | ||
path.resolve(directory, `${lang}.json`), | ||
newJSON, | ||
"utf8" | ||
); | ||
} | ||
}); | ||
} | ||
|
||
function startBuild({ languages, files, directory }) { | ||
const translations = getAllTranslations(files); | ||
const oldTranslationsMap = getOldTranslationsMap(languages, directory); | ||
const newTranslationMap = {}; | ||
languages.forEach(lang => { | ||
const oldTranslation = oldTranslationsMap[lang] || {}; | ||
const unusedKeys = getUnusedKeysFromOldTranslations( | ||
oldTranslation, | ||
translations | ||
); | ||
const newTranslation = Object.assign({}, translations, oldTranslation, { | ||
_unused: unusedKeys.length > 0 ? unusedKeys : undefined, | ||
}); | ||
newTranslationMap[lang] = newTranslation; | ||
}); | ||
writeTranslationMap(oldTranslationsMap, newTranslationMap, directory); | ||
} | ||
|
||
function run({ files, languages, directory }) { | ||
if (languages.length > 0) { | ||
startBuild({ | ||
languages, | ||
files, | ||
directory, | ||
}); | ||
} else { | ||
console.warn("Language not yet set"); | ||
} | ||
} | ||
|
||
run({ | ||
files: [ | ||
path.resolve(__dirname, "../src/lib/service-error-translator/service-error-translator.tsx") | ||
], | ||
languages: ["en-US"], | ||
directory: path.resolve(__dirname, "../src/lib/service-error-translator/translations") | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ | |
*/ | ||
|
||
export * from "./lib/input-validation"; | ||
export * from "./lib/service-error-translator"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Service Error Translator | ||
|
||
## Overview | ||
|
||
Service Error Translator is a library that can be used to turn Justice standard error codes into | ||
user-friendly error message. It contains a React component that accepts a standard error response | ||
from Justice backend services. | ||
|
||
## Usage Example | ||
|
||
```ts | ||
import { | ||
ServiceErrorTranslator, | ||
} from "justice-js-common-utils" | ||
|
||
class Component extends React.Component { | ||
// Declare validation as class property | ||
constructor(){ | ||
// set username input value as state | ||
this.state = ({ | ||
errorMessage: null, | ||
}) | ||
} | ||
|
||
async fetchUsers = () => { | ||
try { | ||
const response = await api.fetchUsers(); | ||
// ... | ||
} catch (error) { | ||
// assuming error is an object with properties ErrorCode and ErrorMessage | ||
this.setState({ | ||
errorMessage: <ServiceErrorTranslator error={error} /> | ||
}); | ||
} | ||
} | ||
|
||
render(){ | ||
// ... | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"languageCodes": ["en-US"], | ||
"defaultLanguage": "en-US", | ||
"fallbackLanguage": "en-US" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright (c) 2018-2019 AccelByte Inc. All Rights Reserved | ||
* This is licensed software from AccelByte Inc, for limitations | ||
* and restrictions contact your company contract manager. | ||
*/ | ||
|
||
import flatten from "flat"; | ||
import i18next, { Resource } from "i18next"; | ||
import { initReactI18next } from "react-i18next"; | ||
import config from "./config.json"; | ||
|
||
const loadedLanguages: { [key: string]: string } = { | ||
"en-US": "enUS", | ||
}; | ||
const availableLanguageCodes = config.languageCodes; | ||
const translationResource = availableLanguageCodes.reduce((resources: Resource, languageCode: string) => { | ||
// eslint-disable-next-line no-param-reassign | ||
resources[languageCode] = { | ||
// Loading unflattened resource | ||
translation: flatten.unflatten(loadedLanguages[languageCode]), | ||
}; | ||
return resources; | ||
}, {}); | ||
|
||
// @ts-ignore | ||
export const i18nInstance = i18next.use(initReactI18next).createInstance( | ||
{ | ||
lng: config.defaultLanguage, | ||
fallbackLng: config.fallbackLanguage, | ||
preload: availableLanguageCodes, | ||
resources: translationResource, | ||
initImmediate: false, | ||
debug: process.env.NODE_ENV === "development", | ||
}, | ||
// tslint:disable-next-line no-empty | ||
() => {} | ||
); // Do not remove the callback. It will break the i18n | ||
|
||
// tslint:disable-next-line no-any | ||
export function t(key: string, options?: any) { | ||
return i18nInstance.t(key, options); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/* | ||
* Copyright (c) 2019. AccelByte Inc. All Rights Reserved | ||
* This is licensed software from AccelByte Inc, for limitations | ||
* and restrictions contact your company contract manager. | ||
*/ | ||
|
||
export * from "./service-error-translator"; |
44 changes: 44 additions & 0 deletions
44
src/lib/service-error-translator/service-error-translator.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* Copyright (c) 2019. AccelByte Inc. All Rights Reserved | ||
* This is licensed software from AccelByte Inc, for limitations | ||
* and restrictions contact your company contract manager. | ||
*/ | ||
|
||
import * as React from "react"; | ||
import { Trans } from "react-i18next"; | ||
|
||
interface ServiceError { | ||
ErrorCode: number, | ||
ErrorMessage: string, | ||
} | ||
|
||
interface ServiceErrorProps { | ||
error: ServiceError; | ||
} | ||
|
||
// tslint:disable-next-line no-any | ||
const isValidServiceError = (error: any): error is ServiceError => { | ||
return typeof error === "object" && typeof error.ErrorCode === "number" && typeof error.ErrorMessage === "string"; | ||
} | ||
|
||
export const ServiceErrorTranslator = (props: ServiceErrorProps): React.ReactNode => { | ||
if (isValidServiceError(props.error) && props.error.ErrorCode in serviceErrorTranslationMap) { | ||
return serviceErrorTranslationMap[props.error.ErrorCode]; | ||
} | ||
return ( | ||
<Trans i18nKey="serviceError.unknown"> | ||
Failed to complete the request | ||
</Trans> | ||
); | ||
}; | ||
|
||
const serviceErrorTranslationMap: { [key: string]: React.ReactNode } = Object.freeze({ | ||
1014002: | ||
<Trans i18nKey="serviceError.1014002"> | ||
User already exist | ||
</Trans>, | ||
1014047: | ||
<Trans i18nKey="serviceError.1014047"> | ||
Failed to create User. Date of birth does not meet the age requirement. | ||
</Trans>, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"serviceError.1014002": "User already exist", | ||
"serviceError.1014047": "Failed to create User. Date of birth does not meet the age requirement.", | ||
"serviceError.unknown": "Failed to complete the request" | ||
} |
Oops, something went wrong.