Internacionalización con Gatsby
— 3 minutos de lectura
En este artículo te enseñaré cómo internacionalicé este blog. Si tienes uno con Gatsby puede que esta solución te sea de ayuda.
Todos los ejemplos provienen del código fuente de este proyecto que puedes consultar aquí por si necesitas más detalles.
Necesidades
Si buscas en Google cómo internacionalizar con Gatsby te aparecerán varias soluciones.
A mí personalmente ninguna de ellas me encajaban en este blog, principalmente por varios problemas.
Este blog viene heredado del gatsby-starter-minimal-blog de LekoArts, lo que para extenderlo de tal forma que genere dinámicamente los posts según el idioma, iba a ocasionar algunos dolores de cabeza y por lo pronto no me es necesario.
También debe ser flexible en cuanto a configuración, para indicar qué páginas no queremos que se generen traducciones.
Además debía permitirme internacionalizar los textos a través de un fichero con las traducciones. Esto permitirá a Gatsby generar nuestras páginas por idioma automáticamente.
¿Cuál fue la solución?
Casualmente el plugin más descargado de i18n fue el que me iba a dar la solución, gatsby-plugin-react-i18next.
Sólo que hay que dotarlo con un poco de configuración extra a la que nos encontramos en otros artículos por la web.
Este plugin cubre los casos que necesitaba a la perfección por su flexibilidad, así que pasemos a cómo se configura.
Primero instálalo en tu proyecto.
1npm install --save gatsby-plugin-react-i18next i18next react-i18next
Luego en gatsby-config.js
añade la configuración. Te dejo como ejemplo la de este proyecto:
1{2 resolve: `gatsby-plugin-react-i18next`,3 options: {4 localeJsonSourceName: `locale`, // name given to `gatsby-source-filesystem` plugin.5 languages: [`es`, `en`],6 defaultLanguage: `es`,7 generateDefaultLanguagePage: false,8 redirect: false,9 // if you are using Helmet, you must include siteUrl, and make sure you add http:https10 siteUrl: `http://localhost:8000/`,11 // you can pass any i18next options12 i18nextOptions: {13 interpolation: {14 escapeValue: false // not needed for react as it escapes by default15 },16 keySeparator: false,17 nsSeparator: false18 },19 pages: [20 {21 matchPath: '/:lang?/about',22 getLanguageFromPath: true,23 excludeLanguages: ['es','en']24 },25 {26 matchPath: '/:lang?/blog/:uid',27 getLanguageFromPath: true,28 excludeLanguages: ['en']29 }30 ]31 }32}
Si os fijáis en la clave pages
, aquí se incluyen las rutas que no queremos que se nos genere traducción con el plugin, porque lo haremos de forma manual. Esto lo definimos con la declarativa excludeLanguages
.
Importante también desactivar generateDefaultLanguagePage
, a no ser que quieras tener /es/
en todos tus enlaces (aunque totalmente válido, a mí no me convencía).
En mi caso se puede ver que lo hice para la página about, ya que me era más sencillo hacerlo de forma manual con rutas. Esto puede hacerse de la forma que expongo a continuación.
En gatsby tenemos la estructura de carpetas content/pages/about
, en este caso para nuestro idioma principal (español).
Para la página de about en inglés tendríamos que crear la siguiente estructura: content/pages/en/about
Luego en el index.mdx
donde tenemos el contenido de la página añadimos la declarativa slug
con el contenido "/en/about"
.
Estás dos páginas serán creadas por Gatsby sin ayuda alguna del plugin. Por lo que sí, podemos internacionalizar de forma manual nuestra web.
Siguiendo con la configuración, podemos ver que también excluí los posts, porque en mi caso no voy a traducirlos, simplemente escribiré tanto en español como en inglés diferentes artículos. Por lo que seguiríamos la misma etsrategia que para la página de about.
Traducciones
Entonces, te estarás preguntando... ¿para qué el plugin?
Pues bueno, para traducir los textos fijos de la web y tener un portfolio con varios idiomas. Además de poder cambiar el idioma en tiempo real sin percibir ningún refresco en la web.
El plugin al detectar en nuestra URL que tenemos un lenguaje de la forma /:lang?/
nos permite acceder a la variable locale
(definida previamente en la configuración anterior) a través de las consultas GraphQL de nuestras páginas generadas en la configuración de gatsby-node.js
.
Esta variable almacena qué idioma se usa en esa ruta, por lo que genera el contenido según nuestro fichero de traducciones. Aquí es nuestro trabajo editar nuestros componentes para traducir los textos.
Para ello usamos el siguiente import:
1import {Link, useTranslation} from 'gatsby-plugin-react-i18next'
El componente Link
lo usaremos tal cual usamos el de Gatsby, sólo que este se encarga automáticamente de redirigir al idioma al que te encuentras.
El hook useTranslation
lo usaremos para nuestros textos. Veamos un ejemplo:
1...2
3import {Link, useTranslation} from 'gatsby-plugin-react-i18next'4
5...6
7const Homepage = ({ posts }: PostsProps) => {8 const {t} = useTranslation()9 10 ...11
12 return (13 <Layout>14 15 ...16
17 <section sx={{ mb: [6, 6, 6], p: { fontSize: [1, 2, 3], mt: [2, 2, 2] }, variant: `section_hero` }}>18 <h3 sx={{ fontSize: [5], fontWeight: 700, color: `primary`}}>{t(`Hola! soy Juanan 👋🏽`)}</h3>19 <br/>20 <p sx={{ fontSize: [1], color: `text`}}>{t(`Full-Stack Developer apasionado por crear y aprender sobre tecnología y otros mundos`)}</p>21 <div sx={{ mt: 4, textAlign: `left`}}>22 <Link to={replaceSlashes(`/${basePath}/about`)}>23 <StunninButton>{t(`más sobre mi`)}</StunninButton>24 </Link>25
26 ...27
28 </Layout>29 )30}31
32export default Homepage
Las traducciones las debemos tener localizadas en locales/en/translation.json
(no hace falta una para español, ya que se visualiza el texto que usamos como clave):
1{2 "más sobre mi": "more about me",3
4 ...5
6 "Hola! soy Juanan 👋🏽": "Hi! I'm Juanan 👋🏽",7 "Full-Stack Developer apasionado por crear y aprender sobre tecnología y otros mundos": "Full-Stack Developer passionate for creating and learning about technology and other worlds",8
9 ...10
11}
Nuestras consultas de GraphQL las modificaremos añadiendoles el acceso a la variable locale
de la siguiente forma:
1export const query = graphql`2 query ($formatString: String!, $language: String!) {3 allPost(sort: { fields: date, order: DESC }, limit: 3) {4 nodes {5 slug6 title7 date(formatString: $formatString)8 excerpt9 timeToRead10 description11 tags {12 name13 slug14 }15 }16 }17 locales: allLocale(filter: {language: {eq: $language}}) {18 edges {19 node {20 ns21 data22 language23 }24 }25 }26 }27`
Importante añadir la variable $language
de tipo String
(línea 2).
Localización de fechas
En este proyecto se usa la librería Moment.js para formatear las fechas.
También podemos aprovechar esta librería para formatear la fecha según el idioma con el hook useContext
para suscribirnos al contexto I18nextContext
, que nos permite acceder al idioma que se está usando.
Una vez tengamos nuestro idioma podemos pasárselo a Moment. Te dejo una implementación de ello:
1...2
3import { I18nextContext, useTranslation } from "gatsby-plugin-react-i18next"4
5...6
7const BlogGridItem = ({ post, showTags = true, showDivider }) => {8 const image = getImage(post.banner)9
10 const { language } = React.useContext(I18nextContext)11 12 moment.locale(language)13 const dateFormat = language === 'es' ? "D MMMM, YYYY" : "MMMM D, YYYY"14 const postDate = moment(post.date, "MMMM D, YYYY")15
16 ...
Cambio de idioma
Si te fijas en esta misma web, en la parte superior a la derecha puedes ver que puedes seleccionar el idioma.
Esto se ha hecho con una combinación del hook useI18next
y el contexto I18nextContext
.
useI18next
nos devuelve los lenguajes que tenemos configurados ({languages}
) y la ruta en la que nos situamos ({originalPath}
), así podremos crear un componente Link
del plugin i18n por cada idioma y cada uno con la ruta actual.
1{languages.map((lng) => (2 <li key={lng}>3 <Link to={originalPath} language={lng}>4 {lng}5 </Link>6 </li>7))}
Luego con el contexto, podemos saber en qué idioma nos situamos para poder darle feedback al usuario dejando activo el enlace.
1{languages.map((lng) => (2 <li key={lng}>3 <Link to={originalPath} language={lng} sx={{color: language == lng ? `text` : `inherit`}}>4 {lng}5 </Link>6 </li>7))}
Veamos una implementación completa de esto:
1...2
3import { I18nextContext, Link, useI18next } from "gatsby-plugin-react-i18next"4
5...6
7const Header = () => {8 9 ...10
11 const {languages, originalPath} = useI18next()12 const { language } = React.useContext(I18nextContext)13
14 return (15 <header sx={{ mb: [4, 5] }}>16 <Helmet htmlAttributes={{ displayMode }} />17 <Flex sx={{ alignItems: `center`, justifyContent: `space-between` }}>18 <HeaderTitle />19 <div sx={{ display: `flex` }}>20 <div sx={{ fontSize: [1, `15px`], color: `secondary`, mr: [`28px`, `30px`]}} className="languages">21 {languages.map((lng, i) => (22 <span key={lng}>23 {i > 0 && (<span>|</span>)}24 <Link to={originalPath} language={lng} sx={{color: language == lng ? `text` : `inherit`}}>25 {lng}26 </Link>27 </span>28 ))}29 </div>30 31 ...32
33 </div>34 </Flex>35
36 ...
Conclusiones
Cómo véis es bastante sencillo de usar este plugin para localizar tu proyecto Gatsby, además de flexible permitiéndote solucionar casi cualquier forma de localización que se te ocurra.
Si aceptáis un consejo, leeros siempre la documentación al completo del recurso que queráis usar, ya que cada maestrillo tiene su librillo y puede que la solución que te muestran en un post no encaje en tu proyecto. Pero observando las posibilidades que puede darte ese recurso seguramente encuentres la solución a tu problema.
🌍