Como suportar múltiplos idiomas em um blog Jekyll com Polyglot (1) - Aplicando o plugin Polyglot & implementando tags hreflang alt, sitemap e botão de seleção de idioma
Apresenta o processo de implementação de suporte multilíngue em um blog Jekyll baseado no tema 'jekyll-theme-chirpy' usando o plugin Polyglot. Este post é o primeiro da série, cobrindo a aplicação do plugin Polyglot e a modificação do cabeçalho HTML e sitemap.
Visão geral
Há cerca de 4 meses, no início de julho de 2024, adicionei suporte multilíngue a este blog baseado em Jekyll e hospedado via GitHub Pages, aplicando o plugin Polyglot. Esta série compartilha os bugs encontrados durante o processo de aplicação do plugin Polyglot ao tema Chirpy, seus processos de resolução e métodos para escrever cabeçalhos HTML e sitemap.xml considerando SEO. A série consiste em dois posts, e este que você está lendo é o primeiro da série.
- Parte 1: Aplicando o plugin Polyglot & implementando tags hreflang alt, sitemap e botão de seleção de idioma (este post)
- Parte 2: Solucionando problemas de falha na compilação do tema Chirpy e erros na função de pesquisa
Requisitos
- Deve ser possível fornecer o resultado da compilação (páginas web) separado por caminhos de idioma (ex.
/posts/pt-BR/
,/posts/ja/
). - Para minimizar o tempo e esforço adicionais necessários para o suporte multilíngue, deve ser possível reconhecer automaticamente o idioma com base no caminho local onde o arquivo está localizado (ex.
/_posts/pt-BR/
,/_posts/ja/
) durante a compilação, sem ter que especificar manualmente as tags ‘lang’ e ‘permalink’ no YAML front matter do arquivo markdown original. - O cabeçalho de cada página do site deve incluir meta tags Content-Language apropriadas e tags alternativas hreflang para atender às diretrizes de SEO para pesquisa multilíngue do Google.
- Deve ser possível fornecer links para todas as páginas que suportam cada idioma no site, sem omissões, no
sitemap.xml
, e o própriositemap.xml
deve existir apenas uma vez no caminho raiz, sem duplicações. - Todas as funcionalidades fornecidas pelo tema Chirpy devem funcionar normalmente em cada página de idioma, e se não funcionarem, devem ser modificadas para funcionar corretamente.
- Funcionamento normal das funcionalidades ‘Recently Updated’ e ‘Trending Tags’
- Sem erros no processo de compilação usando GitHub Actions
- Funcionamento normal da função de pesquisa de posts no canto superior direito do blog
Aplicando o plugin Polyglot
Como o Jekyll não suporta nativamente blogs multilíngues, é necessário usar um plugin externo para implementar um blog multilíngue que atenda aos requisitos acima. Após pesquisar, descobri que o Polyglot é amplamente usado para implementação de sites multilíngues e pode satisfazer a maioria dos requisitos acima, então adotei este plugin.
Instalação do plugin
Como estou usando o Bundler, adicionei o seguinte conteúdo ao Gemfile
:
1
2
3
group :jekyll_plugins do
gem "jekyll-polyglot"
end
Depois, executar bundle update
no terminal completará automaticamente a instalação.
Se você não estiver usando o Bundler, você pode instalar a gem diretamente com o comando gem install jekyll-polyglot
no terminal e então adicionar o plugin ao _config.yml
da seguinte forma:
1
2
plugins:
- jekyll-polyglot
Configuração
Em seguida, abra o arquivo _config.yml
e adicione o seguinte conteúdo:
1
2
3
4
5
6
# Polyglot Settings
languages: ["en", "ko", "ja", "zh-TW", "es", "pt-BR", "fr", "de"]
default_lang: "en"
exclude_from_localization: ["javascript", "images", "css", "public", "assets", "sitemap"]
parallel_localization: false
lang_from_path: true
- languages: Lista de idiomas que você deseja suportar
- default_lang: Idioma padrão de fallback
- exclude_from_localization: Especifica expressões regulares de strings de caminho de arquivo/pasta raiz a serem excluídas da localização
- parallel_localization: Valor booleano que especifica se deve paralelizar o processamento multilíngue durante a compilação
- lang_from_path: Valor booleano, se definido como ‘true’, reconhece e usa automaticamente o código de idioma se a string do caminho do arquivo markdown contiver o código de idioma, mesmo que o atributo ‘lang’ não seja especificado explicitamente no YAML front matter dentro do arquivo markdown
A documentação oficial do protocolo Sitemap afirma o seguinte:
“A localização de um arquivo Sitemap determina o conjunto de URLs que podem ser incluídos nesse Sitemap. Um arquivo Sitemap localizado em http://example.com/catalog/sitemap.xml pode incluir quaisquer URLs começando com http://example.com/catalog/ mas não pode incluir URLs começando com http://example.com/images/.”
“É fortemente recomendado que você coloque seu Sitemap no diretório raiz do seu servidor web.”
Para cumprir isso, deve-se adicionar ‘sitemap.xml’ à lista ‘exclude_from_localization’ para garantir que apenas um arquivo
sitemap.xml
exista no diretório raiz, com o mesmo conteúdo, e não seja criado para cada idioma, evitando o exemplo incorreto abaixo.Exemplo incorreto (o conteúdo de cada arquivo é idêntico, não diferindo por idioma):
/sitemap.xml
/ko/sitemap.xml
/es/sitemap.xml
/pt-BR/sitemap.xml
/ja/sitemap.xml
/fr/sitemap.xml
/de/sitemap.xml
(Atualizado em 14.01.2025) Com a aceitação do Pull Request que submeti reforçando o conteúdo mencionado acima no README, agora a mesma orientação pode ser encontrada na documentação oficial do Polyglot.
Definir ‘parallel_localization’ como ‘true’ tem a vantagem de reduzir significativamente o tempo de compilação, mas em julho de 2024, quando essa funcionalidade foi ativada para este blog, havia um bug onde os títulos dos links nas seções ‘Recently Updated’ e ‘Trending Tags’ na barra lateral direita da página não eram processados corretamente e se misturavam com outros idiomas. Parece que ainda não está totalmente estabilizado, então é necessário testar previamente se funciona normalmente antes de aplicar ao site. Além disso, a funcionalidade também não é suportada ao usar Windows, então deve ser desativada.
Além disso, no Jekyll 4.0, é necessário desativar a geração de CSS sourcemaps da seguinte forma:
1
2
sass:
sourcemap: never # No Jekyll 4.0, os mapas de origem SCSS serão gerados incorretamente devido à forma como o Polyglot opera
Pontos a considerar ao escrever posts
Ao escrever posts multilíngues, deve-se considerar os seguintes pontos:
- Especificação apropriada do código de idioma: Deve-se especificar o código de idioma ISO apropriado usando o caminho do arquivo (ex.
/_posts/pt-BR/example-post.md
) ou o atributo ‘lang’ no YAML front matter (ex.lang: pt-BR
). Consulte os exemplos na documentação do desenvolvedor do Chrome.
No entanto, embora a documentação do desenvolvedor do Chrome mostre códigos de região no formato ‘pt_BR’, na prática, deve-se usar ‘-‘ em vez de ‘_’, como em ‘pt-BR’, para que funcione corretamente ao adicionar tags alternativas hreflang ao cabeçalho HTML posteriormente.
- O caminho e o nome do arquivo devem ser consistentes.
Para mais detalhes, consulte o README do repositório GitHub untra/polyglot.
Modificando o cabeçalho HTML e o sitemap
Agora, para SEO, precisamos inserir meta tags Content-Language e tags alternativas hreflang no cabeçalho HTML de cada página do blog.
Cabeçalho HTML
Na versão 1.8.1, a mais recente em novembro de 2024, o Polyglot tem uma funcionalidade que realiza automaticamente as tarefas acima quando a tag Liquid {% I18n_Headers %}
é chamada na seção de cabeçalho da página. No entanto, isso assume que a tag de atributo ‘permalink’ foi especificada explicitamente para aquela página, e não funcionará corretamente se não for o caso.
Portanto, peguei o head.html do tema Chirpy e adicionei diretamente o seguinte conteúdo: Trabalhei com referência à página SEO Recipes do blog oficial do Polyglot, mas modifiquei para usar o atributo page.url
em vez de page.permalink
quando page.permalink
não está disponível.
1
2
3
4
5
6
<meta http-equiv="Content-Language" content="{{site.active_lang}}">
{% if site.default_lang %}<link rel="alternate" hreflang="{{site.default_lang}}" href="{{site.url}}{{page.url}}" />{% endif %}
{% for lang in site.languages %}{% if lang == site.default_lang %}{% continue %}{% endif %}
<link rel="alternate" hreflang="{{lang}}" href="{{site.url}}/{{lang}}{{page.url}}" />
{% endfor %}
Sitemap
Como o sitemap gerado automaticamente pelo Jekyll durante a compilação não suporta corretamente páginas multilíngues, crie um arquivo sitemap.xml
no diretório raiz e insira o seguinte conteúdo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
---
layout: content
---
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
{% for lang in site.languages %}
{% for node in site.pages %}
{% comment %}<!-- verificação muito preguiçosa para ver se a página está na lista de exclusão - isso significa que as páginas excluídas não estarão no sitemap de forma alguma, escreva exceções conforme necessário -->{% endcomment %}
{% unless site.exclude_from_localization contains node.path %}
{% comment %}<!-- assumindo que se não houver layout atribuído, então não inclua a página no sitemap, você pode querer mudar isso -->{% endcomment %}
{% if node.layout %}
<url>
<loc>{% if lang == site.default_lang %}{{ node.url | absolute_url }}{% else %}{{ node.url | prepend: lang | prepend: '/' | absolute_url }}{% endif %}</loc>
{% if node.last_modified_at and node.last_modified_at != node.date %}<lastmod>{{ node.last_modified_at | date: '%Y-%m-%dT%H:%M:%S%:z' }}</lastmod>{% elsif node.date %}<lastmod>{{ node.date | date: '%Y-%m-%dT%H:%M:%S%:z' }}</lastmod>{% endif %}
</url>
{% endif %}
{% endunless %}
{% endfor %}
{% comment %}<!-- Isso percorre todas as coleções do site, incluindo posts -->{% endcomment %}
{% for collection in site.collections %}
{% for node in site[collection.label] %}
<url>
<loc>{% if lang == site.default_lang %}{{ node.url | absolute_url }}{% else %}{{ node.url | prepend: lang | prepend: '/' | absolute_url }}{% endif %}</loc>
{% if node.last_modified_at and node.last_modified_at != node.date %}<lastmod>{{ node.last_modified_at | date: '%Y-%m-%dT%H:%M:%S%:z' }}</lastmod>{% elsif node.date %}<lastmod>{{ node.date | date: '%Y-%m-%dT%H:%M:%S%:z' }}</lastmod>{% endif %}
</url>
{% endfor %}
{% endfor %}
{% endfor %}
</urlset>
Adicionando botão de seleção de idioma na barra lateral
(Atualizado em 05.02.2025) Melhorei o botão de seleção de idioma para um formato de lista suspensa.
Criei o arquivo _includes/lang-selector.html
e inseri o seguinte conteúdo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<link rel="stylesheet" href="{{ '/assets/css/lang-selector.css' | relative_url }}">
<div class="lang-dropdown">
<select class="lang-select" onchange="changeLang(this.value)" aria-label="Selecionar Idioma">
{%- for lang in site.languages -%}
<option value="{% if lang == site.default_lang %}{{ page.url }}{% else %}/{{ lang }}{{ page.url }}{% endif %}"
{% if lang == site.active_lang %}selected{% endif %}>
{% case lang %}
{% when 'ko' %}🇰🇷 한국어
{% when 'en' %}🇺🇸 English
{% when 'ja' %}🇯🇵 日本語
{% when 'zh-TW' %}🇹🇼 正體中文
{% when 'es' %}🇪🇸 Español
{% when 'pt-BR' %}🇧🇷 Português
{% when 'fr' %}🇫🇷 Français
{% when 'de' %}🇩🇪 Deutsch
{% else %}{{ lang }}
{% endcase %}
</option>
{%- endfor -%}
</select>
</div>
<script>
function changeLang(url) {
window.location.href = url;
}
</script>
Além disso, criei o arquivo assets/css/lang-selector.css
e inseri o seguinte conteúdo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
* Estilo do seletor de idioma
*
* Define o estilo do dropdown de seleção de idioma localizado na barra lateral.
* Suporta o modo escuro do tema e é otimizado para ambientes móveis.
*/
/* Contêiner do seletor de idioma */
.lang-selector-wrapper {
padding: 0.35rem;
margin: 0.15rem 0;
text-align: center;
}
/* Contêiner do dropdown */
.lang-dropdown {
position: relative;
display: inline-block;
width: auto;
min-width: 120px;
max-width: 80%;
}
/* Elemento de entrada de seleção */
.lang-select {
/* Estilo básico */
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
width: 100%;
padding: 0.5rem 2rem 0.5rem 1rem;
/* Fonte e cor */
font-family: Lato, "Pretendard JP Variable", "Pretendard Variable", sans-serif;
font-size: 0.95rem;
color: var(--sidebar-muted);
background-color: var(--sidebar-bg);
/* Forma e interação */
border-radius: var(--bs-border-radius, 0.375rem);
cursor: pointer;
transition: all 0.2s ease;
/* Adição do ícone de seta */
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 1rem;
}
/* Estilo dos emojis de bandeira */
.lang-select option {
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif;
padding: 0.35rem;
font-size: 1rem;
}
.lang-flag {
display: inline-block;
margin-right: 0.5rem;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif;
}
/* Estado de hover */
.lang-select:hover {
color: var(--sidebar-active);
background-color: var(--sidebar-hover);
}
/* Estado de foco */
.lang-select:focus {
outline: 2px solid var(--sidebar-active);
outline-offset: 2px;
color: var(--sidebar-active);
}
/* Suporte para o navegador Firefox */
.lang-select:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 var(--sidebar-muted);
}
/* Suporte para o navegador IE */
.lang-select::-ms-expand {
display: none;
}
/* Suporte para modo escuro */
[data-mode="dark"] .lang-select {
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
}
/* Otimização para ambiente móvel */
@media (max-width: 768px) {
.lang-select {
padding: 0.75rem 2rem 0.75rem 1rem; /* Área de toque maior */
}
.lang-dropdown {
min-width: 140px; /* Área de seleção mais ampla em dispositivos móveis */
}
}
Em seguida, adicionei as seguintes três linhas logo antes da classe “sidebar-bottom” no _includes/sidebar.html
do tema Chirpy para que o Jekyll carregue o conteúdo do _includes/lang-selector.html
criado anteriormente durante a compilação da página:
1
2
3
4
5
6
7
(início)...
<div class="lang-selector-wrapper w-100">
{%- include lang-selector.html -%}
</div>
<div class="sidebar-bottom d-flex flex-wrap align-items-center w-100">
...(fim)
Leitura Adicional
Continua na Parte 2