Skip to content

Commit 19341ff

Browse files
authored
Merge pull request #13 from devakone/codex/update-modern-react-i18next-compat
fix: modernize react i18next compatibility
2 parents b283cbe + dc1737a commit 19341ff

13 files changed

Lines changed: 2241 additions & 2209 deletions

README.md

Lines changed: 82 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,172 +1,111 @@
11
# react-i18next-helpers
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.
32

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.
3+
A small set of compatibility helpers for apps that use React, Formik, i18next, and react-i18next.
64

7-
### Render Example
5+
## Modern usage guidance
86

9-
This example is based on using the `render` method for rendering a Formik form
7+
These helpers are for edge cases where translations live outside normal React rendering.
108

11-
```
9+
For most React UI, prefer calling `t(...)` during render or using `Trans` from `react-i18next`. For most Formik validation, prefer storing translation keys and values in form errors, then translating those errors during render. That approach re-renders naturally when the active language changes.
1210

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-
);
11+
Use this package when an existing app stores already-translated Formik error strings, or when sanitized dynamic HTML contains `data-i18n` tokens that React does not render directly.
4412

45-
}
13+
## Installation
4614

15+
```sh
16+
npm install react-i18next-helpers
4717
```
4818

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
19+
Install the peer dependencies in your app:
5220

21+
```sh
22+
npm install formik i18next react react-i18next
5323
```
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-
};
9424

25+
## Translate Formik errors after language changes
9526

96-
const WithTranslateFormErrors = ({ errors, touched, setFieldTouched, children }) => {
97-
useTranslateFormErrors(errors, touched, setFieldTouched);
98-
return <>{children}</>;
99-
};
27+
Formik validation errors can stay in the previous language after `i18n.changeLanguage()` runs if validation stores translated strings instead of translation keys. `useTranslateFormErrors` re-touches fields that already have errors and have been touched, causing Formik to validate them again.
10028

101-
WithTranslateFormErrors.propTypes = {
102-
form: PropTypes.object
103-
};
29+
Use this helper for legacy or compatibility flows. In new code, prefer rendering errors from keys:
10430

105-
export default WithTranslateFormErrors;
31+
```jsx
32+
{formik.errors.name && <div>{t(formik.errors.name)}</div>}
33+
```
10634

35+
```jsx
36+
import { Formik } from 'formik';
37+
import { useTranslateFormErrors } from 'react-i18next-helpers';
38+
39+
const ContactForm = formik => {
40+
useTranslateFormErrors(formik.errors, formik.touched, formik.setFieldTouched);
41+
42+
return (
43+
<form onSubmit={formik.handleSubmit}>
44+
<input
45+
type="text"
46+
name="name"
47+
onChange={formik.handleChange}
48+
onBlur={formik.handleBlur}
49+
value={formik.values.name}
50+
/>
51+
{formik.errors.name && <div>{formik.errors.name}</div>}
52+
<button type="submit">Submit</button>
53+
</form>
54+
);
55+
};
10756

57+
const Example = () => (
58+
<Formik initialValues={{ name: '' }} onSubmit={values => console.log(values)}>
59+
{formik => <ContactForm {...formik} />}
60+
</Formik>
61+
);
10862
```
10963

64+
You can also use `WithTranslateFormErrors` when you want a wrapper component:
65+
66+
```jsx
67+
import { Formik } from 'formik';
68+
import { WithTranslateFormErrors } from 'react-i18next-helpers';
69+
70+
const Example = () => (
71+
<Formik initialValues={{ name: '' }} onSubmit={values => console.log(values)}>
72+
{formik => (
73+
<WithTranslateFormErrors form={formik}>
74+
<form onSubmit={formik.handleSubmit}>
75+
<input
76+
type="text"
77+
name="name"
78+
onChange={formik.handleChange}
79+
onBlur={formik.handleBlur}
80+
value={formik.values.name}
81+
/>
82+
{formik.errors.name && <div>{formik.errors.name}</div>}
83+
<button type="submit">Submit</button>
84+
</form>
85+
</WithTranslateFormErrors>
86+
)}
87+
</Formik>
88+
);
89+
```
11090

91+
## Translate dynamic HTML
11192

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
93+
`useTranslateHtmlElement` translates elements inside dynamic HTML that use a `data-i18n` attribute. This is intended for CMS or backend-provided HTML that React does not own directly.
11594

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.
95+
For normal React content, prefer `t(...)` or `Trans`. Sanitize untrusted HTML before rendering it.
11796

118-
```
97+
```jsx
98+
import DOMPurify from 'dompurify';
99+
import { useTranslateHtmlElement } from 'react-i18next-helpers';
119100

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-
};
101+
const SafeHtml = ({ html }) => {
102+
const [ref] = useTranslateHtmlElement(html);
103+
const safeHtml = DOMPurify.sanitize(html);
143104

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];
105+
return <div ref={ref} dangerouslySetInnerHTML={{ __html: safeHtml }} />;
163106
};
164-
165-
export default useTranslateHtmlElement;
166-
167107
```
168108

169-
170-
171-
172-
The example is self explanatory.
109+
```html
110+
<span data-i18n="profile.greeting"></span>
111+
```

dist/hoc/WithTranslateFormErrors.js

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

0 commit comments

Comments
 (0)