diff --git a/.env.development b/.env.development index 9307495..adafb44 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,3 @@ NODE_ENV=development -TRANSLATE_API_PROXY= +TEST_TRANSLATE_API_PROXY= diff --git a/README.md b/README.md index 456f132..fa0abe9 100644 --- a/README.md +++ b/README.md @@ -13,17 +13,20 @@ A modern, *free*, *lightweight* npm package for translating react apps (pages an - Allows for custom language list files. (Coming in v2.0.0) ## Install -```npm install react-g-translator``` +```npm install @miracleufo/react-g-translator``` or with yarn -```yarn add react-g-translator``` +```yarn add @miracleufo/react-g-translator``` ## Usage +> DISCLAIMER! +To be 100% legal please use the official [Google Translate API](https://cloud.google.com/translate). This project is mainly for pet projects and prototyping 😉. Also, only use the most recent version of this package. + ### To translate whole component: ```jsx -import Translator from 'react-g-translator'; +import Translator from '@miracleufo/react-g-translator'; return ( @@ -34,7 +37,7 @@ return ( ### To translate specific text inline: ```jsx -import { Translate } from 'react-g-translator'; +import { Translate } from '@miracleufo/react-g-translator'; return (
@@ -48,7 +51,7 @@ return ( ### To get translation of text directly: ```jsx -import { getTranslation } from 'react-g-translator'; +import { getTranslation } from '@miracleufo/react-g-translator'; const helloInIgbo = await getTranslation('Hello', 'en', 'ig'); diff --git a/package-lock.json b/package-lock.json index 0bb7aa6..f10d172 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,12 +13,11 @@ "@testing-library/jest-dom": "^6.1.3", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.5.0", - "@vitalets/google-translate-api": "^9.0.0", "crypto-js": "^4.1.1", "env-cmd": "^10.1.0", "https-proxy-agent": "^7.0.2", "lodash": "^4.17.21", - "react": "^18.2.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^18.2.0", "react-query": "^3.39.2", "react-scripts": "5.0.1", @@ -5997,19 +5996,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@vitalets/google-translate-api": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@vitalets/google-translate-api/-/google-translate-api-9.2.0.tgz", - "integrity": "sha512-w98IPWGuexlGmh8Y19AxF6cgWT0U5JLevVNDKEuFpTWtBC9z3YtDWKTDxF3nPP1k9bWicuB1V7I7YfHoZiDScw==", - "dependencies": { - "@types/http-errors": "^1.8.2", - "http-errors": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -18595,44 +18581,6 @@ "lodash": "^4.17.21" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", diff --git a/package.json b/package.json index d9bdf84..37240e6 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,6 @@ "@testing-library/jest-dom": "^6.1.3", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.5.0", - "@vitalets/google-translate-api": "^9.0.0", "crypto-js": "^4.1.1", "env-cmd": "^10.1.0", "https-proxy-agent": "^7.0.2", diff --git a/rollup.config.mjs b/rollup.config.mjs index 63e95dd..ef6e103 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -41,6 +41,7 @@ const mainConfig = { ], external: ['react', 'react-dom'], plugins: [ + // @ts-ignore peerDepsExternal(), nodeResolve(), commonjs(), diff --git a/src/constants/index.ts b/src/constants/index.ts index c8579b3..e4554e7 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,13 +1,15 @@ import language from '../types/language'; -const { NODE_ENV, TRANSLATE_API_PROXY } = process.env; +const { NODE_ENV, TEST_TRANSLATE_API_PROXY } = process.env; // NODE ENVIRONMENT const NODE_DEVELOPMENT = 'development'; const NODE_TEST = 'test'; const IS_DEVELOPMENT_OR_TEST = NODE_ENV && [NODE_DEVELOPMENT, NODE_TEST].includes(NODE_ENV); -const PROXY = TRANSLATE_API_PROXY; +const PROXY_URL = 'https://react-g-translator-proxy.vercel.app/api'; +const PROXY_URL_ALT = 'https://react-g-translator-proxy-express.onrender.com/translate'; +const PROXY_URL_TEST = TEST_TRANSLATE_API_PROXY; const DEFAULT_PROPS = { from: 'en', @@ -41,7 +43,9 @@ export { NODE_DEVELOPMENT, NODE_TEST, IS_DEVELOPMENT_OR_TEST, - PROXY, + PROXY_URL, + PROXY_URL_ALT, + PROXY_URL_TEST, DEFAULT_PROPS, DEFAULT_QUERY_OPTIONS, DEFAULT_LANGUAGE_FROM, diff --git a/src/utils/getTranslation.ts b/src/utils/getTranslation.ts index 1ca4fbd..0b8c9f2 100644 --- a/src/utils/getTranslation.ts +++ b/src/utils/getTranslation.ts @@ -1,14 +1,8 @@ -import { translate } from '@vitalets/google-translate-api'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import throttle from 'lodash/throttle'; - import chunkRequest from './chunkRequest'; -import { - PROXY, - CHARACTER_LIMIT, - DEBOUNCE_RATE, - IS_DEVELOPMENT_OR_TEST, -} from '../constants'; +import translate from './translate'; + +import { CHARACTER_LIMIT, DEBOUNCE_RATE } from '../constants'; import language from '../types/language'; const getTranslation = async ( @@ -16,14 +10,10 @@ const getTranslation = async ( from?: language, to?: language, ) : Promise => { - // opts for development / testing - // in case `TooManyRequestsError` or Error Code `429` - const fetchOptions = IS_DEVELOPMENT_OR_TEST && PROXY && { agent: new HttpsProxyAgent(PROXY) }; - - // translating happens here. ✨ bing! ✨ const translateRequest = async (chunk: string | string[]) => { - const translation = await translate(chunk as string, { from, to, fetchOptions }); - return translation.text; + // translating happens here. ✨ bing! ✨ + const translation = await translate(chunk as string, from, to); + return JSON.parse(translation)?.text ?? ''; }; return throttle( diff --git a/src/utils/translate.ts b/src/utils/translate.ts new file mode 100644 index 0000000..22a64a1 --- /dev/null +++ b/src/utils/translate.ts @@ -0,0 +1,62 @@ +import { HttpsProxyAgent } from 'https-proxy-agent'; +import getErrorInTranslationMessage from './getErrorInTranslationMessage'; +import { + IS_DEVELOPMENT_OR_TEST, + PROXY_URL, + PROXY_URL_ALT, + PROXY_URL_TEST, +} from '../constants'; +import language from '../types/language'; + +const translate = async (text: string, from?: language, to?: language) => { + // option for development / testing + // in case of `TooManyRequestsError` (Error Code `429`) + const fetchOptions = ( + IS_DEVELOPMENT_OR_TEST && PROXY_URL_TEST && { agent: new HttpsProxyAgent(PROXY_URL_TEST) } + ); + + let response; + try { + response = await fetch(PROXY_URL, { + credentials: 'omit', + mode: 'cors', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( + { + text, + from, + to, + fetchOptions, + }, + ), + }); + } catch (error) { + // If the first request failed, try with the second proxy + response = await fetch(PROXY_URL_ALT, { + credentials: 'omit', + mode: 'cors', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( + { + text, + from, + to, + fetchOptions, + }, + ), + }); + } + + if (response.status === 200) return response.json(); + + const error = new Error(`${response.status} - ${response.statusText}`); + throw getErrorInTranslationMessage(error); +}; + +export default translate;