Skip to content

Commit

Permalink
feat: proxy server and authentication, README edits
Browse files Browse the repository at this point in the history
  • Loading branch information
MiracleUFO committed Oct 30, 2023
1 parent 4412045 commit 0ad5365
Show file tree
Hide file tree
Showing 12 changed files with 75 additions and 91 deletions.
3 changes: 2 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
NODE_ENV=development

TEST_TRANSLATE_API_PROXY=
REACT_APP_TRANSLATE_SERVER_URL=
REACT_APP_TRANSLATE_SERVER_TOKEN=
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ A modern, *free*, *lightweight* npm package for translating react apps (pages an
- Fast and reliable – it uses the same servers that [translate.google.com](https://translate.google.com) uses
- Allows to set defualt language and destination language in code.
- Translates entire pages and just text.
- Translates `input` and `textarea` element placeholder as well as `img` (image) alt text.
- Allows for custom language list files. (Coming in v2.0.0)

## Install
Expand All @@ -22,18 +23,21 @@ or with yarn
## 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 be 100% legal please use the official [Google Translate API](https://cloud.google.com/translate). This project is mainly for pet projects and prototyping 😉. Always only use the most **recent version** of this package. This server is also very limited, for use in **production** see [PRODUCTION USAGE.](#production-usage)

### To translate whole component:
```jsx
import Translator from '@miracleufo/react-g-translator';

return (
<Translator from='en' to='es'>
<App />
<div>
...
</div>
</Translator>
);
```
**NB:** Each non-void React element is translated like a paragraph, for best translation please avoid <br /> and use <p> instead; and end sentences with fullstop. **`<Translator />` `from` and `to` props will always override [`<Translate />`](#to-translate-specific-text-inline) `from` and `to` props.**

### To translate specific text inline:
```jsx
Expand Down Expand Up @@ -66,12 +70,12 @@ return (
### Props
- `from`: Language the text(s) is provided in. Optional.
- Defaults to "en".
- *Type string*. If string provided is not found in [supported languages](https://cloud.google.com/translate/docs/languages), will default to "en".
- *Type string*. String provided must be found in [supported languages.](https://cloud.google.com/translate/docs/languages)
- Overriden by [`setLanguageFrom`](#hook-setlanguagefrom) hook. (Coming in V2)

- `to`: Language to translate to. Optional.
- Defaults to *user's current browser language setting*.
- *Type string*. If string provided is not found in [supported languages](https://cloud.google.com/translate/docs/languages) will default to *user's current browser language setting*.
- *Type string*. String provided must be found in [supported languages.](https://cloud.google.com/translate/docs/languages)
- Overriden by [`setLanguageTo`](#hook-setlanguageto) hook. (Coming in V2)

- `shouldFallback`: Determines error handling. Available in `<Translate />` and `<Translator />`, when error, displays original text when `shouldFallback` is true, or empty string otherwise. Optional.
Expand Down Expand Up @@ -118,25 +122,33 @@ See [Usage](#to-get-translation-of-text-directly)
- For every UNIQUE `text`, `to`, and `from` [(props)](#props) combination the translation is fetched & cached for 24 hours (as long as QueryClient is not destroyed,) this is to prevent unnecessary requests.
- QueryClient is destroyed in this case ONLY when the web page is reloaded.


## Special Cases

- [`from`](#props) and [`to`](#props) being the same will return original text (determined by google translation API.)
- [`from`](#props) and [`to`](#props) being empty strings will be extrapolated from 'en' and *user's current browser language setting* respectively.
- `text` is not in the [`from`](#props) language and google translate API cannot detect language automatically, it will return the original text.

## Production Usage:
The server for this package is very limited and may not meet your projects' needs, to aid package use in production:
- **FORK** the server at this [repo](https://github.com/MiracleUFO/react-g-translator-proxy-express).
- Host the server, then in the environment file(s) (`.env.*`) of the React project assign the hosted server's url/address to `REACT_APP_TRANSLATE_SERVER_TOKEN`.
- To enable authentication, you can protect your server by editing code in the server [repo](https://github.com/MiracleUFO/react-g-translator-proxy-express) see [this](https://christiangiacomi.com/posts/express-barer-strategy) for help, once authorisation code is running on server assign the server's authentication token to `REACT_APP_TRANSLATE_SERVER_TOKEN` in the React projects' environment file(s) (`.env.*`)
- **NB**: You will need a **MONGODB ATLAS CLUSTER** to run the server successfully. [Create one](https://www.mongodb.com/docs/guides/atlas/cluster), and assign the Atlas cluster's credentials to `MONGOOSE_ATLAS_CONNECTION_STRING` & `MONGOOSE_ATLAS_PASSWORD` in your **server's** environment file (keep this private.)
- Also, if **delay** between requests is too long, edit [this](https://github.com/MiracleUFO/react-g-translator-proxy-express/blob/main/src/index.ts#L42) and/or [this](https://github.com/MiracleUFO/react-g-translator-proxy-express/blob/main/src/utils/delayRequests.ts#L17-L19) in your server.

## Developer Testing
- [Install node-modules](#install)
- `npm run test` or `yarn run test`
- **Note**
- Some tests in `src/tests` may fail because google translate API might return synonyms when a string is translated multiple times.
- If `TooManyRequestsError` or Error Code `429` is encountered, use PROXY by updating `env` variable `TRANSLATE_API_PROXY` with a correct [proxy](https://free-proxy-list.net/) (with yes in Google column.) This error is due to Google Translate APIs rate limiting per IP address (this limit seems variable, see [discussion.](https://github.com/vitalets/google-translate-api/issues/107#issuecomment-1302220214)) Switching internet providers may also solve this (temporarily.)
- If `TooManyRequestsError` or Error Code `429` is encountered, this issue is due to Google Translate APIs rate limiting per IP address (this limit seems variable, see [discussion.](https://github.com/vitalets/google-translate-api/issues/107#issuecomment-1302220214)) Switching internet providers may solve this (temporarily.)
- [**Caching**](#caching) is **OFF** by default in testing, to turn **ON**, replace `QUERY_DEFAULT_OPTIONS` in `tests/constants-test.ts` with:
```js
const ONE_DAY_IN_MS = 24 * (60 * 60 * 1000);
const DEFAULT_QUERY_OPTIONS = {
defaultOptions: {
queries: {
retry: false,
staleTime: ONE_DAY_IN_MS,
cacheTime: ONE_DAY_IN_MS,
},
Expand Down
26 changes: 4 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@miracleufo/react-g-translator",
"version": "6.0.0",
"version": "1.0.0",
"description": "A modern, free, lightweight npm package for translating react apps (component wrapper.) No API keys or language list files are required.",
"author": "Miracle Ufodiama",
"license": "MIT",
Expand Down Expand Up @@ -59,7 +59,6 @@
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5",
"@types/crypto-js": "^4.1.2",
"@types/google-translate-api": "^2.3.2",
"@types/jest": "^29.5.4",
"@types/lodash": "^4.14.199",
"@types/react": "^18.0.25",
Expand All @@ -72,6 +71,7 @@
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.26.0",
"env-cmd": "^10.1.0",
"eslint": "^8.28.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
Expand Down Expand Up @@ -105,10 +105,7 @@
"@rollup/plugin-node-resolve": "^15.2.2",
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.0",
"crypto-js": "^4.1.1",
"env-cmd": "^10.1.0",
"https-proxy-agent": "^7.0.2",
"lodash": "^4.17.21",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^18.2.0",
Expand Down
26 changes: 8 additions & 18 deletions src/components/Translate.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';

import Translation from './helpers/Translation';
import { LanguageProvider } from '../context/languageContext';
import useTranslation from '../queries/useTranslation';

import determineRenderedText from '../utils/determineRenderedText';
import { DEFAULT_PROPS, DEFAULT_QUERY_OPTIONS } from '../constants';
import language from '../types/language';

Expand All @@ -20,21 +18,13 @@ const Translate = ({
from?: language,
to?: language,
shouldFallback?: boolean,
}) => {
const {
data,
isError,
isLoading,
} = useTranslation(children, from, to);

return (
<QueryClientProvider client={queryClient}>
<LanguageProvider>
{determineRenderedText(children, data, shouldFallback, isError, isLoading)}
</LanguageProvider>
</QueryClientProvider>
);
};
}) => (
<QueryClientProvider client={queryClient}>
<LanguageProvider>
<Translation text={children} from={from} to={to} shouldFallback={shouldFallback} />
</LanguageProvider>
</QueryClientProvider>
);

Translate.defaultProps = DEFAULT_PROPS;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import React, {
} from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';

import isVoidElement from '../../utils/isVoidElement';
import { LanguageProvider } from '../../context/languageContext';
import isVoidElement from '../utils/isVoidElement';
import { LanguageProvider } from '../context/languageContext';

import Translation from './helpers/Translation';
import TranslationInputImg from './helpers/TranslationInputImg';
import TranslationInputImg from './helpers/TranslationInputImage';

import { DEFAULT_PROPS, DEFAULT_QUERY_OPTIONS } from '../../constants';
import language from '../../types/language';
import { DEFAULT_PROPS, DEFAULT_QUERY_OPTIONS } from '../constants';
import language from '../types/language';

const queryClient = new QueryClient(DEFAULT_QUERY_OPTIONS);

Expand All @@ -40,7 +40,13 @@ const recursivelyTranslate = (
}

if (Children.count(node) === 0 || isVoidElement(node)) {
return node;
return (
<>
&nbsp;
{node}
&nbsp;
</>
);
}

if (isValidElement(node)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React from 'react';
import determineRenderedText from '../../../utils/determineRenderedText';
import useTranslation from '../../../queries/useTranslation';
import determineRenderedText from '../../utils/determineRenderedText';
import useTranslation from '../../queries/useTranslation';

import { DEFAULT_PROPS } from '../../../constants';
import language from '../../../types/language';
import { DEFAULT_PROPS } from '../../constants';
import language from '../../types/language';

const Translation = ({
text,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import React, {
isValidElement,
} from 'react';

import determineRenderedText from '../../../utils/determineRenderedText';
import useTranslation from '../../../queries/useTranslation';
import determineRenderedText from '../../utils/determineRenderedText';
import useTranslation from '../../queries/useTranslation';

import { DEFAULT_PROPS } from '../../../constants';
import language from '../../../types/language';
import { DEFAULT_PROPS } from '../../constants';
import language from '../../types/language';

const TranslationInputImg = ({
const TranslationInputImage = ({
node,
from,
to,
Expand Down Expand Up @@ -56,6 +56,6 @@ const TranslationInputImg = ({
return <>{node}</>;
};

TranslationInputImg.defaultProps = DEFAULT_PROPS;
TranslationInputImage.defaultProps = DEFAULT_PROPS;

export default TranslationInputImg;
export default TranslationInputImage;
20 changes: 11 additions & 9 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { QueryClientConfig } from 'react-query';
import language from '../types/language';

const { NODE_ENV, TEST_TRANSLATE_API_PROXY } = process.env;
const { NODE_ENV, REACT_APP_TRANSLATE_SERVER_URL, REACT_APP_TRANSLATE_SERVER__TOKEN } = process.env;

// NODE ENVIRONMENT
const NODE_DEVELOPMENT = 'development';
Expand All @@ -10,13 +10,8 @@ const IS_DEVELOPMENT_OR_TEST = NODE_ENV && [NODE_DEVELOPMENT, NODE_TEST].include

const PROXY_URL = 'https://react-g-translator-proxy-express.onrender.com/translate';
const PROXY_URL_ALT = 'https://react-g-translator-proxy.vercel.app/api';
const PROXY_URL_TEST = TEST_TRANSLATE_API_PROXY;

const DEFAULT_PROPS = {
from: 'en',
to: 'auto',
shouldFallback: true,
};
const SERVER_URL = REACT_APP_TRANSLATE_SERVER_URL || '';
const SERVER_TOKEN = REACT_APP_TRANSLATE_SERVER__TOKEN || '';

const ONE_DAY_IN_MS = 24 * (60 * 60 * 1000);

Expand All @@ -35,6 +30,12 @@ const DEFAULT_BROWSER_LANGUAGE : language = window?.navigator?.language.startsWi
? window?.navigator?.language as language
: window?.navigator?.language.split('-')[0] as language;

const DEFAULT_PROPS = {
from: 'en',
to: DEFAULT_BROWSER_LANGUAGE || 'auto',
shouldFallback: true,
};

// API REQUESTS
const CHARACTER_LIMIT = 5000;
const DEBOUNCE_RATE = 2000;
Expand All @@ -47,7 +48,8 @@ export {
IS_DEVELOPMENT_OR_TEST,
PROXY_URL,
PROXY_URL_ALT,
PROXY_URL_TEST,
SERVER_URL,
SERVER_TOKEN,
DEFAULT_PROPS,
DEFAULT_QUERY_OPTIONS,
DEFAULT_LANGUAGE_FROM,
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Translate from './components/Translate';
import Translator from './components/Translator/Translator';
import Translator from './components/Translator';
import getTranslation from './scripts/getTranslation';

export {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/isVoidElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const isVoidElement = (
) => {
// List of known void elements
const voidElements = [
'area', 'base', 'br', 'col', 'embed', 'hr', 'link', 'meta', 'param', 'source', 'track', 'wbr',
'area', 'base', 'br', 'col', 'embed', 'hr', 'link', 'meta', 'param', 'source', 'track', 'wbr', 'abbr', 'code',
];

if (isValidElement(node)) return (voidElements.includes(node.type as string));
Expand Down
Loading

0 comments on commit 0ad5365

Please sign in to comment.