Skip to content

Commit

Permalink
Merge pull request #22 from MiracleUFO/feat-vite-next/support-vite-next
Browse files Browse the repository at this point in the history
feat: support for Vite and NextJS, fix for number node causing max stack error
  • Loading branch information
MiracleUFO authored Nov 2, 2023
2 parents 9defea2 + 5012bc9 commit 5074695
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 31 deletions.
48 changes: 38 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ A modern, *free*, *lightweight* npm package for translating react apps (pages an
## Features
- Covers multiple use cases (see [Usage](#usage))
- Enables Web Internationalisation (i18n) and Accessibility (a11y)
- Allows to create custom language change component
- Auto language detection
- Spelling and Language correction
- Supports Next.js ▲ and [Vite ⚡️](#vite-⚡️-usage)
- 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 to set default 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 @@ -27,7 +27,7 @@ To be 100% legal please use the official [Google Translate API](https://cloud.go

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

const Component = () => {
...
Expand All @@ -40,7 +40,13 @@ const Component = () => {
);
}
```
Each non-void React element is translated like a paragraph, for best translation please avoid `<br />` and use `<p>` instead, and always end sentences with fullstop.

#### For best use of [`<Translator />`](#to-translate-whole-component):
- The `<Translator />` wrapper will only translate React non-void elements that are not components themselves (i.e A parent component will not translate children components. It will only translate other React non-void elements inside it, this is for improved scoping of the wrapper's `from` and `to` props.)
- Each non-void React element is translated like a paragraph, for best translation please avoid `<br />` and use `<p>` instead, and always end sentences with fullstop.
- If **spacing** is needed before or after an **inline element**, it should be written as `{' '}` (JSX) this is because translation removes all starting or ending spacing it deems unnecessary.
- To opt out of translating a variable wrap it in either the `<code>` or `<var>` element. Variables of type `number` are not translated by default. Keyboard inputs you don't want translated should be wrapped in `<kbd>`.


### To translate specific text inline:
```jsx
Expand Down Expand Up @@ -135,21 +141,43 @@ See [Usage](#to-get-translation-of-text-directly)
- [`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.

## Vite ⚡️ Usage:
Vite does not have the `process` global, to polyfill this in Vite projects, in the `vite.config.js` or `vite.config.ts` file install and include `vite-plugin-env-compatible` package as shown to load env variables correctly, or error `Uncaught ReferenceError: process is not defined` will be thrown.
```jsx
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react';
import envCompatible from 'vite-plugin-env-compatible';

// https://vitejs.dev/config/
export default defineConfig({
...
plugins: [
...
react(),
// @ts-ignore
envCompatible.default({ prefix: 'VITE', mountedPath: 'process.env'}),
...
],
...
});
```

## 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).
- You will need a **MONGODB ATLAS CLUSTER** to run the server successfully for rate limiting. [Create one for free](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.)
- Host the forked server. 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.*`)
- Host the forked server. In the environment file(s) (`.env.*`) of the React project assign the hosted server's URL/address to `REACT_APP_TRANSLATE_SERVER_URL` OR `VITE_APP_TRANSLATE_SERVER_URL` (if using Vite ⚡️) OR `NEXT_PUBLIC_APP_TRANSLATE_SERVER_URL` (if using Next.js ▲.)
- 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` or `VITE_APP_TRANSLATE_SERVER_TOKEN` (if using Vite ⚡️) OR `NEXT_PUBLIC_APP_TRANSLATE_SERVER_TOKEN` (if using Next.js ▲) in the React projects' environment file(s) (`.env.*`)
- Also, if **delay** between requests is too long, remove [sleep](https://github.com/MiracleUFO/react-g-translator-proxy-express/blob/main/src/index.ts#L42) and/or edit [delay](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)
- **FORK** package [repo](https://github.com/MiracleUFO/react-g-translator)
- Install node-modules: `npm install` or `yarn 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, 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:
- [**Caching**](#caching) is **OFF** by default in testing, to turn **ON**, replace `DEFAULT_QUERY_OPTIONS` in `tests/constants-test.ts` with:
```js
const ONE_DAY_IN_MS = 24 * (60 * 60 * 1000);
const DEFAULT_QUERY_OPTIONS = {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@miracleufo/react-g-translator",
"version": "1.1.2",
"version": "1.1.3",
"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 @@ -42,6 +42,11 @@
"cjs",
"umd",
"react",
"reactjs",
"vite",
"nextjs",
"next",
"next13",
"nodejs",
"commonjs",
"ecmascript",
Expand Down
31 changes: 22 additions & 9 deletions src/components/Translator.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {
JSX,
useMemo,
ReactNode,
ReactElement,
Children,
Expand All @@ -25,7 +26,7 @@ const recursivelyTranslate = (
to?: language,
shouldFallback?: boolean,
): ReactNode => {
if (typeof node === 'string' && !node) return node;
if ((typeof node === 'string' && !node)) return node;

if (typeof node === 'string') {
return (
Expand All @@ -38,7 +39,7 @@ const recursivelyTranslate = (
);
}

if (Children.count(node) === 0 || isVoidElement(node)) {
if (Children.count(node) === 0 || isVoidElement(node) || typeof node === 'number') {
return (
<>
&nbsp;
Expand Down Expand Up @@ -85,13 +86,25 @@ const Translator = ({
from?: language;
to?: language;
shouldFallback?: boolean;
}) => (
<QueryClientProvider client={queryClient}>
<LanguageProvider>
{recursivelyTranslate(<>{children}</>, from, to, shouldFallback)}
</LanguageProvider>
</QueryClientProvider>
);
}) => {
// memoised to eliminate unnecessary re-renders
const translatedChildren = useMemo(() => (
recursivelyTranslate(
<>{children}</>,
from,
to,
shouldFallback,
)
), [children, from, to, shouldFallback]);

return (
<QueryClientProvider client={queryClient}>
<LanguageProvider>
{translatedChildren}
</LanguageProvider>
</QueryClientProvider>
);
};

Translator.defaultProps = DEFAULT_PROPS;

Expand Down
27 changes: 20 additions & 7 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import { QueryClientConfig } from 'react-query';

import language from '../types/language';

const { NODE_ENV, REACT_APP_TRANSLATE_SERVER_URL, REACT_APP_TRANSLATE_SERVER__TOKEN } = process.env;
/* eslint-disable prefer-destructuring */
// prefer not destructuring due to Nextjs webpack issues
// see: https://github.com/vercel/next.js/issues/19420
const REACT_APP_TRANSLATE_SERVER_URL = process.env.REACT_APP_TRANSLATE_SERVER_URL;
const REACT_APP_TRANSLATE_SERVER_TOKEN = process.env.REACT_APP_TRANSLATE_SERVER_TOKEN;
const VITE_APP_TRANSLATE_SERVER_URL = process.env.VITE_APP_TRANSLATE_SERVER_URL;
const VITE_APP_TRANSLATE_SERVER_TOKEN = process.env.VITE_APP_TRANSLATE_SERVER_TOKEN;
const NEXT_PUBLIC_APP_TRANSLATE_SERVER_URL = process.env.NEXT_PUBLIC_APP_TRANSLATE_SERVER_URL;
const NEXT_PUBLIC_APP_TRANSLATE_SERVER_TOKEN = process.env.NEXT_PUBLIC_APP_TRANSLATE_SERVER_TOKEN;

// NODE ENVIRONMENT
const NODE_DEVELOPMENT = 'development';
const NODE_TEST = 'test';
const NODE_DEVELOPMENT = 'development';
const NODE_ENV = process.env.NODE_ENV;
const IS_DEVELOPMENT_OR_TEST = NODE_ENV && [NODE_DEVELOPMENT, NODE_TEST].includes(NODE_ENV);

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 SERVER_URL = REACT_APP_TRANSLATE_SERVER_URL || '';
const SERVER_TOKEN = REACT_APP_TRANSLATE_SERVER__TOKEN || '';
const SERVER_URL = REACT_APP_TRANSLATE_SERVER_URL || VITE_APP_TRANSLATE_SERVER_URL || NEXT_PUBLIC_APP_TRANSLATE_SERVER_URL || '';
const SERVER_TOKEN = REACT_APP_TRANSLATE_SERVER_TOKEN || VITE_APP_TRANSLATE_SERVER_TOKEN || NEXT_PUBLIC_APP_TRANSLATE_SERVER_TOKEN || '';

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

Expand All @@ -26,9 +36,12 @@ const DEFAULT_QUERY_OPTIONS: QueryClientConfig = {
};

const DEFAULT_LANGUAGE_FROM: language = 'en';
const DEFAULT_BROWSER_LANGUAGE : language = window?.navigator?.language.startsWith('zh')
? window?.navigator?.language as language
: window?.navigator?.language.split('-')[0] as language;
// eslint-disable-next-line no-nested-ternary
const DEFAULT_BROWSER_LANGUAGE : language = (typeof window !== 'undefined') ? (
window?.navigator?.language.startsWith('zh')
? window?.navigator?.language as language
: window?.navigator?.language.split('-')[0] as language
) : 'en';

const DEFAULT_PROPS = {
from: 'en',
Expand Down
4 changes: 2 additions & 2 deletions src/utils/isVoidElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { ReactNode, isValidElement } from 'react';
const isVoidElement = (
node: ReactNode,
) => {
// List of known void elements
// List of known void elements and elements to skip translation for
const voidElements = [
'area', 'base', 'br', 'col', 'embed', 'hr', 'link', 'meta', 'param', 'source', 'track', 'wbr', 'abbr', 'code',
'area', 'base', 'br', 'col', 'embed', 'hr', 'link', 'meta', 'param', 'source', 'track', 'wbr', 'abbr', 'code', 'var', 'sub', 'sup', 'svg', 'progress', 'kbd', 'ruby',
];

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

0 comments on commit 5074695

Please sign in to comment.