C贸mo traducir correctamente una aplicaci贸n React usando i18next

Translation into Spanish of an interesting article by Adriano Raiano, bachelor in computer science, software architect, and founder of locize.com,

APPi18nextreacttranslation into spanish
27 April, 2022 Translate react app with i18n
27 April, 2022 Translate react app with i18n

A free translation by Chema, a Spain-based translator specializing in English to Spanish translations of software and apps

An original text written by聽Adriano Raiano, originally published in
https://locize.com/blog/how-to-internationalize-react-i18next/

* * *

Ayudar a superar la barrera del idioma a los usuarios que utilizan un software es algo muy importante. El ingl茅s ya no es el idioma universal de Internet. En marzo de 2020, solo el 25,9 % de los usuarios de Internet hablaban ingl茅s. Hay muchas posibilidades de que tus usuarios pasen por alto tu sitio web si no est谩 traducido y localizado. Si no tienes un sitio web multiling眉e, est谩s perdiendo una gran parte de tus usuarios o clientes potenciales.

En el ecosistema de JavaScript, hay muchos frameworks de internacionalizaci贸n. Aqu铆 puedes encontrar detalles sobre algunos frameworks de JavaScript. En este art铆culo, usaremos i18next para internacionalizar una aplicaci贸n React.js.

Indice

  • Antes que nada: “驴Por qu茅 i18next?”
  • Entramos en materia
    • Requisitos previos
    • Empezamos
    • Conmutador de idioma
    • Interpolaci贸n y Pluralizaci贸n
    • Formateo
    • Contexto
    • Separar las traducciones del c贸digo
    • Mejor gesti贸n de la traducci贸n
      • 隆Con seguridad!
      • 驴C贸mo se ve esto?
      • Guardar traducciones faltantes
      • 馃憖 Aun hay m谩s…
      • 馃摝 Preparaci贸n para producci贸n 馃殌
      • 馃帀馃コ Felicidades 馃帄馃巵

Antes que nada: “驴Por qu茅 i18next?”

Cuando se trata de la localizaci贸n de React, uno de los frameworks m谩s populares es i18next con su extensi贸n react-i18next, y con raz贸n!

i18next se cre贸 a fines de 2011. Es m谩s antigua que la mayor铆a de las bibliotecas que usas hoy en d铆a, incluido react, vue, etc.

鉃★笍 sostenible

Dado el tiempo que i18next ya est谩 disponible en c贸digo abierto, no hay un caso real de i18n que no pueda resolverse con i18next.

鉃★笍 maduro

i18next se puede usar en cualquier entorno javascript (y algunos que no son javascript: .net, elm, iOS, android, ruby, …), con cualquier framwork de usuario, con cualquier formato i18n, … las posibilidades son infinitas .

鉃★笍 extensible

Con i18next podr谩s usar m谩s funciones que con otros frameworks de internacionalizaci贸n.

鉃★笍 potente

Aqu铆 puedes encontrar m谩s informaci贸n sobre por qu茅 i18next es tan especial y c贸mo funciona .

Entramos en materia

Requisitos previos

Aseg煤rate de tener Node.js y npm instalados. Es bueno tener algo de experiencia con HTML , JavaScript y React.js, antes de saltar a react-i18next .

Empezamos

Usa tu propio proyecto React o crea uno nuevo con create-react-app.

npx create-react-app my-app

Vamos a adaptar la app para que detecte el idioma seg煤n la preferencia del usuario.
Y crearemos un conmutador de idioma para que el contenido cambie entre diferentes idiomas.

Instalamos algunas dependencias de i18next:

npm install i18next react-i18next i18next-browser-languagedetector

Preparamos un archivo i18n.js:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    resources: {
      en: {
        translation: {
          // here we will place our translations...
        }
      }
    }
  });

export default i18n;

Importamos ese archivo en alg煤n lugar de nuestro archivo index.js:

Para React >= 18.0.0 usamos:

import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';

// import i18n (needs to be bundled ;))
import './i18n';

const root = createRoot(document.getElementById('root'))
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Para versiones anteriores de React usamos:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

// import i18n (needs to be bundled ;))
import './i18n';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

Ahora intentemos mover un texto codificado de forma r铆gida a las traducciones.

Hemos usado el聽componente Trans聽para el primer texto y el聽
useTranslation hook para el segundo texto:

import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';

function App() {
  const { t } = useTranslation();

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          <Trans i18nKey="description.part1">
            Edit <code>src/App.js</code> and save to reload.
          </Trans>
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          {t('description.part2')}
        </a>
      </header>
    </div>
  );
}

export default App;

