In this example we are going to start form a simple login application and add multilanguage support.
Scenarios that we are going to cover:
- Translate markup text.
- Translate text generated by javascript.
- User interpolation.
More samples coming soon:
- Split language resources in json files.
- Use namespaces.
In the future we will incorporate more samples.
About folder structure:
-
00_start: Starting point (no multilanguage).
-
01_basic_translastion: application supporting mulilanguage (english / spanish).
-
02_change_language: added tu buttons to change the preffered language on the fly.
Each project (except 00 start) includes in its subfolder a Readme.md file with an step by step guide to reproduce it.
Official React Guide: https://react.i18next.com/guides/quick-start.
- Let's start by installing the react-i18next library.
npm install react-i18next i18next --save
- Create a new file i18n.js containing the following content.
./src/i18n.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
// the translations
// (tip move them in a JSON file and import them)
const resources = {
en: {
translation: {
login: "Login",
"Invalid login or password, please type again":
"Invalid login or password, please type again",
"error, review the fields": "error, review the fields"
}
},
es: {
translation: {
login: "Introduzca credenciales",
"Invalid login or password, please type again":
"Usuario o clave no validos, porfavor intentelo de nuevo",
"error, review the fields": "Error, revise los campos por favor"
}
}
};
i18n
.use(initReactI18next) // passes i18n down to react-i18next
.init({
resources,
lng: "en",
keySeparator: false, // we do not use keys in form messages.welcome
interpolation: {
escapeValue: false // react already safes from xss
}
});
export default i18n;
The interesting part here is by i18n.use(initReactI18next) we pass the i18n instance to react-i18next which will make it available for all the components via the context api.
Now let's import this fail in our main.tsx
./src/main.tsx
import * as React from "react";
import { withStyles, createStyles, WithStyles } from "@material-ui/core/styles";
+ import './i18n';
- Let's start translating the heading of the login panel, to do that we will make use of a hook that i18n provides.
./src/pages/login/loginPage.tsx
// (...)
+ import { useTranslation } from 'react-i18next';
// (...)
const LoginPageInner = (props: Props) => {
+ const { t, i18n } = useTranslation();
const { loginInfo, setLoginInfo } = useLogin();
// (...)
return (
<>
<Card className={classes.card}>
- <CardHeader title="Login" />
+ <CardHeader title={t('login')} />
<CardContent>
<LoginForm
onLogin={onLogin}
onUpdateField={onUpdateLoginField}
loginInfo={loginInfo}
loginFormErrors={loginFormErrors}
/>
</CardContent>
</Card>
<NotificationComponent
- message="Invalid login or password, please type again"
+ message={t('Invalid login or password, please type again')}
show={showLoginFailedMessage}
onClose={() => setShowLoginFailedMessage(false)}
/>
</>
);
};
If you are using class based components react i18next exposes an HOC for you to get access to t.
- So far so good, let's check now how can we add multilanguage support to a literal that is injected via javascript.
./src/pages/login/loginPage.tsx
const onLogin = () => {
loginFormValidation.validateForm(loginInfo).then(formValidationResult => {
if (formValidationResult.succeeded) {
if (isValidLogin(loginInfo)) {
loginContext.updateLogin(loginInfo.login);
props.history.push("/pageB");
} else {
setShowLoginFailedMessage(true);
}
} else {
- alert("error, review the fields");
+ alert(t("error, review the fields");
}
});
};
That was nice, let's give a try now to interpolation, in pageB we are showing a message like:
<h3>Login: {props.login}</h3>
We don't need to concatenate translated strings, we can directly include the variables in the string.
- Let's define the entry in the our root i18next.ts file.
./src/i18n.ts
const resources = {
en: {
translation: {
login: "Login",
"Invalid login or password, please type again":
"Invalid login or password, please type again",
"error, review the fields": "error, review the fields rrr",
+ "login plus username": "Usuario: {{username}}"
}
},
es: {
translation: {
login: "Introduzca credenciales",
"Invalid login or password, please type again":
"Usuario o clave no validos, porfavor intentelo de nuevo",
"error, review the fields": "Error, revise los campos por favor",
+ "login plus username": "Usuario: {username}",
}
}
};
To reach this page you have to enter as valid username and password keys admin and test
- Let's use this on the pageB
./src/pages/b/pageB.tsx
import * as React from "react"
import { Link } from 'react-router-dom';
import { SessionContext, withSessionContext } from '../../common/'
+ import { useTranslation } from 'react-i18next';
interface Props {
login : string;
}
- const PageBInner = (props : Props) =>
+ const PageBInner = (props : Props) => {
+ const { t, i18n } = useTranslation();
+ return (
<>
<h2>Hello from page B</h2>
<br />
<br />
- <h3>Login: {props.login}</h3>
+ <h3>t({login plus username}, {username: props.login})</h3>
<Link to="/">Navigate to Login</Link>
</>
+ )
+ }
export const PageB = withSessionContext(PageBInner);
-
To wrap up this example let's translate the inline validation that appear below each input field.
-
In the loginForm on each textFieldForm we are informing a friendly error message, instead of using that, let's use the type of the validation error.
./src/pages/login/loginForm.tsx
<TextFieldForm
label="Name"
name="login"
value={loginInfo.login}
onChange={onUpdateField}
- error={loginFormErrors.login.errorMessage}
+ loginFormErrors.login.succeeded ? "" : loginFormErrors.login.type
/>
<TextFieldForm
label="Password"
name="password"
value={loginInfo.password}
onChange={onUpdateField}
- error={loginFormErrors.password.errorMessage}
+ error={loginFormErrors.password.succeeded ? "" : loginFormErrors.password.type}
/>
- Now if we run the application we will see just the type of the error being shown (not very friendly).
npm start
- Let's add translation keys for that validation errors.
./src/i18n.ts
const resources = {
en: {
translation: {
login: "Login",
"Invalid login or password, please type again":
"Invalid login or password, please type again",
"error, review the fields": "error, review the fields rrr",
"login plus username": "Usuario: {{username}}",
+ "REQUIRED": "Mandatory field",
}
},
es: {
translation: {
login: "Introduzca credenciales",
"Invalid login or password, please type again":
"Usuario o clave no validos, porfavor intentelo de nuevo",
"error, review the fields": "Error, revise los campos por favor",
"login plus username": "Usuario: {{username}}"
+ "REQUIRED": "Campo obligatorio",
}
}
};
- Let's add i18n translator helper to loginForm, and properly translate the text.
./src/pages/login/loginForm.tsx
+ import { useTranslation } from "react-i18next";
export const LoginForm = (props: Props) => {
+ const { t, i18n } = useTranslation();
const { onLogin, onUpdateField, loginInfo, loginFormErrors } = props;
// (...)
return (
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center"
}}
>
<TextFieldForm
label="Name"
name="login"
value={loginInfo.login}
onChange={onUpdateField}
error={
- loginFormErrors.login.succeeded ? "" : loginFormErrors.login.type
+ loginFormErrors.login.succeeded ? "" : t(loginFormErrors.login.type)
}
/>
<TextFieldForm
label="Password"
name="password"
value={loginInfo.password}
onChange={onUpdateField}
error={
loginFormErrors.password.succeeded
? ""
- : loginFormErrors.password.type
+ : t(loginFormErrors.password.type)
}
/>
Excercise: Could you add multilanguage support to the login button?