🌐 feat(i18n): overhaul translation system & add languages (#145)
Revamp the existing translation system, simplifying management and adding several new languages. The new system reads from TOML files in the `/i18n` directory and improves template structures. It also enhances customisation options and robustness by providing fallbacks and modularity. - Implement a new, streamlined translation macro. - Load translations from `/i18n` TOML files. - Remove redundant configuration requirements. - Refactor templates to align with new i18n system. - Add support for Hindi, Japanese, Russian, Portuguese, Chinese, Italian, German, Ukranian, Korean, and French languages. - Credit Thomas Weitzel (@thomasweitzel) for inspiration.
This commit is contained in:
@@ -86,7 +86,7 @@
|
||||
{% if automatic_loading %}
|
||||
<script src="{{ get_url(path='js/' ~ comment_system ~ '.min.js', trailing_slash=false) | safe }}" async></script>
|
||||
{% else %}
|
||||
<button id="load-comments" class="load-comments-button" data-script-src="{{ get_url(path='js/' ~ comment_system ~ '.min.js', trailing_slash=false) | safe }}">{{ macros_translate::translate(key="load_comments", default="Load comments") }}</button>
|
||||
<button id="load-comments" class="load-comments-button" data-script-src="{{ get_url(path='js/' ~ comment_system ~ '.min.js', trailing_slash=false) | safe }}">{{ macros_translate::translate(key="load_comments", default="Load comments", language_strings=language_strings) }}</button>
|
||||
<script src="{{ get_url(path='js/loadComments.min.js', trailing_slash=false) | safe }}" async></script>
|
||||
{% endif %}
|
||||
|
||||
|
@@ -1,197 +0,0 @@
|
||||
{% macro content(page) %}
|
||||
|
||||
{%- set separator = config.extra.separator | default(value="•") -%}
|
||||
|
||||
{%- set rel_attributes = macros_rel_attributes::rel_attributes() | trim -%}
|
||||
|
||||
{%- if config.markdown.external_links_target_blank -%}
|
||||
{%- set blank_target = "target=_blank" -%}
|
||||
{%- else -%}
|
||||
{%- set blank_target = "" -%}
|
||||
{%- endif -%}
|
||||
|
||||
{# Debugging #}
|
||||
{# {% set last_ancestor = page.ancestors | slice(start=-1) %}
|
||||
{% set current_section = get_section(path=last_ancestor.0) %}
|
||||
|
||||
{% set settings_to_test = [
|
||||
"footnote_backlinks",
|
||||
"katex",
|
||||
"quick_navigation_buttons",
|
||||
"show_reading_time",
|
||||
"show_remote_changes",
|
||||
"toc",
|
||||
] %}
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>setting</th>
|
||||
<th>page</th>
|
||||
<th>section</th>
|
||||
<th>config</th>
|
||||
<th>macro output</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for setting in settings_to_test %}
|
||||
<tr>
|
||||
<td><code>{{ setting }}</code></td>
|
||||
<td>{{ page.extra[setting] | default(value="⬛") }}</td>
|
||||
<td>{{ current_section.extra[setting] | default(value="⬛") }}</td>
|
||||
<td>{{ config.extra[setting] | default(value="⬛") }}</td>
|
||||
<td>{{ macros_settings::evaluate_setting_priority(setting=setting, page=page) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table> #}
|
||||
{# End debugging #}
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1 class="article-title">
|
||||
{{ page.title }}
|
||||
</h1>
|
||||
|
||||
<ul class="meta">
|
||||
{% if page.draft %}
|
||||
<li class="draft-label">{{ macros_translate::translate(key="draft", default="DRAFT") }}</li>
|
||||
{% endif %}
|
||||
|
||||
{% if page.date %}
|
||||
<li>{{ macros_format_date::format_date(date=page.date, short=true) }}</li>
|
||||
{% endif %}
|
||||
|
||||
{# page settings override config settings #}
|
||||
{% if macros_settings::evaluate_setting_priority(setting="show_reading_time", page=page, default_global_value=true) == "true" %}
|
||||
{{ separator }} <li title="{{ page.word_count }} {{ macros_translate::translate(key="words", default="words") }}">{{ page.reading_time }} {{ macros_translate::translate(key="min_read", default="min read") }}</li>
|
||||
{% endif %}
|
||||
|
||||
{%- if page.taxonomies and page.taxonomies.tags -%}
|
||||
{{ separator }} <li>{{- macros_translate::translate(key="tags", default="tags") | capitalize -}}: </li>
|
||||
{%- for tag in page.taxonomies.tags -%}
|
||||
<li><a href={{ get_taxonomy_url(kind='tags', name=tag, lang=lang) | safe }}>{{ tag }}</a>
|
||||
{%- if not loop.last -%}
|
||||
,
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
|
||||
{% if page.updated %}
|
||||
</ul><ul class="meta last-updated"><li>{{ macros_translate::translate(key="last_updated_on", default="Last updated on") }} {{ macros_format_date::format_date(date=page.updated, short=true) }}</li>
|
||||
{# Show link to remote changes if enabled #}
|
||||
{% if config.extra.remote_repository_url and macros_settings::evaluate_setting_priority(setting="show_remote_changes", page=page, default_global_value=true) == "true" %}
|
||||
{{ separator }}
|
||||
<li><a href="{{ macros_create_history_url::create_history_url(relative_path=page.relative_path) }}" {{ blank_target }} rel="{{ rel_attributes }}">{{ macros_translate::translate(key="see_changes", default="See changes") }}<small> ↗</small></a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
{% if page.extra.tldr %}
|
||||
<div class="tldr">
|
||||
<h3>TL;DR:</h3>
|
||||
<p>{{ page.extra.tldr }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Optional table of contents below the header #}
|
||||
{% if page.toc and macros_settings::evaluate_setting_priority(setting="toc", page=page, default_global_value=false) == "true" %}
|
||||
{{ macros_toc::toc(page=page, header=true) }}
|
||||
{% endif %}
|
||||
|
||||
<section class="body">
|
||||
{# The replace pattern is used to enable arbitrary locations for the Table of Contents #}
|
||||
{# This is Philipp Oppermann's workaround: https://github.com/getzola/zola/issues/584#issuecomment-474329637 #}
|
||||
{{ page.content | replace(from="<!-- toc -->", to=macros_toc::toc(page=page, header=false)) | safe }}
|
||||
</section>
|
||||
|
||||
{# Check if comments are enabled #}
|
||||
{% set giscus_enabled = config.extra.giscus.enabled_for_all_posts or page.extra.giscus %}
|
||||
{% set utterances_enabled = config.extra.utterances.enabled_for_all_posts or page.extra.utterances %}
|
||||
{% set hyvortalk_enabled = config.extra.hyvortalk.enabled_for_all_posts or page.extra.hyvortalk %}
|
||||
{% set isso_enabled = config.extra.isso.enabled_for_all_posts or page.extra.isso %}
|
||||
|
||||
{# Ensure only one comment system is enabled #}
|
||||
{# Counter for enabled comment systems #}
|
||||
{% set enabled_systems = 0 %}
|
||||
|
||||
{# Check and count the enabled comment systems #}
|
||||
{% if giscus_enabled %}
|
||||
{% set comment_system = "giscus" %}
|
||||
{% set enabled_systems = enabled_systems + 1 %}
|
||||
{% endif %}
|
||||
{% if utterances_enabled %}
|
||||
{% set comment_system = "utterances" %}
|
||||
{% set enabled_systems = enabled_systems + 1 %}
|
||||
{% endif %}
|
||||
{% if hyvortalk_enabled %}
|
||||
{% set comment_system = "hyvortalk" %}
|
||||
{% set enabled_systems = enabled_systems + 1 %}
|
||||
{% endif %}
|
||||
{% if isso_enabled %}
|
||||
{% set comment_system = "isso" %}
|
||||
{% set enabled_systems = enabled_systems + 1 %}
|
||||
{% endif %}
|
||||
|
||||
{# Ensure only one comment system is enabled #}
|
||||
{% if enabled_systems > 1 %}
|
||||
{{ throw(message="ERROR: Multiple comment systems have been enabled for the same page. Check your config.toml and individual page settings to ensure only one comment system is activated at a time.") }}
|
||||
{% endif %}
|
||||
|
||||
{% if comment_system %}
|
||||
{% set automatic_loading = config.extra[comment_system].automatic_loading %}
|
||||
{{ macros_add_comments::add_comments(comment_system=comment_system, automatic_loading=automatic_loading) }}
|
||||
{% endif %}
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
{# Quick navigation buttons #}
|
||||
{% if macros_settings::evaluate_setting_priority(setting="quick_navigation_buttons", page=page, default_global_value=false) == "true" %}
|
||||
<div id="button-container">
|
||||
{# Button to go show a floating Table of Contents #}
|
||||
{% if page.toc %}
|
||||
<div id="toc-floating-container">
|
||||
<input type="checkbox" id="toc-toggle" class="toggle"/>
|
||||
<label for="toc-toggle" class="overlay"></label>
|
||||
<label for="toc-toggle" id="toc-button" class="button" title="Toggle Table of Contents" aria-label="toggle Table of Contents">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M414.82-193.094q-18.044 0-30.497-12.32-12.453-12.319-12.453-30.036t12.453-30.086q12.453-12.37 30.497-12.37h392.767q17.237 0 29.927 12.487 12.69 12.486 12.69 30.203 0 17.716-12.69 29.919t-29.927 12.203H414.82Zm0-244.833q-18.044 0-30.497-12.487Q371.87-462.9 371.87-480.45t12.453-29.92q12.453-12.369 30.497-12.369h392.767q17.237 0 29.927 12.511 12.69 12.512 12.69 29.845 0 17.716-12.69 30.086-12.69 12.37-29.927 12.37H414.82Zm0-245.167q-18.044 0-30.497-12.32t-12.453-30.037q0-17.716 12.453-30.086 12.453-12.369 30.497-12.369h392.767q17.237 0 29.927 12.486 12.69 12.487 12.69 30.203 0 17.717-12.69 29.92-12.69 12.203-29.927 12.203H414.82ZM189.379-156.681q-32.652 0-55.878-22.829t-23.226-55.731q0-32.549 23.15-55.647 23.151-23.097 55.95-23.097 32.799 0 55.313 23.484 22.515 23.484 22.515 56.246 0 32.212-22.861 54.893-22.861 22.681-54.963 22.681Zm0-245.167q-32.652 0-55.878-23.134-23.226-23.135-23.226-55.623 0-32.487 23.467-55.517t56.12-23.03q32.102 0 54.721 23.288 22.62 23.288 22.62 55.775 0 32.488-22.861 55.364-22.861 22.877-54.963 22.877Zm-.82-244.833q-32.224 0-55.254-23.288-23.03-23.289-23.03-55.623 0-32.333 23.271-55.364 23.272-23.03 55.495-23.03 32.224 0 55.193 23.288 22.969 23.289 22.969 55.622 0 32.334-23.21 55.364-23.21 23.031-55.434 23.031Z"/></svg>
|
||||
</label>
|
||||
<div class="toc-content">
|
||||
{{ macros_toc::toc(page=page, header=false) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Button to go to the comment section #}
|
||||
{% if comment_system %}
|
||||
<a href="#comments" id="comments-button" title="Go to comment section">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path d="M18 10c0 3.866-3.582 7-8 7a8.841 8.841 0 01-4.083-.98L2 17l1.338-3.123C2.493 12.767 2 11.434 2 10c0-3.866 3.582-7 8-7s8 3.134 8 7zM7 9H5v2h2V9zm8 0h-2v2h2V9zM9 9h2v2H9V9z" clip-rule="evenodd" fill-rule="evenodd"/></svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{# Button to go to the top of the page #}
|
||||
<a href="#" id="top-button" title="Go to top of page">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Add KaTeX functionality #}
|
||||
{%- if macros_settings::evaluate_setting_priority(setting="katex", page=page, default_global_value=false) == "true" -%}
|
||||
<link rel="stylesheet" href="{{ get_url(path='katex.min.css', trailing_slash=false) | safe }}">
|
||||
<script defer src="{{ get_url(path='js/katex.min.js', trailing_slash=false) | safe }}"></script>
|
||||
{%- endif -%}
|
||||
|
||||
{# Add copy button to code blocks #}
|
||||
{%- if macros_settings::evaluate_setting_priority(setting="copy_button", page=page, default_global_value=true) == "true" -%}
|
||||
<script defer src="{{ get_url(path='js/copyCodeToClipboard.min.js', trailing_slash=false) | safe }}"/></script>
|
||||
{%- endif -%}
|
||||
|
||||
{# Add backlinks to footnotes #}
|
||||
{%- if macros_settings::evaluate_setting_priority(setting="footnote_backlinks", page=page, default_global_value=false) == "true" -%}
|
||||
<script defer src="{{ get_url(path='js/footnoteBacklinks.min.js', trailing_slash=false | safe )}}"/></script>
|
||||
{%- endif -%}
|
||||
|
||||
{% endmacro content %}
|
@@ -1,7 +1,7 @@
|
||||
{% macro format_date(date, short) %}
|
||||
{% macro format_date(date, short, language_strings="") %}
|
||||
|
||||
{# Set locale #}
|
||||
{% set date_locale = macros_translate::translate(key="date_locale", default="en_GB") %}
|
||||
{% set date_locale = macros_translate::translate(key="date_locale", default="en_GB", language_strings=language_strings) %}
|
||||
|
||||
{% if config.extra.short_date_format and short %}
|
||||
{{ date | date(format=config.extra.short_date_format, locale=date_locale) }}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{% macro list_posts(posts, max) %}
|
||||
{% macro list_posts(posts, max, language_strings="") %}
|
||||
|
||||
<div class="bloglist-container">
|
||||
{% for post in posts %}
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
{% if post.date %}
|
||||
<div class="date">
|
||||
{{ macros_format_date::format_date(date=post.date, short=false) }}
|
||||
{{ macros_format_date::format_date(date=post.date, short=false, language_strings=language_strings) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -37,14 +37,14 @@
|
||||
<p>{{ post.summary | striptags | safe | trim_end_matches(pat=".") }}…</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a class="readmore" href={{ post.permalink }}>{{ macros_translate::translate(key="read_more", default="Read more") }} →</a>
|
||||
<a class="readmore" href={{ post.permalink }}>{{ macros_translate::translate(key="read_more", default="Read more", language_strings=language_strings) }} →</a>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if not loop.last %}
|
||||
{% if loop.index == max %}
|
||||
<div class="all-posts">
|
||||
<a href="{{ get_url(path="blog", lang=lang) }}/">{{ macros_translate::translate(key="all_posts", default="All posts") }} ⟶</a>
|
||||
<a href="{{ get_url(path="blog", lang=lang) }}/">{{ macros_translate::translate(key="all_posts", default="All posts", language_strings=language_strings) }} ⟶</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
@@ -1,28 +1,28 @@
|
||||
{% macro paginate() %}
|
||||
{% macro paginate(language_strings="") %}
|
||||
|
||||
{% if paginator %}
|
||||
<ul class="pagination">
|
||||
{% if paginator.previous %}
|
||||
<li class="page-item page-prev">
|
||||
<a href="{{ paginator.previous }}" class="page-link" aria-label="{{ macros_translate::translate(key="prev", default="Prev") }}">← {{ macros_translate::translate(key="prev", default="Prev") }}</a>
|
||||
<a href="{{ paginator.previous }}" class="page-link" aria-label="{{ macros_translate::translate(key="prev", default="Prev", language_strings=language_strings) }}">← {{ macros_translate::translate(key="prev", default="Prev", language_strings=language_strings) }}</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item page-prev">
|
||||
<span class="page-link disabled" aria-disabled="true" aria-label="{{ macros_translate::translate(key="prev", default="Prev") }} (disabled)">← {{ macros_translate::translate(key="prev", default="Prev") }}</span>
|
||||
<span class="page-link disabled" aria-disabled="true" aria-label="{{ macros_translate::translate(key="prev", default="Prev", language_strings=language_strings) }} (disabled)">← {{ macros_translate::translate(key="prev", default="Prev", language_strings=language_strings) }}</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="page-item page-numbers">
|
||||
{{ paginator.current_index }} {{ macros_translate::translate(key="of", default="of") }} {{ paginator.number_pagers }}
|
||||
{{ paginator.current_index }} {{ macros_translate::translate(key="of", default="of", language_strings=language_strings) }} {{ paginator.number_pagers }}
|
||||
</li>
|
||||
|
||||
{% if paginator.next %}
|
||||
<li class="page-item page-next">
|
||||
<a href="{{ paginator.next }}" class="page-link" aria-label="{{ macros_translate::translate(key="next", default="Next") }}">{{ macros_translate::translate(key="next", default="Next") }} →</a>
|
||||
<a href="{{ paginator.next }}" class="page-link" aria-label="{{ macros_translate::translate(key="next", default="Next", language_strings=language_strings) }}">{{ macros_translate::translate(key="next", default="Next", language_strings=language_strings) }} →</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item page-next">
|
||||
<span class="page-link disabled" aria-disabled="true" aria-label="{{ macros_translate::translate(key="next", default="Next") }} (disabled)">{{ macros_translate::translate(key="next", default="Next") }} →</span>
|
||||
<span class="page-link disabled" aria-disabled="true" aria-label="{{ macros_translate::translate(key="next", default="Next", language_strings=language_strings) }} (disabled)">{{ macros_translate::translate(key="next", default="Next", language_strings=language_strings) }} →</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{%- macro set_title() -%}
|
||||
{%- macro set_title(language_strings="") -%}
|
||||
|
||||
{# Setup. #}
|
||||
{%- set prefix = config.title | safe -%}
|
||||
@@ -26,7 +26,7 @@
|
||||
{%- set suffix = term.name -%}
|
||||
{% elif taxonomy.name %}
|
||||
{# List of tags. #}
|
||||
{%- set suffix = macros_translate::translate(key=taxonomy.name) | capitalize -%}
|
||||
{%- set suffix = macros_translate::translate(key=taxonomy.name, language_strings=language_strings) | capitalize -%}
|
||||
{% else %}
|
||||
{%- set suffix = "404" %}
|
||||
{%- endif -%}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{% macro toc(page, header) %}
|
||||
{% macro toc(page, header, language_strings="") %}
|
||||
|
||||
{%- set toc_levels = page.extra.toc_levels | default(value=3) -%}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<div class="toc-container">
|
||||
{% if header %}
|
||||
<h3>{{ macros_translate::translate(key="table_of_contents", default="Table of Contents") }}</h3>
|
||||
<h3>{{ macros_translate::translate(key="table_of_contents", default="Table of Contents", language_strings=language_strings) }}</h3>
|
||||
{% endif %}
|
||||
|
||||
<ul>
|
||||
|
@@ -1,14 +1,24 @@
|
||||
{% macro translate(key, default="", force_lang="") %}
|
||||
{#
|
||||
Macro: translate
|
||||
Purpose: Translate text strings based on the current language setting.
|
||||
Parameters:
|
||||
- key: The key used to look up the translation in the loaded language data.
|
||||
- language_strings: The loaded language data (from a .toml file).
|
||||
- default: The default text to use if a translation is not found.
|
||||
|
||||
{%- if config.default_language != "en" -%}
|
||||
{#- The entire site should be translated -#}
|
||||
{{- trans(key=key | safe, lang=lang) -}}
|
||||
{%- elif lang != config.default_language -%}
|
||||
{{- trans(key=key | safe, lang=lang) -}}
|
||||
{%- elif force_lang -%}
|
||||
{{- trans(key=key | safe, lang=force_lang) -}}
|
||||
{%- else -%}
|
||||
{{- default -}}
|
||||
{%- endif -%}
|
||||
Usage:
|
||||
Use this macro to translate text in templates. The macro looks for the
|
||||
translation based on the given 'key' in 'language_strings'. If not found,
|
||||
it falls back to using the 'default' text.
|
||||
|
||||
Note:
|
||||
The 'language_strings' are loaded in base.html based on the current language
|
||||
from files in the 'i18n' folder.
|
||||
|
||||
Example:
|
||||
{{ macros_translate::translate(key="site_source", language_strings=language_strings, default="Site source", language_strings=language_strings) }}
|
||||
#}
|
||||
|
||||
{% macro translate(key, language_strings="", default="") %}
|
||||
{{- language_strings[key] | default(value=default) | safe -}}
|
||||
{% endmacro %}
|
||||
|
Reference in New Issue
Block a user