Los textos ahora son parte de los recursos de traducci贸n:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          }
        }
      }
    }
  });

export default i18n;

Conmutador de idioma

Ahora definimos un conmutador de idioma:

import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';

const lngs = {
  en: { nativeName: 'English' },
  de: { nativeName: 'Deutsch' }
};

function App() {
  const { t, i18n } = useTranslation();

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div>
          {Object.keys(lngs).map((lng) => (
            <button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => i18n.changeLanguage(lng)}>
              {lngs[lng].nativeName}
            </button>
          ))}
        </div>
        <p>
          <Trans i18nKey="description.part1">
            Edit <code>src/App.js</code> and save to reload.
          </Trans>
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          {t('description.part2')}
        </a>
      </header>
    </div>
  );
}

export default App;

Y tambi茅n agregamos algunas traducciones para el nuevo idioma:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          }
        }
      },
      de: {
        translation: {
          description: {
            part1: '脛ndere <1>src/App.js</1> und speichere um neu zu laden.',
            part2: 'Lerne React'
          }
        }
      }
    }
  });

export default i18n;

馃コ Genial, 隆acabas de crear tu primer conmutador de idiomas!

Gracias a i18next-browser-languagedetector ahora intenta detectar el idioma del navegador y autom谩ticamente usa ese idioma si le proporcionaste las traducciones. El idioma seleccionado manualmente en el conmutador de idioma se mantiene en el almacenamiento local; la pr贸xima vez que alguien visite la p谩gina, ese idioma se utilizar谩 como idioma preferido.

Interpolaci贸n y Pluralizaci贸n

i18next va m谩s all谩 de proporcionar las caracter铆sticas est谩ndar de i18n.
Pero seguro que es capaz de manejar plurales e interpolaciones .

Contemos cada vez que se cambia el idioma:

import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
import { useState } from 'react';

const lngs = {
  en: { nativeName: 'English' },
  de: { nativeName: 'Deutsch' }
};

function App() {
  const { t, i18n } = useTranslation();
  const [count, setCounter] = useState(0);

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div>
          {Object.keys(lngs).map((lng) => (
            <button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => {
              i18n.changeLanguage(lng);
              setCounter(count + 1);
            }}>
              {lngs[lng].nativeName}
            </button>
          ))}
        </div>
        <p>
          <i>{t('counter', { count })}</i>
        </p>
        <p>
          <Trans i18nKey="description.part1">
            Edit <code>src/App.js</code> and save to reload.
          </Trans>
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          {t('description.part2')}
        </a>
      </header>
    </div>
  );
}

export default App;

…y ampliando los recursos de traducci贸n:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          },
          counter_one: 'Changed language just once',
          counter_other: 'Changed language already {{count}} times'
        }
      },
      de: {
        translation: {
          description: {
            part1: '脛ndere <1>src/App.js</1> und speichere um neu zu laden.',
            part2: 'Lerne React'
          },
          counter_one: 'Die Sprache wurde erst ein mal gewechselt',
          counter_other: 'Die Sprache wurde {{count}} mal gewechselt'
        }
      }
    }
  });

export default i18n;

Seg煤n el valor de conteo, i18next elegir谩 la forma plural correcta.
+ info sobre pluralizaci贸n e interpolaci贸n en la documentaci贸n oficial de i18next .

Texto alternativo

馃挕 i18next tambi茅n puede manejar idiomas con m煤ltiples formas plurales, como el 谩rabe:

// translation resources:
{
  "key_0": "zero",
  "key_1": "singular",
  "key_2": "two",
  "key_3": "few",
  "key_4": "many",
  "key_5": "other"
}

// usage:
t('key', {count: 0}); // -> "zero"
t('key', {count: 1}); // -> "singular"
t('key', {count: 2}); // -> "two"
t('key', {count: 3}); // -> "few"
t('key', {count: 4}); // -> "few"
t('key', {count: 5}); // -> "few"
t('key', {count: 11}); // -> "many"
t('key', {count: 99}); // -> "many"
t('key', {count: 100}); // -> "other"

Formateo

Ahora, veamos c贸mo podemos usar diferentes formatos de fecha con la ayuda de i18next y Luxon para manejar la fecha y la hora.

npm install luxon

Nos gusta tener un pie de p谩gina que muestre la fecha actual:

import './Footer.css';

const Footer = ({ t }) => (
  <div className="Footer">
    <div>{t('footer.date', { date: new Date() })}</div>
  </div>
);

export default Footer;

// imported in our App.js and used like this
// <Footer t={t} />

