I am not able to come up with a way to translate field level validations in redux-form.
In the example below, how can I translate a string outside of the component scope?
const minValue0 = minValue(0)('translatedString')
const TheForm => props => {
.. return(<Field
id="quantity"
type="number"
name="quantity"
validate={[minValue0]}
component={InputField} />)
}
Defining minValue0 inside the component does not work because the validation will break.
You can require i18n and use it's t function outside of components (no translate hoc), sample:
import Validator from 'validatorjs';
import i18n from './i18n';
import { unflatten } from 'flat';
// Check if value is includes in usedValuesString.
// Comparison is performed after passing values through 'normalizer'.
function checkUniqueness(value, usedValuesString, normalizer) {
const normalizedUsedValues = usedValuesString.split(',').map(normalizer);
const normalizedValue = normalizer(value);
return !normalizedUsedValues.includes(normalizedValue);
}
export function validate(rules) {
return (values) => {
const commonMessages = i18n.t('validation:validationErrors', { returnObjects: true });
const customMessages = i18n.t('validation', { returnObjects: true });
const validator = new Validator(values || {}, rules, { ...commonMessages, ...customMessages });
validator.passes();
const errors = {};
Object.keys(validator.errors.all()).forEach(field => {
errors[field] = validator.errors.first(field);
});
return unflatten(errors);
};
}
So for you code that would mean if i get it right:
import i18n from '../i18n'; // or where every that file is
const minValue0 = minValue(0)(i18n.t('translatedString'))
const TheForm => props => {
.. return(<Field
id="quantity"
type="number"
name="quantity"
validate={[minValue0]}
component={InputField} />)
}
Hey, tried it out but could not get it to work.
My i18n file:
import i18n from 'i18next';
import LocizeBackend from 'i18next-locize-backend';
import LocizeEditor from 'locize-editor';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(LocizeBackend)
.use(LocizeEditor)
.use(LanguageDetector)
.init({
fallbackLng: 'en',
appendNamespaceToCIMode: true,
saveMissing: true,
// have a common namespace used around the full app
ns: ['translations'],
defaultNS: 'translations',
debug: true,
keySeparator: '### not used ###', // we use content as keys
backend: {
projectId: '386b678b-7052-48ba-ac20-7bb93b01bd52', // <-- replace with your projectId
apiKey: '86346aab-bb3d-42e9-9e8b-71e99f877ace',
referenceLng: 'en'
},
interpolation: {
escapeValue: false, // not needed for react!!
formatSeparator: ',',
format: function(value, format, lng) {
if (format === 'uppercase') return value.toUpperCase();
return value;
}
},
react: {
wait: true
}
});
export default i18n;
I get like 78 of these errors, seems like some kind of loop:
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
I also have these import in the same file. Maybe it breaks something?
import React from 'react'
import { translate } from 'react-i18next'
import { Field, reduxForm } from 'redux-form'
import {minValue } from './validators'
import i18n from '../../i18n'; // or where every that file is
const minValue0 = minValue(0)(i18n.t('translatedString'))
const OrderForm => props => {
.. return(<Field
id="quantity"
type="number"
name="quantity"
validate={[minValue0]}
component={InputField} />)
}
const OrderFormComponent = reduxForm({
form: 'order'})(OrderForm)
export default translate('translations')(OrderFormComponent)
not sure but guessing the warnings come from missing key? Do those show up before the setState warnings? If so that shouldn't be an issue on next reload as the key would been added (saveMissing).
Looking at the full code i would move the minValue0 to your render function - so you're sure i18next has loaded the translations - else calling t upfront will result in not existing value - or even set to wrong language (if changing that programmatically):
import React from 'react'
import { translate } from 'react-i18next'
import { Field, reduxForm } from 'redux-form'
import {minValue } from './validators'
import i18n from '../../i18n'; // or where every that file is
const OrderForm => props => {
.. return(<Field
id="quantity"
type="number"
name="quantity"
validate={[minValue(0)(i18n.t('translatedString'))]}
component={InputField} />)
}
const OrderFormComponent = reduxForm({
form: 'order'})(OrderForm)
export default translate('translations')(OrderFormComponent)
Beside that you leaked your API key - i highly recommend to generate a new one in your project settings and remove the old one - else people could use that to add content on your behalf.
Oops. Generated a new one.
The warnings will be generated every time if the i18n.t('string') is use and on top of that the translations will not be rendered. If I remove i18n.t('string'), everything starts working again.
Moving minValue0 to render will not work, thats the whole problem. Redux-form is not designed that way for some reason. That is why I need to translate the key outside the component.
On another note, how should the translate work in production if the API key cannot be exposed. My react project is client side?
Strange technically there is no difference between:
validate={[minValue(0)(i18n.t('translatedString'))]}
and
validate={[minValue0]}
but you might use validate={[minValue(0)(props.t('translatedString'))]} as t is on props too...
what is minValue returning?!? https://redux-form.com/7.0.4/docs/api/field.md/#-code-validate-value-allvalues-props-name-gt-error-code-optional-
The API key is not needed in production - it is only needed for writing missings. The projectId is enough to load translations.
There is a difference, if inside the render method, the validation function is regenerated every time and that causes failure https://github.com/erikras/redux-form/issues/2453 (erikras comment). So as I understand there is no option from the i18n side also, since it might not be loaded yet?
did you try the suggestion from @szerintedmi https://github.com/erikras/redux-form/issues/2453#issuecomment-330181584
@jamuhl. That solution seems to work, even though it forces the use of component with a state. If you would like to use stateless component, the gist in this comment seems to do the trick.
yes the alternative would be a component memoizing once created...
personally i would go the memoizing way - but would extend it so there is a listen to langaugeChanged event on i18next and on change the mem would be resetted.
Further test shows that having two memoized validators also creates an issue of not validating the fields. Like -> [minValue0, required].
How annoying...what we did (before there was validate introduced on field:
Using https://redux-form.com/7.0.4/docs/api/reduxform.md/#-code-validate-values-object-props-object-gt-errors-object-code-optional-
using the hoc
form({
form: 'loginForm',
validate: validate({
username: 'required|max:50',
password: 'required|min:8'
}),
autoToggleReadonly: false
})(MyComponent);
where validate is:
import Validator from 'validatorjs';
import i18n from './i18n';
import { unflatten } from 'flat';
// Check if value is includes in usedValuesString.
// Comparison is performed after passing values through 'normalizer'.
function checkUniqueness(value, usedValuesString, normalizer) {
const normalizedUsedValues = usedValuesString.split(',').map(normalizer);
const normalizedValue = normalizer(value);
return !normalizedUsedValues.includes(normalizedValue);
}
export function validate(rules) {
return (values) => {
const commonMessages = i18n.t('validation:validationErrors', { returnObjects: true });
const customMessages = i18n.t('validation', { returnObjects: true });
const validator = new Validator(values || {}, rules, { ...commonMessages, ...customMessages });
validator.passes();
const errors = {};
Object.keys(validator.errors.all()).forEach(field => {
errors[field] = validator.errors.first(field);
});
return unflatten(errors);
};
}
But i would open an issue over at redux-form - i mean having two such memoized validators should work in my opinion...
I headed the same question and I was also dealing with async server validation where I receive constants, and I solved that by returning constants also from static validators:
export const required = value => !value ? 'REQUIRED' : false
And translate them in rendering component
{touched && error && <span>{t(error)}</span>}
@melounek This seems to be another good workaround. Only problem I see with this is when my error would use a variable.
For example:
export const minValue = message => min => value => value && value < min ? `${message} ${min}` : undefined
@rntorm did you find a solution to this? if so could this be closed?
@jamuhl Yeah, I used a workaround that creates the translations in the class constructor. It works that way, even though its not the perfect solution. I prefer stateless components to class.
I wanted to document a related problem that I have managed to solve. The issue is already closed, but I believe this is the best place for others to find this hint.
In my project, I am using redux, React, redux-form FieldArrays, react-select, react-i18next.
I wanted to create a form which was able to create an array as one of the form fields. I did not manage to use a function from the props for the 'component' of the FieldArray inside the render function of the form component, but had to create one outside of the render function. That helped me to avoid an infinite loop and is working fine.
Of course it was necessary to import i18n's t function as @jamuhl recommends https://github.com/i18next/react-i18next/issues/306#issuecomment-332868722 .
Most helpful comment
You can require i18n and use it's
tfunction outside of components (no translate hoc), sample: