Skip to content

Commit 60e5cbe

Browse files
committed
v1.0.0 useTranslateHtmlElement and useTranslateFormErrors hooks added
1 parent d2dbb54 commit 60e5cbe

13 files changed

Lines changed: 2829 additions & 4 deletions

.npmignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
src
2+
demo
3+
.babelrc
4+
webpack.config.js

README.md

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,172 @@
11
# react-i18next-helpers
2-
A set of helper hooks and components to use with i18next, react-i18next and Formik
2+
A set of helper hooks and components to use with i18next, react-i18next and Formik; i18next makes translating a breeze in apps, and using the react-i18next opens up the API for most React Applications. However, there are blind spots like with any other libraries. These helpers are here to help you cover these blind spots.
3+
4+
## Using Formik and translating errors
5+
If you use Formik and you have errors rendered on the screen, you will notice that if you change the current language, your errors remain in the language they were before the translation. This library gives you a hook you can use to make sure that when the i18n language changes, your form errors are also translated.
6+
7+
### Render Example
8+
9+
This example is based on using the `render` method for rendering a Formik form
10+
11+
```
12+
13+
...
14+
<Formik component={ContactForm} />;
15+
...
16+
17+
import useTranslateFormErrors from '../../hooks/use-translate-form-errors';
18+
19+
const ContactForm = ({
20+
handleSubmit,
21+
handleChange,
22+
handleBlur,
23+
values,
24+
errors,
25+
touched,
26+
setFieldTouched
27+
}) => {
28+
29+
useTranslateFormErrors(errors, touched, setFieldTouched);
30+
31+
return (
32+
<form onSubmit={handleSubmit}>
33+
<input
34+
type="text"
35+
onChange={handleChange}
36+
onBlur={handleBlur}
37+
value={values.name}
38+
name="name"
39+
/>
40+
{errors.name && <div>{errors.name}</div>}
41+
<button type="submit">Submit</button>
42+
</form>
43+
);
44+
45+
}
46+
47+
```
48+
49+
Just passing the `errors`, `touched`, and `setFieldTouched` FormikProps to the hook in your render method ensures that your errors will get translated if the language changes.
50+
51+
### HOC Example
52+
53+
```
54+
<Formik
55+
render={({ handleSubmit, handleChange, handleBlur, setFieldTouched, values, errors, touched }) => (
56+
<WithTranslateFormErrors errors={errors} touched={touched} setFieldTouched={setFieldTouched}>
57+
<form onSubmit={handleSubmit}>
58+
<input
59+
type="text"
60+
onChange={handleChange}
61+
onBlur={handleBlur}
62+
value={values.name}
63+
name="name"
64+
/>
65+
{errors.name &&
66+
<div>
67+
{errors.name}
68+
</div>}
69+
<button type="submit">Submit</button>
70+
</form>
71+
</WithTranslateFormErrors>
72+
)}
73+
/>
74+
75+
import PropTypes from 'prop-types';
76+
import React, { useEffect } from 'react';
77+
import { useTranslation } from 'react-i18next';
78+
79+
const useTranslateFormErrors = (errors, touched, setFieldTouched) => {
80+
const { i18n } = useTranslation();
81+
useEffect(() => {
82+
i18n.on('languageChanged', lng => {
83+
Object.keys(errors).forEach(fieldName => {
84+
if (Object.keys(touched).includes(fieldName)) {
85+
setFieldTouched(fieldName);
86+
}
87+
});
88+
});
89+
return () => {
90+
i18n.off('languageChanged', lng => {});
91+
};
92+
}, [errors]);
93+
};
94+
95+
96+
const WithTranslateFormErrors = ({ errors, touched, setFieldTouched, children }) => {
97+
useTranslateFormErrors(errors, touched, setFieldTouched);
98+
return <>{children}</>;
99+
};
100+
101+
WithTranslateFormErrors.propTypes = {
102+
form: PropTypes.object
103+
};
104+
105+
export default WithTranslateFormErrors;
106+
107+
108+
```
109+
110+
111+
112+
Just passing the `errors`, `touched`, and `setFieldTouched` FormikProps to the HOC in your render method ensures that your errors will get translated if the language changes.
113+
114+
## Translating raw or dynamic HTML
115+
116+
If you're ever in need of translating HTML you're adding dynamically to your app, and to keep content in that HTML translated, you can use the `useTranslateHtmlElement` hook.
117+
118+
```
119+
120+
import dompurify from 'dompurify';
121+
import useTranslateHtmlElement from './use-translate-html-element';
122+
// Our safe HTML rendering component
123+
// From https://dev.to/jam3/how-to-prevent-xss-attacks-when-using-dangerouslysetinnerhtml-in-react-1464
124+
125+
function SafeHTMLComponent() {
126+
// title is dynamic HTML
127+
const title = response.from.backend.title;
128+
// sanitizer will sanitize the HTML
129+
const sanitizer = dompurify.sanitize;
130+
const safeTitle = sanitizer(title);
131+
const [ref] = useTranslateHtmlElement(safeTitle);
132+
return
133+
134+
import React, { useEffect, useState } from 'react';
135+
import { useTranslation } from 'react-i18next';
136+
137+
const translateI18nTokensInHtmlElement = (i18n, htmlElement) => {
138+
const i18nTokenNodes = htmlElement.querySelectorAll('[data-i18n]');
139+
for (let i = 0; i < i18nTokenNodes.length; i++) {
140+
i18nTokenNodes[i].innerHTML = i18n.t(i18nTokenNodes[i].getAttribute('data-i18n'));
141+
}
142+
};
143+
144+
const useTranslateHtmlElement = html => {
145+
const { i18n } = useTranslation();
146+
const [node, setRef] = useState(null);
147+
const translateIfNodeRendered = node => {
148+
if (node) {
149+
translateI18nTokensInHtmlElement(i18n, node);
150+
}
151+
};
152+
useEffect(() => {
153+
translateIfNodeRendered(node);
154+
155+
i18n.on('languageChanged', lng => {
156+
translateIfNodeRendered(node);
157+
});
158+
return () => {
159+
i18n.off('languageChanged', lng => {});
160+
};
161+
}, [html]);
162+
return [setRef];
163+
};
164+
165+
export default useTranslateHtmlElement;
166+
167+
```
168+
169+
170+
171+
172+
The example is self explanatory.

