Обновлено 2026-07-28!

Эта статья была написана при использовании фреймворка Jekyll. Сейчас я перешёл на Astro!

Обновлено 2024-09-15!

Поддержка многоязычности — это хорошо, но обслуживание стало слишком сложным и запутанным, поэтому я вернул всё к состоянию до применения плагина. Для полноценной поддержки многоязычности требуется переделать гораздо больше, чем кажется, и приходится мириться с неудобством очень сложного процесса слияния с оригинальной темой.

Знакомство с плагином

В среде GitHub-блога есть два основных плагина Jekyll для реализации многоязычности: jekyll-polyglot и jekyll-multiple-languages-plugin. Я использовал первый, jekyll-polyglot. Этот плагин вставляет код языка I18N после корневого URL в зависимости от значения lang, определённого во фронтматере каждого поста, создавая таким образом переведённые версии страниц. Говорят, что этот плагин создан по образцу второго — jekyll-multiple-languages-plugin. Официальное руководство, от установки до мер предосторожности, подробно описано в репозитории Polyglot на GitHub.

Подготовительная работа

Установка и настройка плагина

group :jekyll_plugins do
  gem "jekyll-polyglot"
end

Добавляем плагин в Gemfile, как указано выше, и устанавливаем командой gem install jekyll-polyglot.

plugins:
  - jekyll-polyglot

languages: ["ko", "en"]
default_lang: "ko"
exclude_from_localization: ['javascript', 'images', 'css', 'sitemap.xml']
parallel_localizaion: true

После установки плагина нужно добавить указанные выше строки в _config.yml. В languages указываются языки, которые будет поддерживать страница, в default_lang — язык по умолчанию. Обратите внимание: в среде Windows опция parallel_localization работает некорректно, поэтому её нужно обязательно установить в false.

Исправление бага с регулярным выражением

При попытке сборки после установки плагина возникает ошибка: 'relative_url_regex': target of repeat operator is not specified:. Она возникает из-за того, что некоторые регулярные выражения в файле site.rb плагина не обрабатывают подстановочные знаки (*), такие как exlude: *.gem *.gemspec *.config.js в _config.yml темы Chirpy. Я обратился с этим вопросом к создателю плагина, но получил ответ, что согласно этому документу тема Chirpy неправильно использует глобальные шаблоны в _config.yml.

Однако, учитывая, что другие темы Jekyll, такие как Minimal-Mistakes, тоже используют глобальные шаблоны, похоже, нужно изменить сам код плагина. В этом случае придётся использовать модифицированную версию плагина, поэтому я форкнул проект в свой репозиторий и подключил его в Gemfile следующим образом:

gem 'jekyll-polyglot', git: 'https://github.com/hyngng/jekyll-polyglot', branch: 'master'

Затем я изменил две функции — relative_url_regex() и absolute_url_regex(), которые находятся в файле site.rb по пути jekyll-polyglot-1.8.0/lib/jekyll/polyglot/patches/jekyll:

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

После изменения функций сборка командой bundle exec jekyll s прошла без проблем.

Изменение свойств файлов постов

---
lang: en
permalink: example-url-here
---

Для постов, которые должны быть переведены, нужно указать значение языка во фронтматере. По умолчанию указывается код страны I18N, например ko, en. В моём случае я указал ko-KR и en. permalink задаёт URL-путь поста. Поскольку Jekyll считает два файла с одинаковым URL одинаковыми по умолчанию, это нужно, чтобы искусственно различать оригинал и перевод.

_posts/2010-03-01-salad-recipes-en.md
_posts/2010-03-01-salad-recipes-sv.md
_posts/2010-03-01-salad-recipes-fr.md

Если не нравится различать языки постов через permalink во фронтматере, можно вместо этого изменить имена файлов, как показано выше. Однако в этом случае URL страницы может содержать тавтологию, например example.github.io/en/2010-03-01-salad-recipes-en.

Редактирование шаблона