Importamos luxon y definimos una funci贸n de formato, como se documenta en la documentaci贸n, y agregamos la nueva clave de traducci贸n:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { DateTime } from 'luxon';

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          },
          counter_one: 'Changed language just once',
          counter_other: 'Changed language already {{count}} times',
          footer: {
            date: 'Today is {{date, DATE_HUGE}}'
          }
        }
      },
      de: {
        translation: {
          description: {
            part1: '脛ndere <1>src/App.js</1> und speichere um neu zu laden.',
            part2: 'Lerne React'
          },
          counter_one: 'Die Sprache wurde erst ein mal gewechselt',
          counter_other: 'Die Sprache wurde {{count}} mal gewechselt',
          footer: {
            date: 'Heute ist {{date, DATE_HUGE}}'
          }
        }
      }
    }
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

馃槑 Genial, 隆ahora tenemos un formato de fecha espec铆fico para cada idioma!

Ingl茅s:

Alem谩n:

Contexto

驴Qu茅 pasa con un mensaje de saludo espec铆fico basado en la hora del d铆a actual?聽es decir, ma帽ana, tarde, etc.
Esto es posible gracias a la funci贸n context聽de i18next.

Vamos a crear una funci贸n getGreetingTime y usar el resultado como informaci贸n de contexto para nuestra traducci贸n de pie de p谩gina:

import { DateTime } from 'luxon';
import './Footer.css';

const getGreetingTime = (d = DateTime.now()) => {
    const split_afternoon = 12; // 24hr time to split the afternoon
    const split_evening = 17; // 24hr time to split the evening
    const currentHour = parseFloat(d.toFormat('hh'));

    if (currentHour >= split_afternoon && currentHour <= split_evening) {
        return 'afternoon';
    } else if (currentHour >= split_evening) {
        return 'evening';
  }
    return 'morning';
}

const Footer = ({ t }) => (
  <div className="Footer">
    <div>{t('footer.date', { date: new Date(), context: getGreetingTime() })}</div>
  </div>
);

export default Footer;

Agregamos algunas claves de traducci贸n espec铆ficas del contexto:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { DateTime } from 'luxon';

i18n
  // i18next-http-backend
  // loads translations from your server
  // https://github.com/i18next/i18next-http-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          },
          counter_one: 'Changed language just once',
          counter_other: 'Changed language already {{count}} times',
          footer: {
            date: 'Today is {{date, DATE_HUGE}}',
            date_morning: 'Good morning! Today is {{date, DATE_HUGE}} | Have a nice day!',
            date_afternoon: 'Good afternoon! It\'s {{date, DATE_HUGE}}',
            date_evening: 'Good evening! Today was the {{date, DATE_HUGE}}'
          }
        }
      },
      de: {
        translation: {
          description: {
            part1: '脛ndere <1>src/App.js</1> und speichere um neu zu laden.',
            part2: 'Lerne React'
          },
          counter_one: 'Die Sprache wurde erst ein mal gewechselt',
          counter_other: 'Die Sprache wurde {{count}} mal gewechselt',
          footer: {
            date: 'Heute ist {{date, DATE_HUGE}}',
            date_morning: 'Guten Morgen! Heute ist {{date, DATE_HUGE}} | W眉nsche einen sch枚nen Tag!',
            date_afternoon: 'Guten Tag! Es ist {{date, DATE_HUGE}}',
            date_evening: 'Guten Abend! Heute war {{date, DATE_HUGE}}'
          }
        }
      }
    }
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

馃榿 隆S铆, funciona!

Separar las traducciones del c贸digo

Tener las traducciones en nuestro archivo i18n.js funciona, pero no es adecuado para trabajar con traductores.
Es mucho mejor separar las traducciones del c贸digo y colocarlas en archivos json dedicados.

Debido a que esta es una aplicaci贸n web, i18next-http-backend nos ayudar谩 a hacerlo.

npm install i18next-http-backend

Movemos las traducciones a la carpeta p煤blica:

Adaptamos el archivo i18n.js para usar el i18next-http-backend:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { DateTime } from 'luxon';

i18n
  // i18next-http-backend
  // loads translations from your server
  // https://github.com/i18next/i18next-http-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    }
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

Ahora las traducciones se cargan de forma as铆ncrona, as铆 que aseg煤rate de envolver tu aplicaci贸n con un componente Suspense聽para evitar este error:Uncaught Error: App suspended while rendering, but no fallback UI was specified.

import { Suspense } from 'react';

function App() {
  // your app's code...
}

// here app catches the suspense from page in case translations are not yet loaded
export default function WrappedApp() {
  return (
    <Suspense fallback="...is loading">
      <App />
    </Suspense>
  );
}

