mirror of
https://codeberg.org/Codeberg/Documentation.git
synced 2026-06-16 05:13:54 -07:00
Initial i18n support
This commit is contained in:
parent
0829806f6a
commit
56c41ae914
11 changed files with 256 additions and 36 deletions
|
|
@ -256,3 +256,39 @@ a.show-on-focus:focus {
|
|||
.card-text p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* language dropdown */
|
||||
.lang-dropdown {
|
||||
border-radius: var(--bs-border-radius);
|
||||
border: 1px solid var(--bs-border-color-translucent);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.lang-dropdown .dropdown-item {
|
||||
border-radius: var(--bs-border-radius);
|
||||
padding: 6px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.lang-dropdown .dropdown-item.active {
|
||||
background-color: #0252cc;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .lang-dropdown .dropdown-item.active,
|
||||
.dark-mode .lang-dropdown .dropdown-item.active {
|
||||
background-color: #66a3ff;
|
||||
}
|
||||
|
||||
/* prevent right-overflow */
|
||||
@media (max-width: 767.98px) {
|
||||
.lang-dropdown {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* remove right-side border radius */
|
||||
.btn-group > .dropdown:first-child > .btn {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,3 +23,29 @@ function toggleSidebar() {
|
|||
sidebarEl.classList.add('show');
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
const dropdownToggle = event.target.closest('[data-bs-toggle="dropdown"]');
|
||||
if (dropdownToggle) {
|
||||
event.preventDefault();
|
||||
const dropdownContainer = dropdownToggle.closest('.dropdown');
|
||||
const dropdownMenu = dropdownContainer ? dropdownContainer.querySelector('.dropdown-menu') : null;
|
||||
if (dropdownMenu) {
|
||||
const isExpanded = dropdownToggle.getAttribute('aria-expanded') === 'true';
|
||||
closeAllDropdowns();
|
||||
dropdownToggle.setAttribute('aria-expanded', !isExpanded ? 'true' : 'false');
|
||||
dropdownMenu.classList.toggle('show', !isExpanded);
|
||||
}
|
||||
} else {
|
||||
closeAllDropdowns();
|
||||
}
|
||||
});
|
||||
|
||||
function closeAllDropdowns() {
|
||||
document.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(function (toggle) {
|
||||
toggle.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
document.querySelectorAll('.dropdown-menu').forEach(function (menu) {
|
||||
menu.classList.remove('show');
|
||||
});
|
||||
}
|
||||
|
|
|
|||
20
content/_data/i18n.json
Normal file
20
content/_data/i18n.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"en": {
|
||||
"docs": "Docs",
|
||||
"toggle_dark_mode": "Toggle dark mode",
|
||||
"close": "Close",
|
||||
"filter_docs": "Filter docs",
|
||||
"press_slash_to_focus": "Press [key] to focus",
|
||||
"toggle_sidebar": "Toggle sidebar",
|
||||
"view_history": "View History",
|
||||
"view_source": "View Source",
|
||||
"draft_notice": "Please note that this article is still a draft and might not have any contents yet.",
|
||||
"copyright": "© Codeberg Docs Contributors. See [license]LICENSE[/license]",
|
||||
"find_out_more": "Find out more in this section:",
|
||||
"contributing_title": "Contributing",
|
||||
"contributing_p1": "Hey there! 👋 Thank you for reading this article!",
|
||||
"contributing_p2": "Is there something missing, or do you have an idea on how to improve the documentation? Do you want to write your own article?",
|
||||
"contributing_p3": "You're invited to contribute to the Codeberg Documentation at [repo]its source code repository[/repo] for example, by [pr]adding a pull request[/pr] or joining in on the discussion in [issues]the issue tracker[/issues].",
|
||||
"contributing_p4": "For an introduction on contributing to Codeberg Documentation, please have a look at [faq]the Contributor FAQ[/faq]."
|
||||
}
|
||||
}
|
||||
3
content/_data/languages.json
Normal file
3
content/_data/languages.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"en": "English"
|
||||
}
|
||||
|
|
@ -1,20 +1,25 @@
|
|||
{% set i18nDict = i18n[lang or 'en'] %}
|
||||
<div class="card" data-pagefind-ignore="all">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Contributing</h5>
|
||||
<h5 class="card-title">{{ i18nDict.contributing_title }}</h5>
|
||||
<div class="card-text">
|
||||
<p>Hey there! 👋 Thank you for reading this article!</p>
|
||||
<p>{{ i18nDict.contributing_p1 }}</p>
|
||||
<p>{{ i18nDict.contributing_p2 }}</p>
|
||||
<p>
|
||||
Is there something missing, or do you have an idea on how to improve the documentation?
|
||||
Do you want to write your own article?
|
||||
{{ i18nDict.contributing_p3 | tReplace({
|
||||
"repo": '<a href="https://codeberg.org/Codeberg/Documentation">',
|
||||
"/repo": '</a>',
|
||||
"pr": '<a href="/collaborating/pull-requests-and-git-flow">',
|
||||
"/pr": '</a>',
|
||||
"issues": '<a href="https://codeberg.org/Codeberg/Documentation/issues">',
|
||||
"/issues": '</a>'
|
||||
}) | safe }}
|
||||
</p>
|
||||
<p>
|
||||
You're invited to contribute to the Codeberg Documentation at <a href="https://codeberg.org/Codeberg/Documentation">its source code repository</a>
|
||||
for example, by <a href="/collaborating/pull-requests-and-git-flow">adding a pull request</a>
|
||||
or joining in on the discussion in <a href="https://codeberg.org/Codeberg/Documentation/issues">the issue tracker</a>.
|
||||
</p>
|
||||
<p>
|
||||
For an introduction on contributing to Codeberg Documentation, please have a look
|
||||
at <a href="https://docs.codeberg.org/improving-codeberg/docs-contributor-faq">the Contributor FAQ</a>.
|
||||
{{ i18nDict.contributing_p4 | tReplace({
|
||||
"faq": '<a href="https://docs.codeberg.org/improving-codeberg/docs-contributor-faq">',
|
||||
"/faq": '</a>'
|
||||
}) | safe }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark" data-bs-core="modern">
|
||||
{% set currentLang = lang or 'en' %}
|
||||
{% set i18nDict = i18n[currentLang] %}
|
||||
{% from "language_switcher.njk" import languageSwitcher %}
|
||||
<html lang="{{ currentLang }}" data-bs-theme="dark" data-bs-core="modern">
|
||||
<head>
|
||||
<title>{% if eleventyNavigation.title %}{{ eleventyNavigation.title }} | {% endif %}Codeberg Documentation</title>
|
||||
|
||||
|
|
@ -84,17 +87,17 @@
|
|||
width="24"
|
||||
alt="Codeberg"
|
||||
/>
|
||||
Docs
|
||||
{{ i18nDict.docs }}
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary btn-square d-none d-lg-inline-block"
|
||||
aria-label="Toggle dark mode"
|
||||
aria-label="{{ i18nDict.toggle_dark_mode }}"
|
||||
onclick="toggleDarkMode()"
|
||||
>
|
||||
{% fas_icon "moon" %}
|
||||
</button>
|
||||
<button type="button" class="btn-close d-lg-none ms-1" aria-label="Close" onclick="toggleSidebar()"></button>
|
||||
<button type="button" class="btn-close d-lg-none ms-1" aria-label="{{ i18nDict.close }}" onclick="toggleSidebar()"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body position-relative p-0">
|
||||
<div class="filter-docs sticky-top p-3">
|
||||
|
|
@ -102,23 +105,21 @@
|
|||
id="search-input"
|
||||
type="text"
|
||||
class="form-control search"
|
||||
placeholder="Filter docs"
|
||||
aria-label="Filter docs"
|
||||
placeholder="{{ i18nDict.filter_docs }}"
|
||||
aria-label="{{ i18nDict.filter_docs }}"
|
||||
/>
|
||||
<div class="mt-1">
|
||||
<small
|
||||
>Press
|
||||
<kbd class="text-body" style="font-size: 10px; background-color: hsla(var(--bs-emphasis-color-hsl), 0.1)"
|
||||
>/</kbd
|
||||
>
|
||||
to focus</small
|
||||
>
|
||||
<small>
|
||||
{{ i18nDict.press_slash_to_focus | tReplace({
|
||||
"key": '<kbd class="text-body" style="font-size: 10px; background-color: hsla(var(--bs-emphasis-color-hsl), 0.1)">/</kbd>'
|
||||
}) | safe }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 pt-0">
|
||||
<div id="search-results"></div>
|
||||
<ul class="sidebar-nav list">
|
||||
{% for entry in collections.all | eleventyNavigation %} {% set active = entry.url == page.url or entry.key ==
|
||||
{% for entry in collections.all | filterByLang(currentLang) | eleventyNavigation %} {% set active = entry.url == page.url or entry.key ==
|
||||
eleventyNavigation.parent %} {% if (not entry.draft) or active %}
|
||||
<li class="position-relative mt-4 mb-1">
|
||||
<h5 class="sidebar-header {% if active %} active{% endif %}">
|
||||
|
|
@ -146,10 +147,10 @@
|
|||
<nav class="navbar navbar-expand-md docs-navbar sticky-top">
|
||||
<div class="container-md px-3 px-sm-4 px-xl-5 py-1 justify-content-start">
|
||||
<div class="btn-group me-3 d-lg-none">
|
||||
<button type="button" class="btn btn-secondary btn-square" aria-label="Toggle sidebar" onclick="toggleSidebar()">
|
||||
<button type="button" class="btn btn-secondary btn-square" aria-label="{{ i18nDict.toggle_sidebar }}" onclick="toggleSidebar()">
|
||||
{% fas_icon "bars" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-square" aria-label="Toggle dark mode" onclick="toggleDarkMode()">
|
||||
<button type="button" class="btn btn-secondary btn-square" aria-label="{{ i18nDict.toggle_dark_mode }}" onclick="toggleDarkMode()">
|
||||
{% fas_icon "moon" %}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -162,9 +163,10 @@
|
|||
width="24"
|
||||
alt="Codeberg"
|
||||
/>
|
||||
Docs
|
||||
{{ i18nDict.docs }}
|
||||
</a>
|
||||
<div class="btn-group ms-auto d-md-none">
|
||||
{{ languageSwitcher(true) }}
|
||||
<a href="{{ urls.commitHistoryMaster }}/{{ page.inputPath }}" class="btn btn-secondary btn-square" target="_blank" rel="noreferrer">
|
||||
{% fas_icon "history" %}
|
||||
</a>
|
||||
|
|
@ -174,13 +176,14 @@
|
|||
</div>
|
||||
<div class="collapse navbar-collapse" id="docs-navbar-collapse">
|
||||
<div class="d-flex align-items-center justify-content-center ms-auto gap-2">
|
||||
{{ languageSwitcher(false) }}
|
||||
<a
|
||||
href="{{ urls.commitHistoryMaster }}/{{ page.inputPath }}"
|
||||
class="btn btn-secondary flex-grow-1 d-flex align-items-center flex-nowrap text-start position-relative"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{% fas_icon "history" %} View History
|
||||
{% fas_icon "history" %} {{ i18nDict.view_history }}
|
||||
</a>
|
||||
<a
|
||||
href="{{ urls.docsSourcesMaster }}/{{ page.inputPath }}"
|
||||
|
|
@ -188,7 +191,7 @@
|
|||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{% fas_icon "code" %} View Source
|
||||
{% fas_icon "code" %} {{ i18nDict.view_source }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -202,7 +205,7 @@
|
|||
<h1 id="top" class="lh-base mb-1 content-title font-size-24">{{ eleventyNavigation.title }}</h1>
|
||||
{% endif %} {% if eleventyNavigation.draft %}
|
||||
<strong data-pagefind-ignore="all"
|
||||
>Please note that this article is still a draft and might not have any contents yet.</strong
|
||||
>{{ i18nDict.draft_notice }}</strong
|
||||
>
|
||||
{% endif %}
|
||||
|
||||
|
|
@ -219,8 +222,10 @@
|
|||
<footer class="docs-footer" data-pagefind-ignore="all">
|
||||
<div class="container-md px-3 px-sm-4 px-xl-5 py-3">
|
||||
<p>
|
||||
© Codeberg Docs Contributors. See
|
||||
<a href="https://codeberg.org/Codeberg/Documentation/src/branch/main/LICENSE.md">LICENSE</a>
|
||||
{{ i18nDict.copyright | tReplace({
|
||||
"license": '<a href="https://codeberg.org/Codeberg/Documentation/src/branch/main/LICENSE.md">',
|
||||
"/license": '</a>'
|
||||
}) | safe }}
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
|
|||
46
content/_includes/language_switcher.njk
Normal file
46
content/_includes/language_switcher.njk
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{% macro languageSwitcher(isMobile=false) %}
|
||||
{% if languages and (languages | length) > 1 %}
|
||||
<div class="dropdown">
|
||||
<button
|
||||
class="btn btn-secondary {% if isMobile %}btn-square{% else %}dropdown-toggle d-flex align-items-center gap-1{% endif %}"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
data-bs-display="static"
|
||||
aria-expanded="false"
|
||||
{% if isMobile %}aria-label="Select Language"{% endif %}
|
||||
>
|
||||
{% fas_icon "globe" %}
|
||||
{% if not isMobile %}
|
||||
{{ languages[currentLang] or currentLang }}
|
||||
{% endif %}
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end lang-dropdown">
|
||||
{% set alternateLinks = collections.all | customLocaleLinks(translationKey, currentLang) %}
|
||||
{% for code, label in languages %}
|
||||
{% if code == currentLang %}
|
||||
<li><a class="dropdown-item active" href="#">{{ label }}</a></li>
|
||||
{% else %}
|
||||
{% set translated = false %}
|
||||
{% set translatedUrl = "" %}
|
||||
{% for link in alternateLinks %}
|
||||
{% if link.lang == code %}
|
||||
{% set translated = true %}
|
||||
{% set translatedUrl = link.url %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if translated %}
|
||||
<li><a class="dropdown-item" href="{{ translatedUrl }}">{{ label }}</a></li>
|
||||
{% else %}
|
||||
{% set rootUrl = "/" + code + "/" if code != "en" else "/" %}
|
||||
<li>
|
||||
<a class="dropdown-item text-muted d-flex align-items-center justify-content-between" href="{{ rootUrl }}">
|
||||
{{ label }} <span class="badge text-bg-secondary ms-2" style="font-size: 10px;">Home</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{ "layout": "default_layout" }
|
||||
13
content/content.11tydata.mjs
Normal file
13
content/content.11tydata.mjs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export default {
|
||||
layout: "default_layout",
|
||||
lang: "en",
|
||||
eleventyComputed: {
|
||||
translationKey: (data) => {
|
||||
let stem = data.page.filePathStem;
|
||||
if (stem.endsWith("/index")) {
|
||||
stem = stem.substring(0, stem.length - 6);
|
||||
}
|
||||
return stem.startsWith('/') ? stem.substring(1) : stem;
|
||||
}
|
||||
}
|
||||
};
|
||||
27
content/l10n/l10n.11tydata.mjs
Normal file
27
content/l10n/l10n.11tydata.mjs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
export default {
|
||||
layout: "default_layout",
|
||||
eleventyComputed: {
|
||||
permalink: (data) => {
|
||||
let stem = data.page.filePathStem;
|
||||
if (stem.startsWith("/l10n/")) {
|
||||
stem = stem.substring(5);
|
||||
}
|
||||
if (stem.endsWith("/index")) {
|
||||
stem = stem.substring(0, stem.length - 5);
|
||||
} else if (stem.endsWith("/home")) {
|
||||
stem = stem.substring(0, stem.length - 4);
|
||||
}
|
||||
return stem + "/";
|
||||
},
|
||||
translationKey: (data) => {
|
||||
let stem = data.page.filePathStem;
|
||||
// extract segment after '/l10n/<lang>/'
|
||||
let match = stem.match(/^\/l10n\/[^/]+\/(.*)$/);
|
||||
let relative = match ? match[1] : stem;
|
||||
if (relative.endsWith("/index")) {
|
||||
relative = relative.substring(0, relative.length - 6);
|
||||
}
|
||||
return relative.startsWith('/') ? relative.substring(1) : relative;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -8,6 +8,7 @@ import markdownItAnchor from 'markdown-it-anchor';
|
|||
import { library, icon } from '@fortawesome/fontawesome-svg-core';
|
||||
import { fas } from '@fortawesome/free-solid-svg-icons';
|
||||
import { execSync } from 'child_process';
|
||||
import { EleventyI18nPlugin } from '@11ty/eleventy';
|
||||
|
||||
export default function (eleventyConfig) {
|
||||
eleventyConfig.addPlugin(navigationPlugin);
|
||||
|
|
@ -21,6 +22,41 @@ export default function (eleventyConfig) {
|
|||
decoding: 'async',
|
||||
},
|
||||
});
|
||||
eleventyConfig.addPlugin(EleventyI18nPlugin, {
|
||||
defaultLanguage: 'en',
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter('filterByLang', function (collection, lang) {
|
||||
return (collection || []).filter((item) => {
|
||||
const itemLang = item.data?.lang || 'en';
|
||||
return itemLang === lang;
|
||||
});
|
||||
});
|
||||
eleventyConfig.addFilter('customLocaleLinks', function (collection, currentKey, currentLang) {
|
||||
currentLang = currentLang || 'en';
|
||||
if (!currentKey) return [];
|
||||
return (collection || [])
|
||||
.filter((item) => {
|
||||
const itemKey = item.data?.translationKey;
|
||||
const itemLang = item.data?.lang || 'en';
|
||||
return itemKey === currentKey && itemLang !== currentLang;
|
||||
})
|
||||
.map((item) => ({
|
||||
url: item.url,
|
||||
lang: item.data?.lang || 'en',
|
||||
}));
|
||||
});
|
||||
// tag replacement wrapper for i18n
|
||||
eleventyConfig.addFilter("tReplace", function (str, replacements) {
|
||||
if (!str) return "";
|
||||
if (!replacements || typeof replacements !== 'object') return str;
|
||||
let result = str;
|
||||
for (const [key, value] of Object.entries(replacements)) {
|
||||
const safeValue = value !== null && value !== undefined ? String(value) : "";
|
||||
result = result.replaceAll(`[${key}]`, safeValue);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
eleventyConfig.addPassthroughCopy('assets');
|
||||
eleventyConfig.addPassthroughCopy('fonts');
|
||||
|
|
@ -83,7 +119,7 @@ export default function (eleventyConfig) {
|
|||
});
|
||||
|
||||
// the article list navigation for section index pages
|
||||
eleventyConfig.addShortcode('sectionNav', function (collections) {
|
||||
eleventyConfig.addShortcode('sectionNav', function (collections, lang) {
|
||||
const navFilter = eleventyConfig.getFilter('eleventyNavigation');
|
||||
|
||||
// from the nav tree, find the current page's entry
|
||||
|
|
@ -95,9 +131,13 @@ export default function (eleventyConfig) {
|
|||
.map((child) => `<tr><td><a href="${child.url}">${child.title}</a></td></tr>`)
|
||||
.join('');
|
||||
|
||||
const i18n = this.ctx?.i18n || {};
|
||||
const currentLang = lang || this.ctx?.lang || 'en';
|
||||
const headingText = i18n[currentLang]?.find_out_more || 'Find out more in this section:';
|
||||
|
||||
return `<table class="table">
|
||||
<thead>
|
||||
<th>Find out more in this section:</th>
|
||||
<th>${headingText}</th>
|
||||
</thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>`;
|
||||
|
|
|
|||
Loading…
Reference in a new issue