Comment prendre en charge plusieurs langues sur un blog Jekyll avec Polyglot (2) - Résolution des problèmes de compilation du thème Chirpy et des erreurs de recherche
Présentation du processus d'implémentation du support multilingue en appliquant le plugin Polyglot à un blog Jekyll basé sur le thème 'jekyll-theme-chirpy'. Ce billet est le deuxième de la série, traitant de l'identification et de la résolution des erreurs survenues lors de l'application de Polyglot au thème Chirpy.
Aperçu
Il y a environ 4 mois, début juillet 12024 du calendrier holocène, j’ai ajouté le support multilingue à ce blog hébergé sur GitHub Pages avec Jekyll en appliquant le plugin Polyglot. Cette série partage les bugs rencontrés lors de l’application du plugin Polyglot au thème Chirpy, leur processus de résolution, ainsi que la méthode de rédaction des en-têtes HTML et du sitemap.xml en tenant compte du référencement. La série se compose de deux articles, et celui que vous lisez est le deuxième.
- Partie 1 : Application du plugin Polyglot & implémentation des balises hreflang alt, sitemap et bouton de sélection de langue
- Partie 2 : Résolution des problèmes de compilation du thème Chirpy et des erreurs de recherche (cet article)
Exigences
- Le résultat de la compilation (pages web) doit être fourni avec des chemins distincts par langue (ex.
/posts/ko/
,/posts/ja/
). - Pour minimiser le temps et l’effort supplémentaires nécessaires au support multilingue, le système doit reconnaître automatiquement la langue en fonction du chemin local où se trouve le fichier markdown original (ex.
/_posts/ko/
,/_posts/ja/
), sans avoir à spécifier manuellement les balises ‘lang’ et ‘permalink’ dans le YAML front matter. - L’en-tête de chaque page du site doit inclure les balises méta Content-Language appropriées et les balises alternatives hreflang pour répondre aux directives de référencement de Google pour la recherche multilingue.
- Le
sitemap.xml
doit fournir tous les liens vers les pages prenant en charge chaque langue sans omission, et lesitemap.xml
lui-même doit exister uniquement dans le chemin racine, sans duplication. - Toutes les fonctionnalités fournies par le thème Chirpy doivent fonctionner normalement sur chaque page de langue, sinon elles doivent être modifiées pour fonctionner correctement.
- Fonctionnement normal des fonctionnalités ‘Recently Updated’, ‘Trending Tags’
- Absence d’erreurs lors du processus de compilation avec GitHub Actions
- Fonctionnement normal de la fonction de recherche de posts en haut à droite du blog
Avant de commencer
Cet article fait suite à la première partie, donc si vous ne l’avez pas encore lue, je vous recommande de la lire d’abord.
Résolution de problèmes (‘relative_url_regex’: target of repeat operator is not specified)
Après avoir terminé les étapes précédentes, j’ai exécuté la commande bundle exec jekyll serve
pour tester la compilation, mais elle a échoué avec l’erreur 'relative_url_regex': target of repeat operator is not specified
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...(début omis)
------------------------------------------------
Jekyll 4.3.4 Please append `--trace` to the `serve` command
for any additional information or backtrace.
------------------------------------------------
/Users/yunseo/.gem/ruby/3.2.2/gems/jekyll-polyglot-1.8.1/lib/jekyll/polyglot/
patches/jekyll/site.rb:234:in `relative_url_regex': target of repeat operator
is not specified: /href="?\/((?:(?!*.gem)(?!*.gemspec)(?!tools)(?!README.md)(
?!LICENSE)(?!*.config.js)(?!rollup.config.js)(?!package*.json)(?!.sass-cache)
(?!.jekyll-cache)(?!gemfiles)(?!Gemfile)(?!Gemfile.lock)(?!node_modules)(?!ve
ndor\/bundle\/)(?!vendor\/cache\/)(?!vendor\/gems\/)(?!vendor\/ruby\/)(?!en\/
)(?!ko\/)(?!es\/)(?!pt-BR\/)(?!ja\/)(?!fr\/)(?!de\/)[^,'"\s\/?.]+\.?)*(?:\/[^
\]\[)("'\s]*)?)"/ (RegexpError)
...(fin omise)
Après avoir recherché si un problème similaire avait été signalé, j’ai trouvé exactement le même problème déjà enregistré dans le dépôt Polyglot, avec une solution.
Le fichier _config.yml
du thème Chirpy que j’utilise contient la syntaxe suivante :
1
2
3
4
5
6
7
8
9
exclude:
- "*.gem"
- "*.gemspec"
- docs
- tools
- README.md
- LICENSE
- "*.config.js"
- package*.json
Le problème vient des expressions régulières dans les deux fonctions du fichier site.rb
de Polyglot qui ne traitent pas correctement les modèles de globbing contenant des caractères génériques comme "*.gem"
, "*.gemspec"
, "*.config.js"
.
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
# a regex that matches relative urls in a html document
# matches href="baseurl/foo/bar-baz" href="/fr/foo/bar-baz" and others like it
# avoids matching excluded files. prepare makes sure
# that all @exclude dirs have a trailing slash.
def relative_url_regex(disabled = false)
regex = ''
unless disabled
@exclude.each do |x|
regex += "(?!#{x})"
end
@languages.each do |x|
regex += "(?!#{x}\/)"
end
end
start = disabled ? 'ferh' : 'href'
%r{#{start}="?#{@baseurl}/((?:#{regex}[^,'"\s/?.]+\.?)*(?:/[^\]\[)("'\s]*)?)"}
end
# a regex that matches absolute urls in a html document
# matches href="http://baseurl/foo/bar-baz" and others like it
# avoids matching excluded files. prepare makes sure
# that all @exclude dirs have a trailing slash.
def absolute_url_regex(url, disabled = false)
regex = ''
unless disabled
@exclude.each do |x|
regex += "(?!#{x})"
end
@languages.each do |x|
regex += "(?!#{x}\/)"
end
end
start = disabled ? 'ferh' : 'href'
%r{(?<!hreflang="#{@default_lang}" )#{start}="?#{url}#{@baseurl}/((?:#{regex}[^,'"\s/?.]+\.?)*(?:/[^\]\[)("'\s]*)?)"}
end
Il existe deux façons de résoudre ce problème.
1. Forker Polyglot et modifier les parties problématiques
Au moment de la rédaction de cet article (11.12024), la documentation officielle de Jekyll indique que le paramètre exclude
prend en charge les modèles de globbing pour les noms de fichiers.
“This configuration option supports Ruby’s File.fnmatch filename globbing patterns to match multiple entries to exclude.”
Le problème ne vient donc pas du thème Chirpy mais des fonctions relative_url_regex()
et absolute_url_regex()
de Polyglot, donc la solution fondamentale est de les modifier pour éviter ce problème.
Comme ce bug n’est pas encore résolu dans Polyglot, vous pouvez forker le dépôt Polyglot en vous référant à ce billet de blog et à la réponse dans l’issue GitHub mentionnée, puis modifier les parties problématiques comme suit et utiliser cette version au lieu de Polyglot original.
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
def relative_url_regex(disabled = false)
regex = ''
unless disabled
@exclude.each do |x|
escaped_x = Regexp.escape(x)
regex += "(?!#{escaped_x})"
end
@languages.each do |x|
escaped_x = Regexp.escape(x)
regex += "(?!#{escaped_x}\/)"
end
end
start = disabled ? 'ferh' : 'href'
%r{#{start}="?#{@baseurl}/((?:#{regex}[^,'"\s/?.]+\.?)*(?:/[^\]\[)("'\s]*)?)"}
end
def absolute_url_regex(url, disabled = false)
regex = ''
unless disabled
@exclude.each do |x|
escaped_x = Regexp.escape(x)
regex += "(?!#{escaped_x})"
end
@languages.each do |x|
escaped_x = Regexp.escape(x)
regex += "(?!#{escaped_x}\/)"
end
end
start = disabled ? 'ferh' : 'href'
%r{(?<!hreflang="#{@default_lang}" )#{start}="?#{url}#{@baseurl}/((?:#{regex}[^,'"\s/?.]+\.?)*(?:/[^\]\[)("'\s]*)?)"}
end
2. Remplacer les modèles de globbing par des noms de fichiers exacts dans le fichier ‘_config.yml’ du thème Chirpy
La méthode idéale serait que ce correctif soit intégré au flux principal de Polyglot. Mais en attendant, il faudrait utiliser une version forkée, ce qui peut être fastidieux car il faut suivre les mises à jour de Polyglot. J’ai donc opté pour une autre approche.
En examinant les fichiers à la racine du dépôt du thème Chirpy, on constate que les modèles "*.gem"
, "*.gemspec"
, "*.config.js"
ne correspondent qu’à 3 fichiers :
jekyll-theme-chirpy.gemspec
purgecss.config.js
rollup.config.js
On peut donc supprimer les modèles de globbing dans la section exclude
du fichier _config.yml
et les remplacer comme suit pour que Polyglot puisse les traiter sans problème.
1
2
3
4
5
6
7
8
9
exclude: # Modifié en référence à l'issue https://github.com/untra/polyglot/issues/204
# - "*.gem"
- jekyll-theme-chirpy.gemspec # - "*.gemspec"
- tools
- README.md
- LICENSE
- purgecss.config.js # - "*.config.js"
- rollup.config.js
- package*.json
Modification de la fonction de recherche
Après avoir terminé les étapes précédentes, presque toutes les fonctionnalités du site fonctionnaient comme prévu. Cependant, j’ai découvert que la barre de recherche située en haut à droite de la page avec le thème Chirpy n’indexait pas les pages dans des langues autres que site.default_lang
(l’anglais dans le cas de ce blog), et affichait des pages en anglais même lors de recherches dans d’autres langues.
Pour comprendre la cause, examinons les fichiers impliqués dans la fonction de recherche et où le problème se produit.
‘_layouts/default.html’
En examinant le fichier _layouts/default.html
qui structure toutes les pages du blog, on constate qu’il charge le contenu de search-results.html
et search-loader.html
dans l’élément <body>
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
{% include sidebar.html lang=lang %}
<div id="main-wrapper" class="d-flex justify-content-center">
<div class="container d-flex flex-column px-xxl-5">
(...omis...)
{% include_cached search-results.html lang=lang %}
</div>
<aside aria-label="Scroll to Top">
<button id="back-to-top" type="button" class="btn btn-lg btn-box-shadow">
<i class="fas fa-angle-up"></i>
</button>
</aside>
</div>
(...omis...)
{% include_cached search-loader.html lang=lang %}
</body>
‘_includes/search-result.html’
_includes/search-result.html
crée le conteneur search-results
pour stocker les résultats de recherche.
1
2
3
4
5
6
7
8
9
10
<!-- The Search results -->
<div id="search-result-wrapper" class="d-flex justify-content-center d-none">
<div class="col-11 content">
<div id="search-hints">
{% include_cached trending-tags.html %}
</div>
<div id="search-results" class="d-flex flex-wrap justify-content-center text-muted mt-3"></div>
</div>
</div>
‘_includes/search-loader.html’
_includes/search-loader.html
est la partie essentielle qui implémente la recherche basée sur la bibliothèque Simple-Jekyll-Search. Elle exécute un JavaScript côté client qui trouve les correspondances avec les mots-clés saisis dans le fichier d’index search.json
et renvoie les liens des articles correspondants sous forme d’éléments <article>
.
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
{% capture result_elem %}
<article class="px-1 px-sm-2 px-lg-4 px-xl-0">
<header>
<h2><a href="{url}">{title}</a></h2>
<div class="post-meta d-flex flex-column flex-sm-row text-muted mt-1 mb-1">
{categories}
{tags}
</div>
</header>
<p>{snippet}</p>
</article>
{% endcapture %}
{% capture not_found %}<p class="mt-5">{{ site.data.locales[include.lang].search.no_results }}</p>{% endcapture %}
<script>
{% comment %} Note: dependent library will be loaded in `js-selector.html` {% endcomment %}
document.addEventListener('DOMContentLoaded', () => {
SimpleJekyllSearch({
searchInput: document.getElementById('search-input'),
resultsContainer: document.getElementById('search-results'),
json: '{{ '/assets/js/data/search.json' | relative_url }}',
searchResultTemplate: '{{ result_elem | strip_newlines }}',
noResultsText: '{{ not_found }}',
templateMiddleware: function(prop, value, template) {
if (prop === 'categories') {
if (value === '') {
return `${value}`;
} else {
return `<div class="me-sm-4"><i class="far fa-folder fa-fw"></i>${value}</div>`;
}
}
if (prop === 'tags') {
if (value === '') {
return `${value}`;
} else {
return `<div><i class="fa fa-tag fa-fw"></i>${value}</div>`;
}
}
}
});
});
</script>
‘/assets/js/data/search.json’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
---
layout: compress
swcache: true
---
[
{% for post in site.posts %}
{
"title": {{ post.title | jsonify }},
"url": {{ post.url | relative_url | jsonify }},
"categories": {{ post.categories | join: ', ' | jsonify }},
"tags": {{ post.tags | join: ', ' | jsonify }},
"date": "{{ post.date }}",
{% include no-linenos.html content=post.content %}
{% assign _content = content | strip_html | strip_newlines %}
"snippet": {{ _content | truncate: 200 | jsonify }},
"content": {{ _content | jsonify }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
Ce fichier utilise la syntaxe Liquid de Jekyll pour définir un fichier JSON contenant le titre, l’URL, les catégories et tags, la date de création, un extrait des 200 premiers caractères du contenu, et le contenu complet de tous les articles du site.
Structure de fonctionnement de la recherche et identification du problème
En résumé, la fonction de recherche sur GitHub Pages avec le thème Chirpy fonctionne selon le processus suivant :
stateDiagram
state "Changes" as CH
state "Build start" as BLD
state "Create search.json" as IDX
state "Static Website" as DEP
state "In Test" as TST
state "Search Loader" as SCH
state "Results" as R
[*] --> CH: Make Changes
CH --> BLD: Commit & Push origin
BLD --> IDX: jekyll build
IDX --> TST: Build Complete
TST --> CH: Error Detected
TST --> DEP: Deploy
DEP --> SCH: Search Input
SCH --> R: Return Results
R --> [*]
J’ai constaté que Polyglot génère search.json
pour chaque langue comme suit :
/assets/js/data/search.json
/ko/assets/js/data/search.json
/es/assets/js/data/search.json
/pt-BR/assets/js/data/search.json
/ja/assets/js/data/search.json
/fr/assets/js/data/search.json
/de/assets/js/data/search.json
Le problème se situe donc dans le “Search Loader”. Les pages dans des langues autres que l’anglais ne sont pas trouvées car _includes/search-loader.html
charge statiquement uniquement le fichier d’index anglais (/assets/js/data/search.json
), quelle que soit la langue de la page visitée.
- Contrairement aux fichiers markdown ou html, pour les fichiers JSON, le wrapper Polyglot fonctionne pour les variables Jekyll comme
post.title
,post.content
, mais la fonctionnalité Relativized Local Urls ne semble pas fonctionner.- De même, dans les templates de fichiers JSON, on ne peut pas accéder aux balises liquid fournies par Polyglot
{{ site.default_lang }}
,{{ site.active_lang }}
en plus des variables standard de Jekyll.Par conséquent, les valeurs comme
title
,snippet
,content
dans le fichier d’index sont générées différemment selon la langue, mais la valeururl
renvoie le chemin de base sans tenir compte de la langue, et un traitement approprié doit être ajouté dans la partie “Search Loader”.
Résolution du problème
Pour résoudre ce problème, il faut modifier le contenu de _includes/search-loader.html
comme suit :
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
{% capture result_elem %}
<article class="px-1 px-sm-2 px-lg-4 px-xl-0">
<header>
{% if site.active_lang != site.default_lang %}
<h2><a {% static_href %}href="/{{ site.active_lang }}{url}"{% endstatic_href %}>{title}</a></h2>
{% else %}
<h2><a href="{url}">{title}</a></h2>
{% endif %}
(...omis...)
<script>
{% comment %} Note: dependent library will be loaded in `js-selector.html` {% endcomment %}
document.addEventListener('DOMContentLoaded', () => {
{% assign search_path = '/assets/js/data/search.json' %}
{% if site.active_lang != site.default_lang %}
{% assign search_path = '/' | append: site.active_lang | append: search_path %}
{% endif %}
SimpleJekyllSearch({
searchInput: document.getElementById('search-input'),
resultsContainer: document.getElementById('search-results'),
json: '{{ search_path | relative_url }}',
searchResultTemplate: '{{ result_elem | strip_newlines }}',
(...suite)
- J’ai modifié la syntaxe liquid dans la partie
{% capture result_elem %}
pour ajouter le préfixe"/{{ site.active_lang }}"
devant l’URL du post chargée depuis le fichier JSON lorsquesite.active_lang
(langue de la page actuelle) est différente desite.default_lang
(langue par défaut du site). - De même, j’ai modifié la partie
<script>
pour définirsearch_path
comme le chemin par défaut (/assets/js/data/search.json
) si la langue de la page actuelle est identique à la langue par défaut du site, ou comme le chemin correspondant à cette langue (par exemple,/ko/assets/js/data/search.json
) si elles sont différentes.
Après ces modifications et une nouvelle compilation du site web, j’ai confirmé que les résultats de recherche s’affichent correctement pour chaque langue.
{url}
est un emplacement où sera insérée la valeur URL lue depuis le fichier JSON, et non une URL en soi, donc Polyglot ne le reconnaît pas comme cible de localisation et doit être traité directement selon la langue. Le problème est que"/{{ site.active_lang }}{url}"
est reconnu comme une URL, et bien que la localisation soit déjà terminée, Polyglot ne le sait pas et tente de la localiser à nouveau (par exemple,"/ko/ko/posts/example-post"
). Pour éviter cela, j’ai spécifié la balise{% static_href %}
.