Ahora tu aplicaci贸n se ve igual, pero tus traducciones est谩n separadas.
Si deseas adoptar un nuevo idioma, simplemente crea una nueva carpeta y un nuevo archivo json de traducci贸n.
Esto te permite enviar las traducciones a traductores externos.
O si est谩s trabajando con un sistema de gesti贸n de traducci贸n, simplemente puedes sincronizar los archivos con un cli .

馃挕 por cierto: tambi茅n puedes tener varios archivos de traducci贸n gracias a la funci贸n de espacios de nombres de i18next

馃鈥嶐煉 El c贸digo de esta primera parte se puede encontrar aqu铆 .

Mejor gesti贸n de la traducci贸n

Al enviar las traducciones a algunos traductores o agencias de traducci贸n, tienes m谩s control y un contacto directo con ellos. Pero esto tambi茅n significa m谩s trabajo….
Esta es la forma tradicional de traducir una App. Pero ten en cuenta que enviar archivos siempre crea una sobrecarga.

驴Existe una mejor opci贸n?

隆Con seguridad!

i18next ayuda a traducir la aplicaci贸n, y esto es genial, pero hay m谩s.

  • 驴C贸mo integrar un servicio/agencia de traducci贸n?
  • 驴C贸mo realizar un seguimiento del contenido nuevo o eliminado?
  • 驴C贸mo gestionar el control de versiones?
  • 驴C贸mo implementar cambios de traducci贸n si la app no est谩 completa?
  • y mucho m谩s…

Buscas algo como esto鉂

驴C贸mo se ve esto?

Primero debes registrarte en locize e iniciar sesi贸n .
Luego crea un nuevo proyecto en locize y agrega tus traducciones. Puedes agregar tus traducciones usando la cli o importando los archivos json individuales o a trav茅s de la API .

Hecho esto, vamos a reemplazar i18next-http-backend con i18next-locize-backend .

npm install i18next-locize-backend

Despu茅s de haber importado las traducciones para localizar, elimina la carpeta locales:

Adapta el archivo i18n.js para usar i18next-locize-backend y aseg煤rate de copiar el ID del proyecto y la clave API desde tu proyecto de localizaci贸n:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import { DateTime } from 'luxon';

const locizeOptions = {
  projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
  apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
  referenceLng: 'en',
};

i18n
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    backend: locizeOptions
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

i18next-locize-backend ofrece una funcionalidad para recuperar los idiomas disponibles directamente desde locize, us茅mosla:

import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
import { useState, Suspense, useEffect } from 'react';
import Footer from './Footer'

function App() {
  const { t, i18n } = useTranslation();
  const [count, setCounter] = useState(0);

  const [lngs, setLngs] = useState({ en: { nativeName: 'English' }});

  useEffect(() => {
    i18n.services.backendConnector.backend.getLanguages((err, ret) => {
      if (err) return // TODO: handle err...
      setLngs(ret);
    });
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div>
          {Object.keys(lngs).map((lng) => (
            <button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => {
              i18n.changeLanguage(lng);
              setCounter(count + 1);
            }}>
              {lngs[lng].nativeName}
            </button>
          ))}
        </div>
        <p>
          <i>{t('counter', { count })}</i>
        </p>
        <p>
          <Trans i18nKey="description.part1">
            Edit <code>src/App.js</code> and save to reload.
          </Trans>
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          {t('description.part2')}
        </a>
      </header>
      <Footer t={t} />
    </div>
  );
}

// here app catches the suspense from page in case translations are not yet loaded
export default function WrappedApp() {
  return (
    <Suspense fallback="...is loading">
      <App />
    </Suspense>
  );
}

Guardar traducciones faltantes

Gracias al uso de la funcionalidad 聽saveMissing, se agregan nuevas claves para localizar autom谩ticamente, mientras se desarrolla la aplicaci贸n.

Simplemente pasa saveMissing: trueen las opciones de i18next:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import { DateTime } from 'luxon';

const locizeOptions = {
  projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
  apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
  referenceLng: 'en',
};

i18n
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    backend: locizeOptions,
    saveMissing: true
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

Cada vez que se use una nueva clave, se enviar谩 a localizar.

Es decir:

<div>{t('new.key', 'this will be added automatically')}</div>

dar谩 como resultado una ubicaci贸n como esta:

馃憖 A煤n hay m谩s…

Gracias al complemento聽locize-lastused, podr谩s聽encontrar y filtrar en locize qu茅 teclas se usan o no聽.

Con la ayuda del complemento聽locize, podr谩s usar tu aplicaci贸n dentro del editor locize InContext.

