NextJs + i18n
Documentación Original: Notion
? Índice General
Requisitos
- Leer la documentación oficial
- NextJs: https://nextjs.org/docs
- Nextjs-i18n: https://nextjs.org/docs/advanced-features/i18n-routing
- i18next: https://www.i18next.com/
- next-i18next: https://github.com/isaachinman/next-i18next
- Iniciar proyecto
npx create-next-app@latest --typescript # or yarn create next-app --typescript # or pnpm create next-app -- --typescript
Internacionalización
? ? Si te has leído la documentación, enhorabuena, no necesitas seguir leyendo ? ?
? Índice
? Background
Nextjs tiene soporte integrado para rutas con i18n desde la versión 10.0.0.
Podemos establecer una lista de localizaciones, la localización por defecto y las localizaciones específicas del dominio y Nextjs se encarga automáticamente de su gestión.
Este soporte está destinado a complementar las soluciones de bibliotecas i18n existentes como, react-i18next por ejemplo.
⚙️ Configuración
El siguiente fragmento del código debemos introducirlo en el fichero next.config.js
en el directorio raíz del proyecto.
Generalmente el nombre del local está compuesto por la lengua, región y script separado por un guión. La región y script son opcionales. Por ejemplo:
en-US
– inglés de la región de Estados Unidosen
– también se puede dejar así
Resultado:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
i18n: {
// all of locales suported in our app
locales: ["en-US", "fr", "es-ES"],
// set the default languages for our app
defaultLocale: "es-ES",
// list of the possible domain
domains: [
{
domain: "example.com",
defaultLocale: "en-US",
},
{
domain: "example.fr",
defaultLocale: "fr",
},
{
domain: "example.es",
defaultLocale: "es-ES",
// OPTIONAL: varaible to test the locales in http
http: true,
},
],
},
};
module.exports = nextConfig;
⚠️ ❗Cada vez que se modifica este fichero hay que reiniciar el server del nextjs ❗⚠️
> Found a change in next.config.js. Restart the server to see the changes in effect.
Ahora si queremos ir http://localhost:3000/fr deberíamos tener la misma página sin cambios, en caso de no introducir los cambios de arriba, obtendríamos 404 | This page could not be found.
? ¡Ya tenemos funcionando i18n con nextjs! ?
➕ Añadir idiomas – next-i18next
Vamos a utilizar la librería next-i18next
creado a partir de react-i18next
para Nextjs.
yarn add next-i18next
# or
npm i --save next-i18next
? Documentación: [https://www.i18next.com/](https://www.i18next.com/) [https://github.com/isaachinman/next-i18next](https://github.com/isaachinman/next-i18next)
Una vez añadido la librería, debemos realizar añadir su configuración en el fichero next-i18next.config.js
en el directorio raíz del proyecto. Este fichero será idéntico al next.config.js
:
//next-i18next.config.js
module.exports = {
i18n: {
locales: ["en-US", "fr", "es-ES"],
defaultLocale: "es-ES",
},
};
⚠️ Si añadimos otro idioma en el futuro habrá que cambiar ambos ficheros con sus respectivos cambios.
? Consejo: podemos exportar el objeto i18n desde `next.config.js` , y usarlo en el `next-i18next.config.js`, así se mantiene ambos ficheros sincronizados.
Ahora debemos modificar pages/_app.js
puesto que vamos a usar componente de orden superior (HOC) para englobar nuestra aplicación. Por lo que el fichero va a quedar de la siguiente manera:
//pages/_app.tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { appWithTranslation } from "next-i18next";
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default appWithTranslation(MyApp);
Ahora vamos a renderizar nuestra página dependiendo de unos ficheros json que contendrán los valores en dichos lenguajes.
Vamos a añadir 3 carpetas, una por cada idioma en nuestra aplicación: en-US
, fr
y es-ES
. Podemos crear estos ficheros en cualquier directorio, en nuestro caso lo vamos hacer en el /public/locale
quedandose de la siguiente manera:
en-US/home.json
{
"welcome_msg": "Welcome to your NextJs-i18n app"
}
fr/home.json
{
"welcome_msg": "Bienvenue dans notre application NextJs-i18n"
}
es-ES/home.json
{
"welcome_msg": "Bienvenido a nuestra aplicación de NextJs-i18n"
}
⚠️ ⚠️ Además del fichero es-ES/home.json
la librería de next-i18next
por defecto espera que haya una estructura de carpeta igual a esta:
.
└── public
└── locales
├── en
| └── common.json
└── de
└── common.json
Por lo que nuestra estructura de ficheros sería la siguiente:
.
└── public
└── locales
├── en-US
| └── common.json
| └── home.json
└── fr
| └── common.json
| └── home.json
└── en-ES
└── common.json
└── home.json
ℹ️ Vamos a utilizar un fichero json por cada página que tenemos en nuestro proyecto. Es decir, la página de “home” tendrá sus variables y la página de “about” tendrá las suyas propias. Y el fichero common.json podría ser necesario para todas las páginas.
Una vez que tenemos estos ficheros creados vamos a utilizarlo en nuestra página de home que se encuentra en /pages/index.tsx
. creados Vamos a utilizar unas de las ventajas que nos ofrece Nextjs que es getStaticProps
para obtener los datos previo a la renderización de la página.
? getStaticProps: https://nextjs.org/docs/basic-features/data-fetching/get-static-props
Primero añadimos la función getStaticProps
//pages/index.tsx
import { GetStaticProps } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
export const getStaticProps: GetStaticProps = async (context) => {
const { locale } = context;
//locale can be string or undefined, so we do a check before returning the props
if (locale) {
return {
props: {
//fetch the file for our home page depending of the locale
...(await serverSideTranslations(locale, ["home"])),
},
};
}
//in case the locale is undefined, we can just serve our default languege file
return {
props: {
...(await serverSideTranslations("es-ES", ["home"])),
},
};
};
Todavía nuestra página no está haciendo uso del fichero de idiomas, para ello debemos añadir el siguiente hook useTranslation()
de next-i18next
de la siguiente manera:
//pages/index.tsx
import type { NextPage } from "next";
import Head from "next/head";
import styles from "../styles/Home.module.css";
import { useTranslation } from "next-i18next";
const Home: NextPage = () => {
const { t } = useTranslation();
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
{/* here we are accessing welcome_msg from home.json */}
<h1 className={styles.title}>{t("home:welcome_msg")}</h1>
</main>
</div>
);
};
export default Home;
Ahora si, podemos ir a las siguientes rutas y veremos como cambia el mensaje de bienvenida:
-
Resultado final:
//pages/index.tsx import type { NextPage } from "next"; import Head from "next/head"; import { useTranslation } from "next-i18next"; import { GetStaticProps } from "next"; import styles from "../styles/Home.module.css"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; export const getStaticProps: GetStaticProps = async (context) => { const { locale } = context; //locale can be string or undefined, so we do a check before returning the props if (locale) { return { props: { ...(await serverSideTranslations(locale, ["home"])), }, }; } //in case the locale is undefined, we can just serve our default langueges return { props: { ...(await serverSideTranslations("es-ES", ["home"])), }, }; }; const Home: NextPage = () => { const { t } = useTranslation(); return ( <div className={styles.container}> <Head> <title>Create Next App</title> <meta name="description" content="Generated by create next app" /> <link rel="icon" href="/favicon.ico" /> </Head> <main className={styles.main}> {/* here we are accessing welcome_msg from home.json */} <h1 className={styles.title}>{t("home:welcome_msg")}</h1> </main> </div> ); }; export default Home;
? Transición entre locales
La transición se puede realizar a través de next/link
o next/router
de la siguiente manera:
//pages/index.tsx
//skipped imports
import Link from "next/link";
// skipped getStaticProps code
const Home: NextPage = () => {
const { t } = useTranslation();
return (
<>
{/*skipped code */}
<h4> Static *</h4>*<ul style={{ margin: "0" }}>
<li>
<Link href="/" locale="es-ES">
<a style={{ textDecoration: "underline" }}>To /</a>
</Link>
</li>
<li>
<Link href="/" locale="fr">
<a style={{ textDecoration: "underline" }}>To /fr</a>
</Link>
</li>
<li>
<Link href="/" locale="en-US">
<a style={{ textDecoration: "underline" }}>To /en-US</a>
</Link>
</li>
</ul>
</>
);
};
export default Home;
Con next/router
tendríamos:
//pages/index.tsx
//skipped imports
import { useRouter } from "next/router";
// skipped getStaticProps code
const Home: NextPage = () => {
const { t } = useTranslation();
const router = useRouter();
return (
<>
{/*skipped code */}
<h4> With next/router </h4>
<div
style={{ cursor: "pointer" }}
onClick={() => {
router.push("/", "/", { locale: "fr" });
}}
>
to /fr
</div>
</>
);
};
export default Home;
¿Y si necesitamos rutas dinámicas? Pues también lo tenemos cubierto
//pages/index.tsx
//skipped imports
import { useRouter } from "next/router";
// skipped getStaticProps code
const Home: NextPage = () => {
const { t } = useTranslation();
const router = useRouter();
const dynamicLink = (locale: string) => {
router.push({ pathname, query }, asPath, { locale: locale });
};
return (
<>
{/*skipped code */}
<h4> Dynamic route with router.push() </h4>
<div
style={{ cursor: "pointer" }}
onClick={() => {
// This variable can come from the api of another page, etc.
// and we could go to the locale of that page
dynamicLink("fr");
}}
>
to /fr
</div>
</>
);
};
export default Home;