babel.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = function (api) {
2+
api.cache(true);
3+
4+
const presets = [ "@babel/preset-env", "@babel/preset-react" ];
5+
const plugins = [ ];
6+
7+
return {
8+
presets,
9+
plugins
10+
};
11+
};

dist/hoc/WithTranslateFormErrors.js

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/hooks/use-translate-form-errors.js

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/hooks/use-translate-html-element.js

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
"name": "react-i18next-helpers",
33
"version": "1.0.0",
44
"description": "A set of helper hooks and components to use with i18next, react-i18next and Formik",
5-
"main": "index.js",
5+
"main": "dist/index.js",
66
"scripts": {
7-
"test": "npm run test"
7+
"test": "npm run test",
8+
"prepublishOnly": "babel ./src --out-dir ./dist -s inline"
89
},
10+
"files": [
11+
"dist/*"
12+
],
913
"repository": {
1014
"type": "git",
1115
"url": "git+https://github.com/devakone/react-i18next-helpers.git"
@@ -22,5 +26,15 @@
2226
"bugs": {
2327
"url": "https://github.com/devakone/react-i18next-helpers/issues"
2428
},
25-
"homepage": "https://github.com/devakone/react-i18next-helpers#readme"
29+
"homepage": "https://github.com/devakone/react-i18next-helpers#readme",
30+
"devDependencies": {
31+
"@babel/cli": "^7.4.4",
32+
"@babel/core": "^7.4.5",
33+
"@babel/preset-env": "^7.4.5",
34+
"@babel/preset-react": "^7.0.0",
35+
"formik": "^1.5.7",
36+
"i18next": "^17.0.3",
37+
"react": "^16.8.6",
38+
"react-i18next": "^10.11.0"
39+
}
2640
}

0 commit comments

Comments
 (0)