Gracias al flujo de trabajo de traducci贸n autom谩tica automatizada y la funcionalidad saveMissing, no solo se agregan nuevas claves para localizar autom谩ticamente mientras se desarrolla la aplicaci贸n, sino que tambi茅n se traducen autom谩ticamente a los idiomas de destino mediante traducci贸n autom谩tica.

隆 Mira este聽video para ver el flujo de trabajo de traducci贸n autom谩tica automatizada!

npm install locize-lastused locize

util铆zalos en i18n.js:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import LastUsed from 'locize-lastused';
import { locizePlugin } from 'locize';
import { DateTime } from 'luxon';

const locizeOptions = {
  projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
  apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
  referenceLng: 'en',
};

i18n
  // locize-lastused
  // sets a timestamp of last access on every translation segment on locize
  // -> safely remove the ones not being touched for weeks/months
  // https://github.com/locize/locize-lastused
  .use(LastUsed)
  // locize-editor
  // InContext Editor of locize
  .use(locizePlugin)
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    backend: locizeOptions,
    locizeLastUsed: locizeOptions,
    saveMissing: true
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

Traducci贸n autom谩tica automatizada:

脷ltimo filtro de traducci贸n utilizado :

Editor en contexto :

馃摝 Preparaci贸n para producci贸n 馃殌

Ahora, preparamos la aplicaci贸n para聽pasar a producci贸n.

Primero en locize, crea una versi贸n dedicada para producci贸n. No habilites la publicaci贸n autom谩tica para esa versi贸n, publ铆cala manualmente o a trav茅s de API聽o聽CLI聽.
Por 煤ltimo,聽habilita Cache-Control max-age鈥 para esa versi贸n de producci贸n.

Hagamos uso de la funci贸n de entorno de react-scripts .

Vamos a crear un archivo de entorno predeterminado y uno para desarrollo y otro para producci贸n:

.env:

SKIP_PREFLIGHT_CHECK=true

REACT_APP_VERSION=$npm_package_version

# locize
REACT_APP_LOCIZE_PROJECTID=0bbc223a-9aba-4a90-ab93-ab9d7bf7f780
REACT_APP_LOCIZE_REFLNG=en

.env.development:

REACT_APP_LOCIZE_VERSION=latest
REACT_APP_LOCIZE_APIKEY=aaad4141-54ba-4625-ae37-657538fe29e7

.env.production:

REACT_APP_LOCIZE_VERSION=production

Ahora adaptemos el archivo i18n.js:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import LastUsed from 'locize-lastused';
import { locizePlugin } from 'locize';
import { DateTime } from 'luxon';

const isProduction = process.env.NODE_ENV === 'production';

const locizeOptions = {
  projectId: process.env.REACT_APP_LOCIZE_PROJECTID,
  apiKey: process.env.REACT_APP_LOCIZE_APIKEY, // YOU should not expose your apps API key to production!!!
  referenceLng: process.env.REACT_APP_LOCIZE_REFLNG,
  version: process.env.REACT_APP_LOCIZE_VERSION
};

if (!isProduction) {
  // locize-lastused
  // sets a timestamp of last access on every translation segment on locize
  // -> safely remove the ones not being touched for weeks/months
  // https://github.com/locize/locize-lastused
  i18n.use(LastUsed);
}

i18n
  // locize-editor
  // InContext Editor of locize
  .use(locizePlugin)
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    backend: locizeOptions,
    locizeLastUsed: locizeOptions,
    saveMissing: !isProduction // you should not use saveMissing in production
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

Ahora, durante el desarrollo, continuar谩 guardando las claves que faltan y haciendo uso de la 煤ltima funci贸n utilizada. => npm run start

Y en el entorno de producci贸n, saveMissing y lastused est谩n deshabilitados, y tampoco se expone la clave de API. => npm run build && npm run serve.

Almacenamiento en cach茅 :

Combinando versiones :

馃鈥嶐煉 El c贸digo completo se puede encontrar aqu铆 .

Consulta tambi茅n la parte de integraci贸n del c贸digo en este video de YouTube .

馃帀馃コ Felicidades 馃帄馃巵

Espero que hayas aprendido algunas cosas nuevas sobre i18nextla localizaci贸n de React.js y los flujos de trabajo de localizaci贸n modernos.

Si deseas llevar tu proyecto i18n al siguiente nivel, vale la pena probar locize.

Los fundadores de聽locize聽tambi茅n son los creadores de聽i18next por lo que con el uso de locize, apoyas directamente el futuro de i18next.

Valora este art铆culo