jekyll-theme-zer0 1.26.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +172 -1
- data/README.md +10 -27
- data/_data/authors.yml +4 -3
- data/_data/backlog.yml +28 -0
- data/_data/features.yml +65 -18
- data/_data/i18n/languages.yml +36 -0
- data/_data/theme-manifest.yml +0 -2
- data/_data/ui-text.yml +36 -246
- data/_includes/README.md +4 -0
- data/_includes/components/author-avatar-url.html +4 -2
- data/_includes/components/env-switcher.html +3 -1
- data/_includes/components/language-toggle.html +81 -0
- data/_includes/components/search-modal.html +2 -2
- data/_includes/components/shortcuts-modal.html +1 -1
- data/_includes/components/translation-notice.html +27 -0
- data/_includes/content/intro.html +16 -15
- data/_includes/core/footer.html +9 -4
- data/_includes/core/head.html +9 -0
- data/_includes/core/header.html +9 -4
- data/_includes/core/hreflang.html +33 -0
- data/_includes/core/i18n.html +36 -0
- data/_includes/navigation/breadcrumbs.html +1 -1
- data/_includes/navigation/navbar.html +5 -4
- data/_includes/navigation/sidebar-right.html +3 -2
- data/_includes/navigation/unified-drawer.html +1 -1
- data/_layouts/article.html +7 -1
- data/_layouts/default.html +5 -3
- data/_layouts/news.html +4 -2
- data/_layouts/root.html +14 -8
- data/_layouts/section.html +4 -2
- data/_sass/core/_obsidian.scss +9 -1
- data/_sass/layouts/_navbar-extras.scss +6 -1
- data/assets/js/obsidian-graph.js +5 -1
- data/scripts/README.md +20 -26
- data/scripts/bin/audit-consumer +1 -1
- data/scripts/bin/manifest +0 -1
- data/scripts/bin/sync-plugins +0 -1
- data/scripts/dev/rasterize-svg.js +65 -0
- data/scripts/features/generate-preview-images +49 -1390
- data/scripts/features/install-preview-generator +55 -33
- data/scripts/install/README.md +9 -20
- data/scripts/install/ai/prompts/wizard.system.md +8 -17
- data/scripts/lib/README.md +1 -5
- data/scripts/lib/install/deploy/README.md +3 -9
- data/scripts/lib/preview_generator.py +2261 -1341
- data/scripts/translate.rb +1114 -0
- metadata +9 -3
- data/_plugins/preview_image_generator.rb +0 -351
data/_data/ui-text.yml
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
# User interface text and labels
|
|
2
|
-
# Used by:
|
|
3
|
-
#
|
|
1
|
+
# User interface text and labels — ENGLISH SOURCE OF TRUTH
|
|
2
|
+
# Used by: _includes/core/i18n.html (and through it, layouts/includes)
|
|
3
|
+
#
|
|
4
|
+
# Multilingual model (ZER0-078): this file holds ONLY the English strings.
|
|
5
|
+
# Alternate languages are NOT hand-maintained here anymore — they are
|
|
6
|
+
# machine-generated into _data/i18n/<lang>.yml by scripts/translate.rb
|
|
7
|
+
# (Claude Code OAuth) via .github/workflows/translate.yml. To change a
|
|
8
|
+
# translated label, edit the English string here and let the translate
|
|
9
|
+
# workflow regenerate the alternates. Languages are configured under
|
|
10
|
+
# `translation.languages` in _config.yml.
|
|
11
|
+
#
|
|
12
|
+
# (The former hand-written es/fr/de/ar blocks were removed in favor of the
|
|
13
|
+
# generated pipeline — see docs/systems/multilingual-translation.md.)
|
|
4
14
|
|
|
5
15
|
# English (default)
|
|
6
16
|
# -----------------
|
|
@@ -8,6 +18,7 @@ en: &DEFAULT_EN
|
|
|
8
18
|
skip_links : "Skip links"
|
|
9
19
|
skip_primary_nav : "Skip to primary navigation"
|
|
10
20
|
skip_content : "Skip to content"
|
|
21
|
+
skip_main_content : "Skip to main content"
|
|
11
22
|
skip_footer : "Skip to footer"
|
|
12
23
|
page : "Page"
|
|
13
24
|
pagination_previous : "Previous"
|
|
@@ -79,7 +90,28 @@ en: &DEFAULT_EN
|
|
|
79
90
|
shortcuts_toggle_sidebar : "Toggle sidebar"
|
|
80
91
|
shortcuts_toggle_toc : "Toggle table of contents"
|
|
81
92
|
shortcuts_show_help : "Show this help"
|
|
82
|
-
|
|
93
|
+
# Header / navbar chrome (ZER0-078)
|
|
94
|
+
home_label : "Home"
|
|
95
|
+
main_nav_label : "Main Navigation"
|
|
96
|
+
search_button_label : "Search"
|
|
97
|
+
settings_button_label : "Settings"
|
|
98
|
+
toc_title : "Table of Contents"
|
|
99
|
+
# Article intro / metadata chrome (ZER0-078)
|
|
100
|
+
published_label : "Published"
|
|
101
|
+
updated_label : "Updated"
|
|
102
|
+
reading_time_label : "Estimated reading time:"
|
|
103
|
+
reading_time_minute : "minute"
|
|
104
|
+
share_label : "Share"
|
|
105
|
+
copy_link_label : "Copy Link"
|
|
106
|
+
improve_label : "Improve"
|
|
107
|
+
edit_on_github : "Edit on GitHub"
|
|
108
|
+
# Language toggle & machine translation (ZER0-078)
|
|
109
|
+
lang_toggle_label : "Language"
|
|
110
|
+
lang_toggle_aria : "Change language"
|
|
111
|
+
lang_not_available : "Not yet translated"
|
|
112
|
+
machine_translation_notice : "This page was translated automatically from the English original."
|
|
113
|
+
machine_translation_view_source : "View the original"
|
|
114
|
+
|
|
83
115
|
en-US:
|
|
84
116
|
<<: *DEFAULT_EN
|
|
85
117
|
en-CA:
|
|
@@ -88,245 +120,3 @@ en-GB:
|
|
|
88
120
|
<<: *DEFAULT_EN
|
|
89
121
|
en-AU:
|
|
90
122
|
<<: *DEFAULT_EN
|
|
91
|
-
|
|
92
|
-
# Spanish
|
|
93
|
-
# -------
|
|
94
|
-
es: &DEFAULT_ES
|
|
95
|
-
skip_links : "Saltar enlaces"
|
|
96
|
-
skip_primary_nav : "Saltar a navegación principal"
|
|
97
|
-
skip_content : "Saltar a contenido"
|
|
98
|
-
skip_footer : "Saltar a pie"
|
|
99
|
-
page : "Página"
|
|
100
|
-
pagination_previous : "Anterior"
|
|
101
|
-
pagination_next : "Siguiente"
|
|
102
|
-
breadcrumb_home_label : "Inicio"
|
|
103
|
-
breadcrumb_separator : "/"
|
|
104
|
-
menu_label : "Alternar menú"
|
|
105
|
-
search_label : "Alternar búsqueda"
|
|
106
|
-
toc_label : "En esta página"
|
|
107
|
-
ext_link_label : "Enlace directo"
|
|
108
|
-
less_than : "menos de"
|
|
109
|
-
minute_read : "minuto(s) de lectura"
|
|
110
|
-
share_on_label : "Compartir en"
|
|
111
|
-
meta_label :
|
|
112
|
-
tags_label : "Etiquetas:"
|
|
113
|
-
categories_label : "Categorías:"
|
|
114
|
-
date_label : "Actualizado:"
|
|
115
|
-
author_label : "Autor:"
|
|
116
|
-
comments_label : "Deja un comentario"
|
|
117
|
-
comments_title : "Comentarios"
|
|
118
|
-
more_label : "Ver más"
|
|
119
|
-
related_label : "Puede que también te interese"
|
|
120
|
-
follow_label : "Seguir:"
|
|
121
|
-
feed_label : "Feed"
|
|
122
|
-
powered_by : "Funciona con"
|
|
123
|
-
website_label : "Sitio web"
|
|
124
|
-
email_label : "Correo electrónico"
|
|
125
|
-
recent_posts : "Entradas recientes"
|
|
126
|
-
undefined_wpm : "El parámetro words_per_minute (palabras por minuto) no está definido en _config.yml"
|
|
127
|
-
comment_form_info : "Tu dirección de correo electrónico no se publicará. Los campos obligatorios están marcados"
|
|
128
|
-
comment_form_comment_label : "Comentario"
|
|
129
|
-
comment_form_md_info : "Puedes utilizar Markdown"
|
|
130
|
-
comment_form_name_label : "Nombre"
|
|
131
|
-
comment_form_email_label : "Dirección de correo electrónico"
|
|
132
|
-
comment_form_website_label : "Sitio web (opcional)"
|
|
133
|
-
comment_btn_submit : "Enviar comentario"
|
|
134
|
-
comment_btn_submitted : "Enviado"
|
|
135
|
-
comment_success_msg : "¡Gracias por tu comentario! Se mostrará en el sitio una vez que haya sido aprobado."
|
|
136
|
-
comment_error_msg : "Lo sentimos, ha habido un error con tu envío. Por favor, asegúrate de que todos los campos obligatorios se han completado e inténtalo de nuevo."
|
|
137
|
-
loading_label : "Cargando..."
|
|
138
|
-
search_label_text : "Introduce tu término de búsqueda..."
|
|
139
|
-
search_placeholder_text : "Introduce tu término de búsqueda..."
|
|
140
|
-
search_no_results : "Sin resultados"
|
|
141
|
-
results_found : "Resultado(s) encontrado(s)"
|
|
142
|
-
back_to_top : "Volver arriba"
|
|
143
|
-
# Statistics labels
|
|
144
|
-
stats_total_posts : "Total de Publicaciones"
|
|
145
|
-
stats_total_pages : "Total de Páginas"
|
|
146
|
-
stats_categories : "Categorías"
|
|
147
|
-
stats_tags : "Etiquetas"
|
|
148
|
-
stats_last_updated : "Última Actualización"
|
|
149
|
-
stats_overview : "Resumen"
|
|
150
|
-
stats_content_breakdown : "Desglose de Contenido"
|
|
151
|
-
# Theme-specific labels
|
|
152
|
-
theme_toggle : "Cambiar tema"
|
|
153
|
-
dark_mode : "Modo oscuro"
|
|
154
|
-
light_mode : "Modo claro"
|
|
155
|
-
view_source : "Ver código fuente"
|
|
156
|
-
edit_page : "Editar esta página"
|
|
157
|
-
report_issue : "Reportar un problema"
|
|
158
|
-
|
|
159
|
-
es-ES:
|
|
160
|
-
<<: *DEFAULT_ES
|
|
161
|
-
es-MX:
|
|
162
|
-
<<: *DEFAULT_ES
|
|
163
|
-
|
|
164
|
-
# French
|
|
165
|
-
# ------
|
|
166
|
-
fr: &DEFAULT_FR
|
|
167
|
-
skip_links : "Liens de saut"
|
|
168
|
-
skip_primary_nav : "Aller à la navigation principale"
|
|
169
|
-
skip_content : "Aller au contenu"
|
|
170
|
-
skip_footer : "Aller au pied de page"
|
|
171
|
-
page : "Page"
|
|
172
|
-
pagination_previous : "Précédent"
|
|
173
|
-
pagination_next : "Suivant"
|
|
174
|
-
breadcrumb_home_label : "Accueil"
|
|
175
|
-
breadcrumb_separator : "/"
|
|
176
|
-
menu_label : "Basculer le menu"
|
|
177
|
-
search_label : "Basculer la recherche"
|
|
178
|
-
toc_label : "Sur cette page"
|
|
179
|
-
ext_link_label : "Lien direct"
|
|
180
|
-
less_than : "moins de"
|
|
181
|
-
minute_read : "minute(s) de lecture"
|
|
182
|
-
share_on_label : "Partager sur"
|
|
183
|
-
meta_label :
|
|
184
|
-
tags_label : "Tags :"
|
|
185
|
-
categories_label : "Catégories :"
|
|
186
|
-
date_label : "Mis à jour :"
|
|
187
|
-
author_label : "Auteur :"
|
|
188
|
-
comments_label : "Laisser un commentaire"
|
|
189
|
-
comments_title : "Commentaires"
|
|
190
|
-
more_label : "En savoir plus"
|
|
191
|
-
related_label : "Vous pourriez aussi aimer"
|
|
192
|
-
follow_label : "Suivre :"
|
|
193
|
-
feed_label : "Flux"
|
|
194
|
-
powered_by : "Propulsé par"
|
|
195
|
-
website_label : "Site web"
|
|
196
|
-
email_label : "Email"
|
|
197
|
-
recent_posts : "Articles récents"
|
|
198
|
-
back_to_top : "Retour en haut"
|
|
199
|
-
# Statistics labels
|
|
200
|
-
stats_total_posts : "Total des Publications"
|
|
201
|
-
stats_total_pages : "Total des Pages"
|
|
202
|
-
stats_categories : "Catégories"
|
|
203
|
-
stats_tags : "Tags"
|
|
204
|
-
stats_last_updated : "Dernière Mise à Jour"
|
|
205
|
-
stats_overview : "Aperçu"
|
|
206
|
-
stats_content_breakdown : "Répartition du Contenu"
|
|
207
|
-
# Theme-specific labels
|
|
208
|
-
theme_toggle : "Changer de thème"
|
|
209
|
-
dark_mode : "Mode sombre"
|
|
210
|
-
light_mode : "Mode clair"
|
|
211
|
-
view_source : "Voir la source"
|
|
212
|
-
edit_page : "Modifier cette page"
|
|
213
|
-
report_issue : "Signaler un problème"
|
|
214
|
-
|
|
215
|
-
fr-FR:
|
|
216
|
-
<<: *DEFAULT_FR
|
|
217
|
-
fr-CA:
|
|
218
|
-
<<: *DEFAULT_FR
|
|
219
|
-
|
|
220
|
-
# German
|
|
221
|
-
# ------
|
|
222
|
-
de: &DEFAULT_DE
|
|
223
|
-
skip_links : "Links überspringen"
|
|
224
|
-
skip_primary_nav : "Zur Hauptnavigation springen"
|
|
225
|
-
skip_content : "Zum Inhalt springen"
|
|
226
|
-
skip_footer : "Zur Fußzeile springen"
|
|
227
|
-
page : "Seite"
|
|
228
|
-
pagination_previous : "Zurück"
|
|
229
|
-
pagination_next : "Weiter"
|
|
230
|
-
breadcrumb_home_label : "Startseite"
|
|
231
|
-
breadcrumb_separator : "/"
|
|
232
|
-
menu_label : "Menü umschalten"
|
|
233
|
-
search_label : "Suche umschalten"
|
|
234
|
-
toc_label : "Auf dieser Seite"
|
|
235
|
-
ext_link_label : "Direktlink"
|
|
236
|
-
less_than : "weniger als"
|
|
237
|
-
minute_read : "Minute(n) Lesezeit"
|
|
238
|
-
share_on_label : "Teilen auf"
|
|
239
|
-
meta_label :
|
|
240
|
-
tags_label : "Tags:"
|
|
241
|
-
categories_label : "Kategorien:"
|
|
242
|
-
date_label : "Aktualisiert:"
|
|
243
|
-
author_label : "Autor:"
|
|
244
|
-
comments_label : "Einen Kommentar hinterlassen"
|
|
245
|
-
comments_title : "Kommentare"
|
|
246
|
-
more_label : "Mehr erfahren"
|
|
247
|
-
related_label : "Das könnte Sie auch interessieren"
|
|
248
|
-
follow_label : "Folgen:"
|
|
249
|
-
feed_label : "Feed"
|
|
250
|
-
powered_by : "Betrieben mit"
|
|
251
|
-
website_label : "Webseite"
|
|
252
|
-
email_label : "E-Mail"
|
|
253
|
-
recent_posts : "Neueste Beiträge"
|
|
254
|
-
back_to_top : "Nach oben"
|
|
255
|
-
# Statistics labels
|
|
256
|
-
stats_total_posts : "Gesamte Beiträge"
|
|
257
|
-
stats_total_pages : "Gesamte Seiten"
|
|
258
|
-
stats_categories : "Kategorien"
|
|
259
|
-
stats_tags : "Tags"
|
|
260
|
-
stats_last_updated : "Zuletzt Aktualisiert"
|
|
261
|
-
stats_overview : "Übersicht"
|
|
262
|
-
stats_content_breakdown : "Inhaltsaufschlüsselung"
|
|
263
|
-
# Theme-specific labels
|
|
264
|
-
theme_toggle : "Thema wechseln"
|
|
265
|
-
dark_mode : "Dunkler Modus"
|
|
266
|
-
light_mode : "Heller Modus"
|
|
267
|
-
view_source : "Quelle ansehen"
|
|
268
|
-
edit_page : "Diese Seite bearbeiten"
|
|
269
|
-
report_issue : "Ein Problem melden"
|
|
270
|
-
|
|
271
|
-
de-DE:
|
|
272
|
-
<<: *DEFAULT_DE
|
|
273
|
-
de-AT:
|
|
274
|
-
<<: *DEFAULT_DE
|
|
275
|
-
de-CH:
|
|
276
|
-
<<: *DEFAULT_DE
|
|
277
|
-
|
|
278
|
-
# Arabic
|
|
279
|
-
# ------
|
|
280
|
-
ar: &DEFAULT_AR
|
|
281
|
-
skip_links : "تخطي الروابط"
|
|
282
|
-
skip_primary_nav : "انتقل إلى التنقل الرئيسي"
|
|
283
|
-
skip_content : "انتقل إلى المحتوى"
|
|
284
|
-
skip_footer : "انتقل إلى التذييل"
|
|
285
|
-
page : "صفحة"
|
|
286
|
-
pagination_previous : "السابق"
|
|
287
|
-
pagination_next : "التالي"
|
|
288
|
-
breadcrumb_home_label : "الرئيسية"
|
|
289
|
-
breadcrumb_separator : "/"
|
|
290
|
-
menu_label : "تبديل القائمة"
|
|
291
|
-
search_label : "تبديل البحث"
|
|
292
|
-
toc_label : "في هذه الصفحة"
|
|
293
|
-
ext_link_label : "رابط مباشر"
|
|
294
|
-
less_than : "أقل من"
|
|
295
|
-
minute_read : "دقيقة للقراءة"
|
|
296
|
-
share_on_label : "مشاركة على"
|
|
297
|
-
meta_label :
|
|
298
|
-
tags_label : "الوسوم:"
|
|
299
|
-
categories_label : "الأقسام:"
|
|
300
|
-
date_label : "تاريخ التحديث:"
|
|
301
|
-
author_label : "الكاتب:"
|
|
302
|
-
comments_label : "اترك تعليقاً"
|
|
303
|
-
comments_title : "التعليقات"
|
|
304
|
-
more_label : "اقرأ المزيد"
|
|
305
|
-
related_label : "قد يعجبك أيضاً"
|
|
306
|
-
follow_label : "تابعنا:"
|
|
307
|
-
feed_label : "الخلاصة"
|
|
308
|
-
powered_by : "يعمل بواسطة"
|
|
309
|
-
website_label : "الموقع"
|
|
310
|
-
email_label : "البريد الإلكتروني"
|
|
311
|
-
recent_posts : "المنشورات الأخيرة"
|
|
312
|
-
back_to_top : "العودة للأعلى"
|
|
313
|
-
# Statistics labels
|
|
314
|
-
stats_total_posts : "إجمالي المنشورات"
|
|
315
|
-
stats_total_pages : "إجمالي الصفحات"
|
|
316
|
-
stats_categories : "الأقسام"
|
|
317
|
-
stats_tags : "الوسوم"
|
|
318
|
-
stats_last_updated : "آخر تحديث"
|
|
319
|
-
stats_overview : "نظرة عامة"
|
|
320
|
-
stats_content_breakdown : "تفصيل المحتوى"
|
|
321
|
-
# Theme-specific labels
|
|
322
|
-
theme_toggle : "تبديل المظهر"
|
|
323
|
-
dark_mode : "الوضع الداكن"
|
|
324
|
-
light_mode : "الوضع الفاتح"
|
|
325
|
-
view_source : "عرض المصدر"
|
|
326
|
-
edit_page : "تحرير هذه الصفحة"
|
|
327
|
-
report_issue : "الإبلاغ عن مشكلة"
|
|
328
|
-
|
|
329
|
-
ar-SA:
|
|
330
|
-
<<: *DEFAULT_AR
|
|
331
|
-
ar-EG:
|
|
332
|
-
<<: *DEFAULT_AR
|
data/_includes/README.md
CHANGED
|
@@ -12,6 +12,8 @@ Essential layout components that form the foundation of the site:
|
|
|
12
12
|
- `header.html` - Main site header with navigation
|
|
13
13
|
- `footer.html` - Site footer (if exists)
|
|
14
14
|
- `branding.html` - Site branding and title display
|
|
15
|
+
- `i18n.html` - Resolves the per-page-language UI-string set into the `ui` variable (generated translations fall back to English)
|
|
16
|
+
- `hreflang.html` - `<link rel="alternate" hreflang>` tags for pages with machine-generated translations
|
|
15
17
|
|
|
16
18
|
### `navigation/`
|
|
17
19
|
|
|
@@ -40,6 +42,8 @@ Analytics and tracking integrations:
|
|
|
40
42
|
Reusable UI components and widgets:
|
|
41
43
|
|
|
42
44
|
- `searchbar.html` - Deprecated search stub (superseded by `search-modal.html`)
|
|
45
|
+
- `language-toggle.html` - Navbar dropdown switching between the English original and its machine-generated translations
|
|
46
|
+
- `translation-notice.html` - Disclosure banner on machine-translated pages linking back to the English original
|
|
43
47
|
- `powered-by.html` - "Powered by" credits display
|
|
44
48
|
- `quick-index.html` - Quick page index
|
|
45
49
|
- `dev-shortcuts.html` - Developer shortcuts
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
reference an external avatar, e.g. a GitHub one:
|
|
11
11
|
avatar: "https://avatars.githubusercontent.com/u/10567847?v=4"
|
|
12
12
|
2. `avatar` is a relative path (e.g. /images/authors/me.png) — prefixed with
|
|
13
|
-
the site's
|
|
13
|
+
the site's public_folder and passed through relative_url, so baseurl is
|
|
14
|
+
applied and the slashes never double into a protocol-relative //… URL
|
|
15
|
+
(even when public_folder and/or baseurl are empty or unset).
|
|
14
16
|
3. No `avatar`, but a `github` handle is set — falls back to that account's
|
|
15
17
|
GitHub avatar: https://github.com/<handle>.png
|
|
16
18
|
4. Otherwise — empty output (the caller renders a fallback icon).
|
|
@@ -22,7 +24,7 @@
|
|
|
22
24
|
{%- endcomment -%}
|
|
23
25
|
{%- assign _avatar = include.data.avatar -%}
|
|
24
26
|
{%- if _avatar and _avatar != "" -%}
|
|
25
|
-
{%- if _avatar contains "://" -%}{{ _avatar }}{%- else -%}{
|
|
27
|
+
{%- if _avatar contains "://" -%}{{ _avatar }}{%- else -%}{%- capture _avatar_path -%}/{{ site.public_folder }}{{ _avatar }}{%- endcapture -%}{{ _avatar_path | replace: "//", "/" | relative_url }}{%- endif -%}
|
|
26
28
|
{%- elsif include.data.github and include.data.github != "" -%}
|
|
27
29
|
https://github.com/{{ include.data.github }}.png
|
|
28
30
|
{%- endif -%}
|
|
@@ -132,7 +132,8 @@
|
|
|
132
132
|
</div>
|
|
133
133
|
</li>
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
{% unless is_production %}
|
|
136
|
+
<!-- Development URL (dev builds only — never leak localhost into production HTML) -->
|
|
136
137
|
<li class="list-group-item d-flex justify-content-between align-items-center gap-2">
|
|
137
138
|
<div class="d-flex align-items-center min-w-0">
|
|
138
139
|
<span class="badge bg-warning text-dark me-2">Dev</span>
|
|
@@ -162,6 +163,7 @@
|
|
|
162
163
|
</button>
|
|
163
164
|
</div>
|
|
164
165
|
</li>
|
|
166
|
+
{% endunless %}
|
|
165
167
|
|
|
166
168
|
{% if site.repository %}
|
|
167
169
|
<!-- GitHub Source -->
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{% comment %} Feature: ZER0-078 {% endcomment %}
|
|
2
|
+
{%- comment -%}
|
|
3
|
+
Component: language-toggle
|
|
4
|
+
Path: _includes/components/language-toggle.html
|
|
5
|
+
Purpose: Navbar dropdown that switches between the English original and
|
|
6
|
+
its machine-generated translations (scripts/translate.rb).
|
|
7
|
+
Shows the source language plus every configured target
|
|
8
|
+
language; targets without a generated translation for the
|
|
9
|
+
current page render as disabled entries.
|
|
10
|
+
Params: none — reads page.lang / page.url / page.translation_source_url
|
|
11
|
+
and the generated _data/i18n/manifest.yml.
|
|
12
|
+
Depends on: site.translation.* config, site.data.i18n.manifest,
|
|
13
|
+
_data/i18n/languages.yml (display names), core/i18n.html,
|
|
14
|
+
Bootstrap 5 dropdown, Bootstrap Icons (bi-translate)
|
|
15
|
+
Notes: Renders nothing when translation is disabled or no target
|
|
16
|
+
languages are configured. The chosen language is remembered in
|
|
17
|
+
localStorage("zer0-lang") for future UX (no auto-redirect).
|
|
18
|
+
Must NOT be include_cached — the menu is page-dependent.
|
|
19
|
+
{%- endcomment -%}
|
|
20
|
+
{%- assign _lt_langs = site.translation.languages -%}
|
|
21
|
+
{%- if site.translation.enabled and _lt_langs and _lt_langs.size > 0 -%}
|
|
22
|
+
{%- include core/i18n.html -%}
|
|
23
|
+
{%- assign _lt_source = site.translation.source_lang | default: 'en' -%}
|
|
24
|
+
{%- assign _lt_current = page.lang | default: _lt_source -%}
|
|
25
|
+
{%- if _lt_current == _lt_source -%}
|
|
26
|
+
{%- assign _lt_src_url = page.url -%}
|
|
27
|
+
{%- else -%}
|
|
28
|
+
{%- assign _lt_src_url = page.translation_source_url -%}
|
|
29
|
+
{%- endif -%}
|
|
30
|
+
{%- assign _lt_entry = site.data.i18n.manifest.pages[_lt_src_url] -%}
|
|
31
|
+
{%- assign _lt_names = site.data.i18n.languages -%}
|
|
32
|
+
{%- assign _lt_source_name = _lt_names[_lt_source].native | default: _lt_source -%}
|
|
33
|
+
<div class="dropdown nav-lang-toggle" id="zer0-lang-toggle">
|
|
34
|
+
<button
|
|
35
|
+
class="btn nav-lang-button dropdown-toggle"
|
|
36
|
+
type="button"
|
|
37
|
+
id="langToggleButton"
|
|
38
|
+
data-bs-toggle="dropdown"
|
|
39
|
+
aria-expanded="false"
|
|
40
|
+
aria-label="{{ ui.lang_toggle_aria | default: 'Change language' }}"
|
|
41
|
+
title="{{ ui.lang_toggle_label | default: 'Language' }}"
|
|
42
|
+
>
|
|
43
|
+
<i class="{{ site.default_icon }} bi-translate" aria-hidden="true"></i>
|
|
44
|
+
<span class="nav-link-text d-none d-xl-inline ms-1 text-uppercase">{{ _lt_current | slice: 0, 2 }}</span>
|
|
45
|
+
</button>
|
|
46
|
+
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="langToggleButton">
|
|
47
|
+
<li>
|
|
48
|
+
{%- if _lt_current == _lt_source %}
|
|
49
|
+
<span class="dropdown-item active" aria-current="true">{{ _lt_source_name }}</span>
|
|
50
|
+
{%- else %}
|
|
51
|
+
<a class="dropdown-item" data-lang="{{ _lt_source }}" hreflang="{{ _lt_source }}" href="{{ _lt_src_url | default: '/' | relative_url }}">{{ _lt_source_name }}</a>
|
|
52
|
+
{%- endif %}
|
|
53
|
+
</li>
|
|
54
|
+
{%- for _lt_lang in _lt_langs -%}
|
|
55
|
+
{%- assign _lt_name = _lt_names[_lt_lang].native | default: _lt_lang -%}
|
|
56
|
+
{%- assign _lt_t = _lt_entry[_lt_lang] %}
|
|
57
|
+
<li>
|
|
58
|
+
{%- if _lt_current == _lt_lang %}
|
|
59
|
+
<span class="dropdown-item active" aria-current="true">{{ _lt_name }}</span>
|
|
60
|
+
{%- elsif _lt_t %}
|
|
61
|
+
<a class="dropdown-item" data-lang="{{ _lt_lang }}" hreflang="{{ _lt_lang }}" href="{{ _lt_t.url | relative_url }}">{{ _lt_name }}</a>
|
|
62
|
+
{%- else %}
|
|
63
|
+
<span class="dropdown-item disabled" aria-disabled="true" title="{{ ui.lang_not_available | default: 'Not yet translated' }}">{{ _lt_name }} <small class="text-body-secondary">({{ ui.lang_not_available | default: 'Not yet translated' }})</small></span>
|
|
64
|
+
{%- endif %}
|
|
65
|
+
</li>
|
|
66
|
+
{%- endfor %}
|
|
67
|
+
</ul>
|
|
68
|
+
</div>
|
|
69
|
+
<script>
|
|
70
|
+
// Remember the visitor's language pick (no auto-redirect — just preference).
|
|
71
|
+
(function () {
|
|
72
|
+
var toggle = document.getElementById('zer0-lang-toggle');
|
|
73
|
+
if (!toggle) return;
|
|
74
|
+
toggle.addEventListener('click', function (event) {
|
|
75
|
+
var item = event.target.closest('a.dropdown-item[data-lang]');
|
|
76
|
+
if (!item) return;
|
|
77
|
+
try { localStorage.setItem('zer0-lang', item.getAttribute('data-lang')); } catch (e) { /* private mode */ }
|
|
78
|
+
});
|
|
79
|
+
})();
|
|
80
|
+
</script>
|
|
81
|
+
{%- endif -%}
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
===================================================================
|
|
19
19
|
-->
|
|
20
20
|
|
|
21
|
-
{%-
|
|
22
|
-
{%- assign search_placeholder =
|
|
21
|
+
{%- include core/i18n.html lang=include.lang -%}
|
|
22
|
+
{%- assign search_placeholder = ui.search_placeholder_text | default: "Enter your search term..." -%}
|
|
23
23
|
{%- comment -%}
|
|
24
24
|
Existence-gate the form action to /sitemap/ only when that page is present
|
|
25
25
|
in the build (mirroring the footer Quick Links guard, and the section-sidebar
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
is a no-op when the element is absent.
|
|
15
15
|
===================================================================
|
|
16
16
|
{%- endcomment -%}
|
|
17
|
-
{%-
|
|
17
|
+
{%- include core/i18n.html lang=include.lang -%}
|
|
18
18
|
<div class="modal fade" id="zer0-shortcuts-modal" tabindex="-1"
|
|
19
19
|
aria-labelledby="zer0-shortcuts-modal-title" aria-hidden="true">
|
|
20
20
|
<div class="modal-dialog modal-dialog-centered">
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{% comment %} Feature: ZER0-078 {% endcomment %}
|
|
2
|
+
{%- comment -%}
|
|
3
|
+
Component: translation-notice
|
|
4
|
+
Path: _includes/components/translation-notice.html
|
|
5
|
+
Purpose: Disclosure banner shown at the top of machine-generated
|
|
6
|
+
translations: tells the reader the page was translated
|
|
7
|
+
automatically and links back to the English original.
|
|
8
|
+
Params: none — renders only when page.machine_translated is true
|
|
9
|
+
(set by scripts/translate.rb on generated pages).
|
|
10
|
+
Depends on: core/i18n.html, Bootstrap 5 alert, Bootstrap Icons
|
|
11
|
+
Notes: No-op on every human-authored page, so it is safe to include
|
|
12
|
+
unconditionally from the root layout.
|
|
13
|
+
{%- endcomment -%}
|
|
14
|
+
{%- if page.machine_translated -%}
|
|
15
|
+
{%- include core/i18n.html -%}
|
|
16
|
+
<div class="container-xl mt-3 translation-notice">
|
|
17
|
+
<div class="alert alert-info d-flex align-items-start gap-2 mb-0" role="note">
|
|
18
|
+
<i class="bi bi-translate flex-shrink-0 mt-1" aria-hidden="true"></i>
|
|
19
|
+
<span>
|
|
20
|
+
{{ ui.machine_translation_notice | default: "This page was translated automatically from the English original." }}
|
|
21
|
+
{%- if page.translation_source_url %}
|
|
22
|
+
<a class="alert-link" href="{{ page.translation_source_url | relative_url }}" hreflang="{{ site.translation.source_lang | default: 'en' }}">{{ ui.machine_translation_view_source | default: "View the original" }}</a>
|
|
23
|
+
{%- endif %}
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
{%- endif -%}
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
- Responsive flex layout: full-width content block, frosted metadata footer with reading time and action buttons
|
|
13
13
|
- Balanced padding and typography hierarchy via bd-intro-* classes
|
|
14
14
|
- Metadata row: author, dates, category, difficulty, tags, view source
|
|
15
|
-
-->
|
|
15
|
+
-->
|
|
16
|
+
{%- include core/i18n.html -%}
|
|
16
17
|
{% if page.collection %}
|
|
17
18
|
{% capture page_dir %}{{ site.collections_dir }}{% endcapture %}
|
|
18
19
|
{% assign testvar = "collection exists" %}
|
|
@@ -133,7 +134,7 @@
|
|
|
133
134
|
{% endif %}
|
|
134
135
|
|
|
135
136
|
<div class="bd-intro" style="
|
|
136
|
-
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('{{ preview_path
|
|
137
|
+
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('{{ preview_path }}') no-repeat center center / cover;">
|
|
137
138
|
|
|
138
139
|
<div class="bd-intro-inner">
|
|
139
140
|
<div class="bd-intro-content">
|
|
@@ -161,7 +162,7 @@
|
|
|
161
162
|
<li class="bd-intro-meta-item">
|
|
162
163
|
<i class="bi bi-calendar3 bd-intro-meta-icon" aria-hidden="true"></i>
|
|
163
164
|
<time datetime="{{ intro_published | date_to_xmlschema }}" itemprop="datePublished">
|
|
164
|
-
Published {{ intro_published | date: "%b %d, %Y" }}
|
|
165
|
+
{{ ui.published_label | default: "Published" }} {{ intro_published | date: "%b %d, %Y" }}
|
|
165
166
|
</time>
|
|
166
167
|
</li>
|
|
167
168
|
{% endif %}
|
|
@@ -170,7 +171,7 @@
|
|
|
170
171
|
<li class="bd-intro-meta-item">
|
|
171
172
|
<i class="bi bi-pencil bd-intro-meta-icon" aria-hidden="true"></i>
|
|
172
173
|
<time datetime="{{ intro_updated | date_to_xmlschema }}" itemprop="dateModified">
|
|
173
|
-
Updated {{ intro_updated | date: "%b %d, %Y" }}
|
|
174
|
+
{{ ui.updated_label | default: "Updated" }} {{ intro_updated | date: "%b %d, %Y" }}
|
|
174
175
|
</time>
|
|
175
176
|
</li>
|
|
176
177
|
{% endif %}
|
|
@@ -212,7 +213,7 @@
|
|
|
212
213
|
{% if intro_source_url %}
|
|
213
214
|
<li class="bd-intro-meta-item bd-intro-meta-item--source d-none d-md-inline-flex">
|
|
214
215
|
<i class="bi bi-file-earmark-code bd-intro-meta-icon" aria-hidden="true"></i>
|
|
215
|
-
<a class="bd-intro-meta-link" href="{{ intro_source_url }}" target="_blank" rel="noopener noreferrer">View source</a>
|
|
216
|
+
<a class="bd-intro-meta-link" href="{{ intro_source_url }}" target="_blank" rel="noopener noreferrer">{{ ui.view_source | default: "View source" }}</a>
|
|
216
217
|
</li>
|
|
217
218
|
{% endif %}
|
|
218
219
|
</ul>
|
|
@@ -221,9 +222,9 @@
|
|
|
221
222
|
<p class="bd-intro-meta">
|
|
222
223
|
<i class="bi bi-clock me-1" aria-hidden="true"></i>
|
|
223
224
|
{% if page.estimated_reading_time %}
|
|
224
|
-
Estimated reading time: {{ page.estimated_reading_time }}
|
|
225
|
+
{{ ui.reading_time_label | default: "Estimated reading time:" }} {{ page.estimated_reading_time }}
|
|
225
226
|
{% else %}
|
|
226
|
-
Estimated reading time: {{ intro_read_mins }} minute{% if intro_read_mins != 1 %}s{% endif %}
|
|
227
|
+
{{ ui.reading_time_label | default: "Estimated reading time:" }} {{ intro_read_mins }} {{ ui.reading_time_minute | default: "minute" }}{% if intro_read_mins != 1 %}s{% endif %}
|
|
227
228
|
{% endif %}
|
|
228
229
|
</p>
|
|
229
230
|
|
|
@@ -233,12 +234,12 @@
|
|
|
233
234
|
<div class="dropdown">
|
|
234
235
|
<button class="btn btn-sm btn-info dropdown-toggle" type="button" id="shareDropdownBottom" data-bs-toggle="dropdown" aria-expanded="false">
|
|
235
236
|
<i class="bi bi-share" aria-hidden="true"></i>
|
|
236
|
-
<span class="d-none d-sm-inline ms-1">Share</span>
|
|
237
|
+
<span class="d-none d-sm-inline ms-1">{{ ui.share_label | default: "Share" }}</span>
|
|
237
238
|
</button>
|
|
238
239
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="shareDropdownBottom">
|
|
239
240
|
<li>
|
|
240
241
|
<a class="dropdown-item" href="https://reddit.com/submit?url={{ page_absolute_url | uri_escape }}&title={{ page.title | uri_escape }}" target="_blank" rel="noopener noreferrer">
|
|
241
|
-
<i class="bi bi-reddit me-2"></i>Share on Reddit
|
|
242
|
+
<i class="bi bi-reddit me-2"></i>{{ ui.share_on_label | default: "Share on" }} Reddit
|
|
242
243
|
</a>
|
|
243
244
|
</li>
|
|
244
245
|
<li>
|
|
@@ -250,12 +251,12 @@
|
|
|
250
251
|
data-share-url="{{ page_absolute_url | escape }}"
|
|
251
252
|
data-share-title="{{ linkedin_share_title | escape }}"
|
|
252
253
|
data-share-description="{{ linkedin_share_description | escape }}">
|
|
253
|
-
<i class="bi bi-linkedin me-2"></i>Share on LinkedIn
|
|
254
|
+
<i class="bi bi-linkedin me-2"></i>{{ ui.share_on_label | default: "Share on" }} LinkedIn
|
|
254
255
|
</a>
|
|
255
256
|
</li>
|
|
256
257
|
<li>
|
|
257
|
-
<a class="dropdown-item" href="https://x.com/intent/tweet?url={{ site.url | append: page.url | url_encode }}&text={{ page.title | url_encode }}" target="_blank" aria-label="Share on X">
|
|
258
|
-
<i class="bi bi-twitter-x me-2"></i>Share on X
|
|
258
|
+
<a class="dropdown-item" href="https://x.com/intent/tweet?url={{ site.url | append: page.url | url_encode }}&text={{ page.title | url_encode }}" target="_blank" aria-label="{{ ui.share_on_label | default: 'Share on' }} X">
|
|
259
|
+
<i class="bi bi-twitter-x me-2"></i>{{ ui.share_on_label | default: "Share on" }} X
|
|
259
260
|
</a>
|
|
260
261
|
</li>
|
|
261
262
|
<li><hr class="dropdown-divider"></li>
|
|
@@ -266,7 +267,7 @@
|
|
|
266
267
|
the blocking alert() the legacy markup used.
|
|
267
268
|
{%- endcomment -%}
|
|
268
269
|
<button class="dropdown-item" type="button" data-copy="{{ site.url | append: page.url }}">
|
|
269
|
-
<i class="bi bi-clipboard me-2" aria-hidden="true"></i>Copy Link
|
|
270
|
+
<i class="bi bi-clipboard me-2" aria-hidden="true"></i>{{ ui.copy_link_label | default: "Copy Link" }}
|
|
270
271
|
</button>
|
|
271
272
|
</li>
|
|
272
273
|
</ul>
|
|
@@ -284,7 +285,7 @@
|
|
|
284
285
|
rel="noopener noreferrer"
|
|
285
286
|
title="Report a problem or suggest an improvement for this page">
|
|
286
287
|
<i class="bi bi-megaphone" aria-hidden="true"></i>
|
|
287
|
-
<span class="d-none d-sm-inline ms-1">Improve</span>
|
|
288
|
+
<span class="d-none d-sm-inline ms-1">{{ ui.improve_label | default: "Improve" }}</span>
|
|
288
289
|
</a>
|
|
289
290
|
{% endif %}
|
|
290
291
|
|
|
@@ -294,7 +295,7 @@
|
|
|
294
295
|
target="_blank"
|
|
295
296
|
rel="noopener noreferrer">
|
|
296
297
|
<i class="bi bi-github" aria-hidden="true"></i>
|
|
297
|
-
<span class="d-none d-sm-inline ms-1">Edit on GitHub</span>
|
|
298
|
+
<span class="d-none d-sm-inline ms-1">{{ ui.edit_on_github | default: "Edit on GitHub" }}</span>
|
|
298
299
|
</a>
|
|
299
300
|
</div>
|
|
300
301
|
</div>
|
data/_includes/core/footer.html
CHANGED
|
@@ -22,15 +22,20 @@
|
|
|
22
22
|
- site.powered_by: Framework and service credits
|
|
23
23
|
- site.links: Social media and affiliate links
|
|
24
24
|
- site.default_icon: Icon framework (Bootstrap Icons)
|
|
25
|
-
-
|
|
25
|
+
- core/i18n.html: UI strings (per-page language via the `lang` param)
|
|
26
26
|
- Bootstrap 5 classes provided by the theme
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
Params:
|
|
29
|
+
- lang (optional): page language. Root layout renders this include via
|
|
30
|
+
include_cached, so it MUST pass lang=page.lang for per-language caching.
|
|
31
|
+
|
|
28
32
|
Notes:
|
|
29
33
|
- Guard optional fields (address/phone/email) to avoid empty output
|
|
30
34
|
- Use relative_url for internal links to respect baseurl
|
|
31
35
|
===================================================================
|
|
32
36
|
-->
|
|
33
37
|
|
|
38
|
+
{%- include core/i18n.html lang=include.lang -%}
|
|
34
39
|
<footer class="bd-footer border-top" role="contentinfo">
|
|
35
40
|
{% assign footer_page_urls = site.html_pages | map: "url" | join: "|" | prepend: "|" | append: "|" %}
|
|
36
41
|
<!-- Powered by Row -->
|
|
@@ -40,7 +45,7 @@
|
|
|
40
45
|
</p>
|
|
41
46
|
<div class="footer-powered-by-row">
|
|
42
47
|
<span class="footer-powered-by-label" id="footer-powered-by-label">
|
|
43
|
-
{{
|
|
48
|
+
{{ ui.powered_by | default: "Powered by" }}:
|
|
44
49
|
</span>
|
|
45
50
|
<ul class="footer-powered-by-links list-unstyled mb-0"
|
|
46
51
|
aria-labelledby="footer-powered-by-label">
|
|
@@ -197,7 +202,7 @@
|
|
|
197
202
|
<li class="list-inline-item">
|
|
198
203
|
<a class="text-light text-decoration-none" href="{{ '/feed.xml' | relative_url }}">
|
|
199
204
|
<i class="{{ site.default_icon }} bi-rss" aria-hidden="true"></i>
|
|
200
|
-
<span class="d-none d-md-inline">{{
|
|
205
|
+
<span class="d-none d-md-inline">{{ ui.feed_label | default: "Feed" }}</span>
|
|
201
206
|
</a>
|
|
202
207
|
</li>
|
|
203
208
|
</ul>
|