Эта часть относится только к теме Chirpy. Если вы используете другой шаблон Jekyll, можете пропустить этот раздел и перейти к следующему абзацу. Но если вам, как и мне, нужно адаптировать шаблон Chirpy, следующая информация может быть полезна.

  • Переменные, доступные в плагине jekyll-polyglot:
    • site.default_lang: значение языка по умолчанию, объявленное в _config.yml.
    • site.active_lang: значение языка, активного на текущей веб-странице.
    • page.lang: значение языка поста, объявленное во фронтматере.

Используя эти три переменные, можно писать условные конструкции, например {% raw %}{% if page.lang == site.default_lang %}{% endraw %}, и ограничивать отображаемый на странице язык в зависимости от ситуации.

Загрузка языка сайта

{% if site.active_lang %}
  {% assign lang = site.active_lang %}
{% elsif site.data.locales[page.lang] %}
  {% assign lang = page.lang %}
{% elsif site.data.locales[site.lang] %}
  {% assign lang = site.lang %}
{% else %}
  {% assign lang = 'site.default_lang'' %}
{% endif %}

Шаблон Chirpy настраивает язык в отдельном файле _includes/lang.html. Можно изменить этот файл, как показано выше, а затем подключать lang.html в файлах макетов.

Отображение содержимого в зависимости от языка

{% include lang.html %}

В большинстве случаев я подключал lang.html как указано выше. В таких случаях, как пагинация, простого изменения указания языка было недостаточно, поэтому я написал отдельные конструкции. В основном я изменил логику так, чтобы на страницах определённого языка отображалась только информация, относящаяся к постам, написанным на этом языке.

<div id="post-list" class="flex-grow-1 px-xl-1">
  {% for post in posts %}
    {% if post.lang == site.active_lang %}
      <article class="card-wrapper card">...</article>
    {% endif %}
  {% endfor %}
</div>

Например, в _layouts/home.html я добавил условие {% raw %}{% if post.lang == site.active_lang %}{% endraw %}, чтобы на главной странице при английском языке сайта отображались только посты с lang: en. Вот список других файлов, в которые были внесены изменения:

НазначениеПуть к файлу
Общий макет_layouts/default.html
Главная страница_layouts/home.html
Категории_layouts/category.html
Теги_layouts/tags.html
Архив_layouts/archive.html
Информация_layouts/about.html
Недавно изменённые посты_includes/update-list.html
Обзор тегов_includes/trending_tags.html
Похожие посты_includes/related-posts.html
Навигация по постам_includes/post-nav.html
Пагинация_includes/post-paginator.html

Разделение содержимого страницы «О себе»

{% if site.active_lang == 'ko-KR' %}
## 한국어 자기소개
...
{% elsif site.active_lang == 'en' %}
## English Self-Introduction
...
{% endif %}

Способ отображения разного содержимого на странице «О себе» в зависимости от языка. Сначала я думал, что нужно будет создавать отдельные файлы вроде about-en.md, но оказалось, что проще всего показывать разное содержимое в одном файле в зависимости от языка сайта.

Естественное отображение количества слов

<span
  class="readtime"
  data-bs-toggle="tooltip"
  data-bs-placement="bottom"
  title="{{ words }}{% if site.active_lang != 'ko-KR' %}{{ ' ' }}{% endif %}{{ site.data.locales[include.lang].post.words }}
>

Маленькая деталь, которую я изменил, потому что она меня беспокоила. В этой теме при наведении курсора мыши на время чтения в верхней части поста отображается количество слов. Независимо от языка, между числом и единицей измерения был пробел — отображалось как «1000 자». Мне это казалось неестественным, поэтому я изменил так, чтобы в корейском отображалось «1000자», а в других языках — «1000 words» с пробелом.

Другие задачи

Указание языка страницы в заголовке

{% I18n_Headers %}

Это то, что рекомендуется в документации Google Search Center по международным и многоязычным версиям. Это необязательно, но если вы заботитесь об SEO, рекомендуется добавить указанный выше код в заголовок, чтобы указать язык страницы. После сборки код преобразуется в следующее:

<meta http-equiv="Content-Language" content="ko-KR">
<link rel="alternate" hreflang="ko-KR" href="ttps://hyngng.github.io/posts/:title/"/>
<link rel="alternate" hreflang="en" href="https://hyngng.github.io/en/posts/:title/"/>

Включение плагина в процесс сборки

name: Jekyll site CI

on:
  push:
    branches: [ "site" ]
  pull_request:
    branches: [ "site" ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Build the site in the jekyll/builder container
      run: |
        docker run \
        -v $:/srv/jekyll -v $/_site:/srv/jekyll/_site \
        jekyll/builder:latest /bin/bash -c "chmod -R 777 /srv/jekyll && jekyll build --future"

    - name: Push
      uses: s0/git-publish-subdir-action@develop
      env:
          REPO: self
          BRANCH: main
          FOLDER: _site
          GITHUB_TOKEN: $
          MESSAGE: "Build: ({sha}) {msg}"

В отличие от встроенных плагинов, jekyll-polyglot считается внешним плагином и должен собираться отдельно по соображениям безопасности. Если создать новый .yml файл в папке .github/workflows/ и написать в нём указанный выше код, сборка пройдёт без проблем.

Включение всех страниц в карту сайта

...
{% for lang in site.languages %}
  {% for post in site.posts %}
    {% if lang == post.lang %}
      <url>
        <loc>
          {{ site.url }}
          {% if lang == site.default_lang %}
            {{ post.url }}
          {% else %}
            {{ post.url | prepend: lang | prepend: '/' }}
          {% endif %}
        </loc>
        ...
      </url>
    {% endif %}
  {% endfor %}
{% endfor %}

Карта сайта — одна из самых больших проблем при поддержке многоязычности. По умолчанию теги <loc> создаются только для базовых страниц. Вместо этого я изменил логику так, чтобы проверять все языки из site.languages, игнорируя при этом недействительные элементы, такие как автоматически сгенерированные корейские страницы из файлов с lang: en.

Добавление кнопки переключения языка на страницу

{% for lang in site.languages %}
  <div class="lang" style="display: inline;">
    <a style="
      {% if lang == site.active_lang %}
        font-weight: bold;
      {% endif %}"
      href="
      {% if lang == site.default_lang %}
        {{site.baseurl}}{{page.url}}
      {% else %}
        {{site.baseurl}}/{{ lang }}{{page.url}}
      {% endif %}">
      {{ lang }}
    </a>
    {% if forloop.last == false %}
      <span class="lang-border"> </span>
    {% endif %}
  </div>
{% endfor %}

При необходимости таким кодом можно добавить кнопку переключения языка в нужное место. Однако лично я не стал её добавлять, потому что в моём блоге нет эксклюзивного контента для каждого языка, и, думаю, посетителям моего сайта нет особой необходимости просматривать страницы на другом языке.

Разделение содержимого фида по языкам

{% assign filtered_posts = site.posts | where: "lang", site.active_lang %}

{% for post in filtered_posts limit: 5 %}
  <entry> ... </entry>
{% endfor %}

Фид тоже был настроен так, чтобы динамически генерировать filtered_posts, включающие только посты, соответствующие site.active_lang. При регистрации в инструментах для веб-мастеров я отдельно регистрировал feed.xml и /en/feed.xml.

Результат

result-lightresult-dark

Заключение

Это было трудно. jekyll-polyglot оставляет скорее ощущение громоздкости, чем гибкости и удобства. Процесс внедрения никак нельзя назвать лёгким и простым, даже с натяжкой. Я думал, не лучше ли создать отдельный сайт только на английском и управлять двумя сайтами, но недостатков вроде сложности синхронизации контента и настройки индексации оказалось бы больше, поэтому я остановился на jekyll-polyglot. Тем не менее, если реализовать эту систему, преимущества jekyll-polyglot для создания собственной многоязычной поддержки очевидны.