Saturday, 14 May, 2022 UTC


Summary

NextJs + i18n
Documentaci贸n Original: Notion
Demo

馃搼 脥ndice General
馃Requisitos
馃實Internacionalizaci贸n

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 [email protected] --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
  • 鈿欙笍 Configuraci贸n
  • 鉃 A帽adiendo idiomas 鈥 next-i18next
  • 馃殑 Transici贸n entre locales

馃 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.
脥ndice

鈿欙笍 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 Unidos
  • en聽鈥 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;
鈿狅笍 鉂桟ada 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! 馃憦
脥ndice

鉃 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 鈥渉ome鈥 tendr谩 sus variables y la p谩gina de 鈥渁bout鈥 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 getStaticPropspara 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:
  • http://localhost:3000/
  • http://localhost:3000/en-US
  • http://localhost:3000/fr
  • 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;
脥ndice

馃殑 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;
GitHub
View Github