dois:pontos

Criando um site multilíngue com Gatsby

- 7 min

Depois de converter um site de Jekyll para o Gatsby, uma coisa ficou faltando: como eu faço para deixar o site bilíngue? No Jekyll eu já sabia como fazer, mas não no Gatsby. Procurei em diversos sites alguma dica de como fazer isto, porém em sua grande maioria eram tutoriais de integração com algum CMS ou serviços externos. Minha necessidade era apenas a mais básica, um site simples com conteúdo feito em arquivos Markdown.

Não achei nenhum tutorial que tenha atendido exatamente ao que precisava, então tive de quebrar a cabeça sozinho para encontrar uma solução. Felizmente, deu certo e este site é prova disto. Abaixo descrevo o processo que usei para alcançar este objetivo.

Instalação do plugin

Para adicionar o suporte para outros idiomas no site, instalei o plugin gatsby-plugin-intl. Existem outras extensões para atingir o mesmo objetivo, mas esta foi o que melhor me atendeu.

Para instalar com o Yarn basta usar este comando no terminal:

yarn add gatsby-plugin-intl

Caso esteja usando NPM, use este outro.

npm install gatsby-plugin-intl

Pronto. A instalação está completa.

Configuração

No Gatsby, após instalar um plugin, faz-se uma configuração para incluí-lo no processamento. Basta incluir o nome do plugin, junto com a suas opções dentro da lista de plugins, no arquivo gatsby-config.js. O meu ficou configurado da seguinte maneira:

module.exports = {
  plugins: [
    /* CONFIGURAÇÃO DO PLUGIN */
    {
      resolve: `gatsby-plugin-intl`,
      options: {
        // Diretório com os JSON com as palavras a serem traduzidas
        path: `${__dirname}/src/intl`,
        // Idiomas suportados
        languages: [`pt`, `en`],
        // Idioma padrão
        defaultLanguage: `pt`,
        // Redireciona para `/pt` ao entrar na rota `/`
        redirect: false,
      },
    },
    /* FIM DA CONFIGURAÇÃO */
  ],
}

Uma breve explicação das opções acima:

  • resolve: nome do plugin do Gatsby
  • options: lista com opções para configuração
  • path: caminho para o diretório onde ficarão os arquivos JSON com a tradução. A palavra-chave __dirname substitui a necessidade de colocar o endereço absoluto da pasta.
  • languages: lista com as abreviações ISO do idioma desejado para o site. Exemplo: pl para polonês e de para alemão. No meu caso, usei apenas português e inglês.
  • defaultLanguage: idioma padrão do site. Português no meu caso.
  • redirect: adiciona /pt na URL do site com idioma padrão. Eu deixei falso para o meu site, para não afetar os endereços existentes.

Termos para tradução

Além da configuração, é preciso ter um arquivo com os termos a serem traduzidos no site. Nomes de links, títulos de páginas estáticas e dicas de ferramenta são boas aplicações.

{
  "about": "Sobre",
  "comments": "Comentários",
  "home": "Início"
}

No exemplo acima, usei uma lista, com um termo e sua tradução equivalente. A estrutura deve ser a mesma para todos os idiomas que você quiser acrescentar no seu site, apenas mudando a tradução, claro.

O nome do arquivo deve seguir o padrão [sigla-do-idioma].json, dentro do diretório mencionado na configuração.

Exemplo: src/intl/en.json, src/intl/pt.json, etc.

Aplicando as traduções nos arquivos

Depois disto feito, vem a parte de traduzir as páginas e componentes. Para isto é só seguir os passos:

Importar o gancho useIntl do plugin instalado:

import React from "react"
// Importação do gancho
import { useIntl } from "gatsby-plugin-intl"

export default function Index() {
  // Tornando o useIntl disponível para uso
  const intl = useIntl()
  // Usando a sigla para facilitar o uso de rotas
  const locale = intl.locale !== "pt" ? `/${intl.locale}` : ""

Para a tradução propriamente, substitui-se a palavra a ser traduzida pelo método formatMessage.

  /* Antes */
  <Link activeClassName="active" to="/">
    Início
  </Link>
  /* Depois */
  <Link activeClassName="active" to={`${locale}/`}>
    {intl.formatMessage({ id: "home" })}
  </Link>

Para datas, usa-se o componente <FormattedDate />.

<FormattedDate value={new Date(datadoPost)} month="long" day="numeric" />

A documentação para as opções disponíveis para o componente podem ser encontradas aqui. O texto está em inglês.

Listagem de artigos em markdown

Nem só de traduções de palavras vive um site bilíngue, mas principalmente do conteúdo. No exemplo citado neste artigo, este vem de arquivos Markdown no diretório /posts. Nada muito diferente do normal foi feito no arquivo gatsby-node.js.

const path = require("path")

exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions
  const blogPostTemplate = path.resolve("src/templates/blog-post.js")
  const search = await graphql(`
    query {
      allMarkdownRemark(
        sort: { order: DESC, fields: frontmatter___date }
        limit: 1000
      ) {
        edges {
          node {
            frontmatter {
              slug
              lang
            }
          }
        }
      }
    }
  `)

  if (search.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }

  // Contexto e modelo de página para o conteúdo
  search.data.allMarkdownRemark.edges.forEach(({ node }) => {
    const language = node.frontmatter.lang
    const locale = language !== "pt" ? `/${language}` : ""
    createPage({
      path: `/post${node.frontmatter.slug}`,
      component: blogPostTemplate,
      context: {
        slug: node.frontmatter.slug,
        lang: language,
      },
    })
  })

  // Paginação da listagem de artigos
  const posts = search.data.allMarkdownRemark.edges
  const postsPerPage = 20
  const numPages = Math.ceil(posts.length / postsPerPage)
  Array.from({ length: numPages }).forEach((_, i) => {
    createPage({
      path: i === 0 ? `/articles` : `/articles/${i + 1}`,
      component: path.resolve("./src/templates/articles.js"),
      context: {
        limit: postsPerPage,
        skip: i * postsPerPage,
        numPages,
        currentPage: i + 1,
      },
    })
  })
}

Este arquivo é responsável por ler os arquivos *.md e transformá-los em páginas HTML.

Primeiro é feita uma consulta no GraphQL para localizar os dados dos arquivos markdown. Depois, o arquivo de modelo para a página para o artigo e seu contexto é associado. O contexto é o que informa ao Gatsby qual arquivo deve ser mostrado, ao acessar um link.

Por último, a paginação da listagem de artigos, com 10 ítens por página. O número vinte aparece ali porque são dez posts para cada idioma, como o site possui 2, deixei o postsPerPage como 20. Sei que não é a saída mais elegante, mas é a que funcionou para mim. Se eu encontrar uma melhor, eu atualizo este artigo e o repositório com ela.

Conteúdo em markdown para os idiomas

O frontmatter, espécie de cabeçalho para os arquivos de conteúdo, tem a estrutura abaixo:

---
lang: pt
title: "Lorem ipsum"
slug: "/lorem-ipsum"
date: 2020-07-11
categories: lorem
thumbnail: https://lorempixel.com/1500/900
---

## Lorem

Lorem ipsum dolor sit amet consectetuer adispiscing elit.

Nada de especial, a não ser a identificação do idioma, para a filtragem posterior. Basta colocá-los na pasta informada para receber os arquivos no gatsby-node.js. Um cuidado que tive foi separá-los em subdiretórios para cada idioma.

Listando o conteúdo

Para listar os artigos, primeiro fiz uma consulta no GraphQL para trazer todos os artigos, conforme as especificações dadas no arquivo gatsby-node.js na função de criação de páginas createPages.

export const articlesQuery = graphql`
  query articlesQuery($skip: Int!, $limit: Int!) {
    allMarkdownRemark(
      sort: { fields: frontmatter___date, order: DESC }
      limit: $limit
      skip: $skip
    ) {
      edges {
        node {
          id
          excerpt
          frontmatter {
            date
            slug
            title
            lang
          }
        }
      }
    }
  }
`

Após isto, usa-se o resultado da busca na página.

import React from "react"
import { graphql, Link } from "gatsby"
import { useIntl } from "gatsby-plugin-intl"

export default function Articles(props) {
  // Internacionalização
  const intl = useIntl()
  const locale = intl.locale !== "pt" ? `/${intl.locale}` : ""

  // Dados puros da consulta
  const posts = props.data.allMarkdownRemark.edges

  // Filtragem do resultado usando a localização da página
  const filteredPosts = posts.filter((edge) =>
    edge.node.frontmatter.lang.includes(intl.locale)
  )

Para mais detalhes deste arquivo, basta consultar o repositório que fiz de exemplo no Github. O link está no final do artigo.

Alternando entre idiomas

Nada de especial aqui também:

import React from "react"
import { Link } from "gatsby"

export default function LanguageSelector({ label, className }) {
  const labelText = label || "Languages"
  const selectorClass = className || "language-selector"

  return (
    <div className={selectorClass} data-label={labelText}>
      <ul>
        <li>
          <Link to="/en">En</Link>
        </li>
        <li>
          <Link to="/">Pt</Link>
        </li>
      </ul>
    </div>
  )
}

Como o plugin de internacionalização funciona com base nas rotas, bastou fazer um link para a rota do idioma desejado. Fiz assim para evitar erros 404 ao trocar o idioma na página interna do artigo, dado que as URLs das versões em inglês e português são diferentes.

Conclusão

Pode não ser a melhor estratégia para criação de sites multilíngues, mas esta foi a que funcionou para mim. Como disse no começo deste artigo, foi mais difícil do que pensei achar alguma ajuda neste tema. Talvez por já ser tão comum para alguns, esquecem que tem gente começando que ainda não tem ideia de como fazer.

Deixei um link para o repositório do projeto no Github para consulta, caso ainda tenha mais alguma dúvida. Quaisquer sugestões ou comentários fique à vontade!

Links

Comentários