Merge pull request #80 from welpo/feat/multilingual-support

add multilingual support
main
Óscar 2 years ago committed by GitHub
commit 3210de56d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
# tabi
A fast, lightweight, and modern [Zola](https://getzola.org) theme. It aims to be a personal page and home to blog posts.
A fast, lightweight, and modern [Zola](https://getzola.org) theme with multi-language support. It aims to be a personal page and home to blog posts.
See a live preview [here](https://welpo.github.io/tabi).
@ -14,6 +14,7 @@ tabi has a perfect score on Google's Lighthouse audit:
## Features
- [X] Multi-language support.
- [X] Dark and light themes. Defaults to the OS setting, with a switcher in the navigation bar.
- [X] Perfect Lighthouse score (Performance, Accessibility, Best Practices and SEO).
- [X] [KaTeX](https://katex.org/) support.
@ -27,8 +28,6 @@ tabi has a perfect score on Google's Lighthouse audit:
- [X] [Custom shortcodes](./templates/shortcodes/).
- [X] Customizable secure headers.
See the project's roadmap [here](https://github.com/users/welpo/projects/1).
## Quick start
```bash

@ -1,7 +1,6 @@
# Necessary stuff.
base_url = "https://welpo.github.io/tabi"
title = "~/tabi"
description = "tabi is a simple personal site and blogging theme for Zola."
description = "tabi is a fast, lightweight, and modern Zola theme with multi-language support, optional JavaScript, and a perfect Lighthouse score."
generate_feed = true
compile_sass = true
minify_html = true
@ -15,14 +14,98 @@ external_links_target_blank = true
highlight_code = true
highlight_theme = "css"
default_language = "en"
[languages.es]
title = "~/tabi"
description = "tabi es un tema de Zola rápido, liviano y moderno con JavaScript opcional y una puntuación perfecta en Lighthouse."
generate_feed = true
compile_sass = true
minify_html = true
taxonomies = [
{name = "tags", feed = true},
]
[languages.ca]
title = "~/tabi"
description = "tabi és un tema de Zola ràpid, lleuger i modern amb JavaScript opcional i una puntuació perfecta a Lighthouse."
generate_feed = true
compile_sass = true
minify_html = true
taxonomies = [
{name = "tags", feed = true},
]
[languages.es.translations]
language_name = "Español"
date_locale = "es_ES"
# Menu items.
blog = "blog"
archive = "archivo"
tags = "etiquetas"
words = "palabras"
projects = "proyectos"
# Other text.
tags_title = "Todas las etiquetas"
404_error = "La página que has solicitado parece no existir o aún no se ha traducido a tu idioma. Revisa la URL en busca de errores o"
go_home = "regresa a la página de inicio"
read_more = "Leer más"
all_posts = "Todas las entradas"
all_tags = "Todas las etiquetas"
min_read = "min de lectura"
powered_by = "Impulsado por"
and = "y"
post = "entrada"
posts = "entradas"
prev = "Anterior"
next = "Siguiente"
of = "de"
draft = "BORRADOR"
table_of_contents = "Tabla de contenido"
last_updated_on = "Última actualización el"
[languages.ca.translations]
language_name = "Català"
date_locale = "ca_ES"
# Menu items.
blog = "blog"
archive = "arxiu"
tags = "etiquetes"
words = "paraules"
projects = "projectes"
# Other text.
tags_title = "Totes les etiquetes"
404_error = "La pàgina que has sol·licitat sembla que no existeix o encara no s'ha traduït al teu idioma. Comprova l'URL per detectar errors o"
go_home = "torna a la pàgina d'inici"
read_more = "Llegir més"
all_posts = "Totes les entrades"
all_tags = "Totes les etiquetes"
min_read = "min de lectura"
powered_by = "Propulsat per"
and = "i"
post = "entrada"
posts = "entrades"
prev = "Anterior"
next = "Següent"
of = "de"
draft = "ESBORRANY"
table_of_contents = "Taula de contingut"
last_updated_on = "Última actualizació el"
[extra]
language_name.ca = "Català"
language_name.en = "English"
language_name.es = "Español"
# Enable JavaScript theme toggler for dark/light mode (and automatic switching).
# The default setting is the light theme.
theme_switcher = true
# Date format used when listing posts (main page, /blog section, tag posts list…)
# Default is "6th July 2049".
# Default is "6th July 2049" in English and "%d %B %Y" in other languages.
long_date_format = "%d %B %Y"
# Date format used for blog posts.
@ -43,10 +126,10 @@ favicon_emoji = "🌱"
headerImage = ""
menu = [
{ name = "blog", url = "$BASE_URL/blog" },
{ name = "archive", url = "$BASE_URL/archive" },
{ name = "tags", url = "$BASE_URL/tags" },
{ name = "projects", url = "$BASE_URL/projects" },
{ name = "blog", url = "blog/" },
{ name = "archive", url = "archive/" },
{ name = "tags", url = "tags/" },
{ name = "projects", url = "projects/" },
]
# The icons available can be found in "social_icons" in the "static" folder

@ -0,0 +1,13 @@
+++
path = "/"
title = "Publicacions recents"
sort_by = "date"
template = "section.html"
[extra]
header = {title = "Hola! Soc tabi~", img = "$BASE_URL/img/main.webp" }
section_path = "blog/_index.ca.md"
max_posts = 4
+++
tabi és un tema de [Zola](https://getzola.org) ràpid, lleuger i modern. Té com a objectiu ser una pàgina personal i llar d'entrades de blog. Compta amb una puntuació perfecta de Lighthouse, disseny responsive, tema fosc i clar, shortcodes personalitzats i molt més.

@ -0,0 +1,13 @@
+++
path = "/"
title = "Publicaciones recientes"
sort_by = "date"
template = "section.html"
[extra]
header = {title = "¡Hola! Soy tabi~", img = "$BASE_URL/img/main.webp" }
section_path = "blog/_index.es.md"
max_posts = 4
+++
tabi es un tema de [Zola](https://getzola.org) rápido, ligero y moderno. Su objetivo es ser una página personal y hogar para publicaciones de blogs. Cuenta con una puntuación perfecta en Lighthouse, diseño responsive, tema oscuro y claro, shortcodes personalizados y mucho más.

@ -0,0 +1,4 @@
+++
title = "Arxiu"
template = "archive.html"
+++

@ -0,0 +1,4 @@
+++
title = "Archivo"
template = "archive.html"
+++

@ -1,5 +1,4 @@
+++
title = "Archive"
path = "archive"
template = "archive.html"
+++

@ -0,0 +1,8 @@
+++
paginate_by = 5
path = "/blog"
title = "Blog"
sort_by = "date"
template = "section.html"
insert_anchor_links = "left"
+++

@ -0,0 +1,8 @@
+++
paginate_by = 5
path = "/blog"
title = "Blog"
sort_by = "date"
template = "section.html"
insert_anchor_links = "left"
+++

@ -0,0 +1,17 @@
+++
title = "Gairebé sense JavaScript"
date = 2023-01-06
updated = 2023-04-28
description = "JavaScript només s'utilitza quan HTML i CSS no són suficients."
[taxonomies]
tags = ["funcionalitat"]
+++
# JavaScript?
Aquest tema gairebé no utilitza JavaScript. Inclou uns ~900 bytes de fitxers `.js` amb la lògica per al canvi de tema clar/fosc, que es pot desactivar establint `theme_switcher = false` al fitxer `config.toml`.
La funcionalitat de KaTex, que requereix carregar un fitxer JavaScript de 274 KB, es pot activar per a publicacions específiques.
A part d'això, és un tema ràpid amb HTML i CSS. Tal i com hauria de ser (la major part de) la web :-)

@ -0,0 +1,17 @@
+++
title = "Casi sin JavaScript"
date = 2023-01-06
updated = 2023-04-28
description = "JavaScript solo se utiliza cuando HTML y CSS no son suficientes."
[taxonomies]
tags = ["funcionalidad"]
+++
# ¿JavaScript?
Este tema casi no utiliza JavaScript. Incluye ~900 bytes de código `.js` con la lógica para el interruptor de modo claro/oscuro, el cual puede ser desactivado configurando `theme_switcher = false` en el archivo `config.toml`.
El soporte de KaTeX, que requiere cargar un archivo JavaScript de 274 KB, puede ser activado para publicaciones específicas.
Aparte de esto, es un tema rápido con HTML y CSS. Como debería ser (en su mayoría) la web :-)

@ -0,0 +1,190 @@
+++
title = "Optimitza la càrrega amb un subconjunt de font personalitzat"
date = 2023-04-29
updated = 2023-05-25
description = "Aprèn com crear un subconjunt personalitzat que només inclogui els glifs necessaris."
[taxonomies]
tags = ["funcionalitat", "tutorial"]
+++
## El problema
Les fonts personalitzades causen parpelleig de text a Firefox. Per veure un gif i més detalls, mira [aquesta issue](https://github.com/welpo/tabi/issues/75).
## La solució
Per solucionar això, tabi carrega un subconjunt de glifs per a l'encapçalament. Donat que això augmenta lleugerament el temps de càrrega inicial, és una bona idea intentar minimitzar la mida d'aquest subconjunt.
Per defecte, tabi inclou fitxers de subconjunts per a caràcters en anglès i espanyol (amb alguns símbols). Aquests fitxers es carreguen quan la pàgina o el lloc web de Zola està en aquest idioma.
Per a una optimització addicional, pots crear un subconjunt de fonts personalitzat que només inclogui els caràcters utilitzats en el teu encapçalament.
## Requisits
Instal·la aquestes eines:
- [fonttools](https://github.com/fonttools/fonttools)
- [brotli](https://github.com/google/brotli)
Executa `pip install fonttools brotli` per instal·lar totes dues.
## L'script
El següent script pren un fitxer `config.toml` i un fitxer de font com a entrada, extreu els caràcters necessaris, crea un subconjunt de la font i genera un fitxer CSS que conté el subconjunt codificat en base64.
```bash
#!/usr/bin/env bash
usage() {
echo "Usage: $0 [--config | -c CONFIG_FILE] [--font | -f FONT_FILE] [--output | -o OUTPUT_PATH]"
echo
echo "Options:"
echo " --config, -c Path to the config.toml file."
echo " --font, -f Path to the font file."
echo " --output, -o Output path for the generated custom_subset.css file (default: current directory)"
echo " --help, -h Show this help message and exit"
}
# La sortida per defecte és el directori actual.
output_path="."
# Opcions de la línia de comandes.
while [ "$#" -gt 0 ]; do
case "$1" in
--config|-c)
config_file="$2"
shift 2
;;
--font|-f)
font_file="$2"
shift 2
;;
--output|-o)
output_path="$2"
shift 2
;;
--help|-h)
usage
exit 0
;;
*)
echo "Unknown option: $1"
usage
exit 1
;;
esac
done
# Comprova si s'han proporcionat les opcions -c i -f.
if [ -z "$config_file" ]; then
echo "Error: --config|-c option is required."
usage
exit 1
fi
if [ -z "$font_file" ]; then
echo "Error: --font|-f option is required."
usage
exit 1
fi
# Comprova si els fitxers de configuració i de font existeixen.
if [ ! -f "$config_file" ]; then
echo "Error: Config file '$config_file' not found."
exit 1
fi
if [ ! -f "$font_file" ]; then
echo "Error: Font file '$font_file' not found."
exit 1
fi
# Extreu el títol i els noms del menú del fitxer de configuració.
title=$(awk -F' = ' '/^title/{print $2}' "$config_file" | tr -d '"' | grep -v "atom feed")
menu_names=$(awk -F' = ' '/^menu/{f=1;next} /socials/{f=0} f && /name/{print $2}' "$config_file" | cut -d',' -f1 | tr -d '"' )
language_names=$(awk -F' = ' '/^language_name\./{print $2}' "$config_file" | tr -d '"' )
# Si el lloc web és multilingüe, obté les traduccions del menú.
if [ -n "$language_names" ]; then
for menu_name in $menu_names; do
# Find the line with the menu name inside a [languages.*.translations] section and get the translated menus.
menu_translation=$(awk -F' = ' "/\\[languages.*\\.translations\\]/{f=1;next} /^\\[/ {f=0} f && /$menu_name =/{print \$2}" "$config_file" | tr -d '"' )
# Add the found menu value to the translations string
menu_names+="$menu_translation"
done
fi
# Combina les cadenes extretes.
combined="$title$menu_names$language_names"
# Obté els caràcters únics.
unique_chars=$(echo "$combined" | grep -o . | sort -u | tr -d '\n')
# Crea un fitxer temporal per a subset.woff2.
temp_subset=$(mktemp)
# Crea el subconjunto.
pyftsubset "$font_file" \
--text="$unique_chars" \
--layout-features="*" --flavor="woff2" --output-file="$temp_subset" --with-zopfli
# Codifica en base64 el fitxer temporal subset.woff2 i crea el fitxer CSS.
base64_encoded_font=$(base64 -i "$temp_subset")
echo "@font-face{font-family:\"Inter Subset\";src:url(data:application/font-woff2;base64,$base64_encoded_font);}" > "$output_path/custom_subset.css"
# Elimina el fitxer temporal subset.woff2.
rm "$temp_subset"
```
## Ús
Guarda l'script a algun lloc com `~/bin/subset_font`. Fes-lo executable amb `chmod +x ~/bin/subset_font`.
Ara pots executar-lo amb les opcions requerides `--config` i `--font`:
```
~/bin/subset_font --config path/to/config.toml --font path/to/font.woff2
```
De forma predeterminada, això generarà un fitxer `custom_subset.css` al directori actual. Utilitza `-o` o `--output` per especificar una ruta diferent:
```
~/bin/subset_font -c path/to/config.toml -f path/to/font.woff2 -o path/to/output
```
Col·loca aquest fitxer `custom_subset.css` dins del directori `static/` del teu projecte de Zola.
## Automatització amb un Pre-commit Hook
És possible que canviïs el títol o les opcions del menú del teu lloc web, la qual cosa faria que el subconjunt personalitzat deixés de ser útil.
Per automatitzar el procés de creació d'aquest fitxer, pots integrar l'script en un ganxo (hook) pre-commit de Git que s'activi en detectar canvis al fitxer `config.toml`, executi l'script i guardi el fitxer CSS resultant al directori `static/` del teu lloc web.
1. Crea un fitxer `.git/hooks/pre-commit` al teu projecte de Git, si encara no existeix.
2. Fes-lo executable amb `chmod +x .git/hooks/pre-commit`.
3. Afegeix el següent codi al fitxer:
```bash
# Comprova si config.toml s'ha modificat.
if git diff --cached --name-only | grep -q "config.toml"; then
echo "config.toml modified. Running subset_font…"
# Executa l'script subset_font.
~/bin/subset_font -c config.toml -f static/fonts/Inter4.woff2 -o static/
# Afegeix el fitxer subset.css generat al commit.
git add static/custom_subset.css
fi
```
Asegura't de modificar l'script perquè coincideixi amb la ruta on has guardat l'script `subset_font`. Les rutes de configuració i font haurien de funcionar correctament amb la configuració predeterminada de tabi.
Ara, cada vegada que facis canvis al teu projecte de Git i facis commit, el ganxo pre-commit verificarà les modificacions al fitxer `config.toml` i executarà automàticament l'script `subset_font` per actualitzar el fitxer `custom_subset.css`.
Per cert, si t'interessa una forma d'actualitzar automàticament la data de les teves publicacions a Zola o comprimir automàticament els teus fitxers PNG, fes un cop d'ull a [aquesta publicació](https://osc.garden/ca/blog/zola-date-git-hook/).
Si desitges utilitzar tots els scripts alhora (compressió de fitxers PNG, actualització de la data i creació del subconjunt de fonts), combina el seu codi en un sol fitxer `.git/hooks/pre-commit`.

@ -0,0 +1,191 @@
+++
title = "Optimiza la carga con un subconjunto de fuente personalizado"
date = 2023-04-29
updated = 2023-05-25
description = "Aprende cómo crear un subconjunto personalizado que solo incluya los glifos necesarios."
[taxonomies]
tags = ["funcionalidad", "tutorial"]
+++
## El problema
Las fuentes personalizadas causan parpadeo de texto en Firefox. Para ver un gif y más detalles, mira [esta issue](https://github.com/welpo/tabi/issues/75).
## La solución
Para solucionar esto, tabi carga un subconjunto de glifos para el encabezado. Dado que esto aumenta ligeramente el tiempo de carga inicial, es una buena idea tratar de minimizar el tamaño de este subconjunto.
Por defecto, tabi incluye archivos de subconjuntos para caracteres en inglés y español (con algunos símbolos). Estos archivos se cargan cuando la página o el sitio de Zola está en ese idioma.
Para una optimización adicional, puedes crear un subconjunto de fuentes personalizado que solo incluya los caracteres utilizados en tu encabezado.
## Requisitos
Instala estas herramientas:
- [fonttools](https://github.com/fonttools/fonttools)
- [brotli](https://github.com/google/brotli)
Ejecuta `pip install fonttools brotli` para instalar ambas.
## El script
El script que sigue toma un archivo `config.toml` y un archivo de fuente como entrada, extrae los caracteres necesarios, crea un subconjunto de la fuente y genera un archivo CSS que contiene el subconjunto codificado en base64.
```bash
#!/usr/bin/env bash
usage() {
echo "Usage: $0 [--config | -c CONFIG_FILE] [--font | -f FONT_FILE] [--output | -o OUTPUT_PATH]"
echo
echo "Options:"
echo " --config, -c Path to the config.toml file."
echo " --font, -f Path to the font file."
echo " --output, -o Output path for the generated custom_subset.css file (default: current directory)"
echo " --help, -h Show this help message and exit"
}
# La salida predeterminada es el directorio actual.
output_path="."
# Analiza las opciones de la línea de comandos.
while [ "$#" -gt 0 ]; do
case "$1" in
--config|-c)
config_file="$2"
shift 2
;;
--font|-f)
font_file="$2"
shift 2
;;
--output|-o)
output_path="$2"
shift 2
;;
--help|-h)
usage
exit 0
;;
*)
echo "Unknown option: $1"
usage
exit 1
;;
esac
done
# Comprueba si se proporcionan las opciones -c y -f.
if [ -z "$config_file" ]; then
echo "Error: --config|-c option is required."
usage
exit 1
fi
if [ -z "$font_file" ]; then
echo "Error: --font|-f option is required."
usage
exit 1
fi
# Comprueba si existen los archivos de configuración y de fuentes.
if [ ! -f "$config_file" ]; then
echo "Error: Config file '$config_file' not found."
exit 1
fi
if [ ! -f "$font_file" ]; then
echo "Error: Font file '$font_file' not found."
exit 1
fi
# Extrae el título y los nombres de los menús del archivo de configuración.
title=$(awk -F' = ' '/^title/{print $2}' "$config_file" | tr -d '"' | grep -v "atom feed")
menu_names=$(awk -F' = ' '/^menu/{f=1;next} /socials/{f=0} f && /name/{print $2}' "$config_file" | cut -d',' -f1 | tr -d '"' )
language_names=$(awk -F' = ' '/^language_name\./{print $2}' "$config_file" | tr -d '"' )
# Si el sitio es multilingüe, obtiene las traducciones de los menús.
if [ -n "$language_names" ]; then
for menu_name in $menu_names; do
# Find the line with the menu name inside a [languages.*.translations] section and get the translated menus.
menu_translation=$(awk -F' = ' "/\\[languages.*\\.translations\\]/{f=1;next} /^\\[/ {f=0} f && /$menu_name =/{print \$2}" "$config_file" | tr -d '"' )
# Add the found menu value to the translations string
menu_names+="$menu_translation"
done
fi
# Combina las cadenas extraídas.
combined="$title$menu_names$language_names"
# Obtiene los caracteres únicos.
unique_chars=$(echo "$combined" | grep -o . | sort -u | tr -d '\n')
# Crea un archivo temporal para subset.woff2.
temp_subset=$(mktemp)
# Crea el subconjunto.
pyftsubset "$font_file" \
--text="$unique_chars" \
--layout-features="*" --flavor="woff2" --output-file="$temp_subset" --with-zopfli
# Codifica en Base64 el archivo temporal subset.woff2 y crea el archivo CSS.
base64_encoded_font=$(base64 -i "$temp_subset")
echo "@font-face{font-family:\"Inter Subset\";src:url(data:application/font-woff2;base64,$base64_encoded_font);}" > "$output_path/custom_subset.css"
# Elimina el archivo temporal subset.woff2.
rm "$temp_subset"
```
## Uso
Guarda el script en algún lugar como `~/bin/subset_font`. Hazlo ejecutable con `chmod +x ~/bin/subset_font`.
Ahora puedes ejecutarlo con las opciones requeridas `--config` y `--font`:
```
~/bin/subset_font --config path/to/config.toml --font path/to/font.woff2
```
De forma predeterminada, esto generará un archivo `custom_subset.css` en el directorio actual. Usa `-o` o `--output` para especificar una ruta diferente:
```
~/bin/subset_font -c path/to/config.toml -f path/to/font.woff2 -o path/to/output
```
Coloca este archivo `custom_subset.css` dentro del directorio `static/`.
## Automatización con un Pre-commit Hook
Es posible que cambies el título o las opciones del menú de tu sitio, lo que haría que el subconjunto personalizado deje de ser útil.
Para automatizar el proceso de creación de este archivo, puedes integrar el script en un gancho (hook) pre-commit de Git que se active al detectar cambios en el archivo `config.toml`, ejecute el script y guarde el archivo CSS resultante en el directorio `static/` de tu sitio.
1. Crea un archivo `.git/hooks/pre-commit` en tu proyecto de Git, si aún no existe.
2. Hazlo ejecutable con `chmod +x .git/hooks/pre-commit`.
3. Agrega el siguiente código al archivo:
```bash
# Comprueba si config.toml se ha modificado.
if git diff --cached --name-only | grep -q "config.toml"; then
echo "config.toml modified. Running subset_font…"
# Ejecuta el script subset_font.
~/bin/subset_font -c config.toml -f static/fonts/Inter4.woff2 -o static/
# Añadie el subset.css recién generado al commit.
git add static/custom_subset.css
fi
```
Asegúrate de modificar el script para que coincida con la ruta donde has guardado el script `subset_font`. Las rutas de configuración y fuente deberían funcionar correctamente con la configuración predeterminada de tabi.
Ahora, cada vez que hagas cambios en tu proyecto de Git y los confirmes, el gancho pre-commit verificará las modificaciones en el archivo `config.toml` y ejecutará automáticamente el script `subset_font` para actualizar el archivo `custom_subset.css`.
Por cierto, si te interesa una forma de actualizar automáticamente la fecha de tus publicaciones en Zola o comprimir automáticamente tus archivos PNG, echa un vistazo a [esta publicación](https://osc.garden/es/blog/zola-date-git-hook/).
Si deseas utilizar todos los scripts a la vez (compresión de archivos PNG, actualización de la fecha y creación del subconjunto de fuentes), combina su código en un solo archivo `.git/hooks/pre-commit`.

@ -1,7 +1,7 @@
+++
title = "Optimise loading times with a custom font subset"
date = 2023-04-29
updated = 2023-05-18
updated = 2023-05-25
description = "Learn how to create a custom subset that only includes the necessary glyphs."
[taxonomies]
@ -43,7 +43,7 @@ usage() {
echo "Options:"
echo " --config, -c Path to the config.toml file."
echo " --font, -f Path to the font file."
echo " --output, -o Output path for the generated custom_subset.css file (default: current directory)"
echo " --output, -o Output path for the generated subset.css file (default: current directory)"
echo " --help, -h Show this help message and exit"
}
@ -102,11 +102,22 @@ if [ ! -f "$font_file" ]; then
fi
# Extract the title and menu names from the config file.
title=$(awk -F' = ' '/title/{print $2}' "$config_file" | tr -d '"' | grep -v "atom feed")
menu_names=$(awk '/menu/{f=1;next} /^\S/{f=0} f{print}' "$config_file" | awk -F' = ' '/name/{print $2}' | tr -d '"' )
title=$(awk -F' = ' '/^title/{print $2}' "$config_file" | tr -d '"' | grep -v "atom feed")
menu_names=$(awk -F' = ' '/^menu/{f=1;next} /socials/{f=0} f && /name/{print $2}' "$config_file" | cut -d',' -f1 | tr -d '"' )
language_names=$(awk -F' = ' '/^language_name\./{print $2}' "$config_file" | tr -d '"' )
# If the site is multilingual, get the menu translations.
if [ -n "$language_names" ]; then
for menu_name in $menu_names; do
# Find the line with the menu name inside a [languages.*.translations] section and get the translated menus.
menu_translation=$(awk -F' = ' "/\\[languages.*\\.translations\\]/{f=1;next} /^\\[/ {f=0} f && /$menu_name =/{print \$2}" "$config_file" | tr -d '"' )
# Add the found menu value to the translations string
menu_names+="$menu_translation"
done
fi
# Combine the extracted strings.
combined="$title$menu_names"
combined="$title$menu_names$language_names"
# Get unique characters.
unique_chars=$(echo "$combined" | grep -o . | sort -u | tr -d '\n')
@ -117,7 +128,7 @@ temp_subset=$(mktemp)
# Create the subset.
pyftsubset "$font_file" \
--text="$unique_chars" \
--layout-features="" --flavor="woff2" --output-file="$temp_subset" --with-zopfli
--layout-features="*" --flavor="woff2" --output-file="$temp_subset" --with-zopfli
# Remove trailing slash from output path, if present.
output_path=${output_path%/}

@ -0,0 +1,60 @@
+++
title = "Exemples de Markdown"
date = 2023-01-31
updated = 2023-04-28
description = "Aquesta publicació mostra alguns exemples de format en Markdown, incloent-hi una taula, blocs de codi i etiquetes, citacions, taules i notes a peu de pàgina."
[taxonomies]
tags = ["markdown", "funcionalitat"]
[extra]
katex = true
+++
## $\KaTeX$
[$\KaTeX$](https://katex.org/) és una llibreria ràpida i fàcil d'usar que permet representar notació matemàtica mitjançant la sintaxi LaTeX.
Pots utilitzar $\KaTeX$ **en línia** embolcallant l'expressió entre `$` o entre `\\(` i `\\)`.
Per exemple, `$ \sin(x) = \sum_{n=0}^{\infty} \frac{(-1)^n}{(2n + 1)!} x^{2n + 1} $` es renderitzarà com: $ \sin(x) = \sum_{n=0}^{\infty} \frac{(-1)^n}{(2n + 1)!} x^{2n + 1} $
Per mostrar l'expressió **en una línia pròpia i centrada**, embolcalla-la amb `$$` o entre `\\[` i `\\]`.
Per exemple, `\\[ r = \frac{\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum_{i=1}^{n}(x_i - \bar{x})^2}\sqrt{\sum_{i=1}^{n}(y_i - \bar{y})^2}} \\]` es renderitzarà com: \\[ r = \frac{\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum_{i=1}^{n}(x_i - \bar{x})^2}\sqrt{\sum_{i=1}^{n}(y_i - \bar{y})^2}} \\]
Per activar $\KaTeX$ en una publicació, inclou `katex = true` dins de la secció `[extra]` dels metadades de la publicació. Per obtenir un millor rendiment i seguretat, els fitxers JavaScript, CSS i les tipografies s'allotgen localment.
**Nota**: Després d'activar $\KaTeX$, si vols utilitzar el caràcter \$ sense renderitzar-lo com a expressió matemàtica, escapa'l amb una barra inversa: `\$`.
## Taula
Aquí tens un exemple de taula[^1]. Els seus colors canvien en funció del tema actual.
| Símbol | Element | Nombre atòmic |
|---------|---------|---------------|
| H | Hidrogen| 1 |
| C | Carboni | 6 |
| Fe | Ferro | 26 |
| Au | Or | 79 |
## Bloc de codi
```rust
fn main() {
println!("Hola, món!") -> ();
}
```
## Etiquetes de codi
Lorem ipsum `dolor` sit amet, `consectetur adipiscing` elit.
`Lorem ipsum dolor sit amet, consectetur adipiscing elit.`
## Quote
> La vida, perquè sigui vida, s'ha de viure a poc a poc…
>
> — Mercè Rodoreda, La plaça del Diamant
[^1]: I aquí tens un exemple de nota a peu de pàgina!

@ -0,0 +1,60 @@
+++
title = "Ejemplos de Markdown"
date = 2023-01-31
updated = 2023-04-28
description = "Esta publicación muestra algunos ejemplos de formato Markdown, incluyendo una tabla, bloques de código y etiquetas, citas, tablas y notas al pie de página."
[taxonomies]
tags = ["markdown", "funcionalidad"]
[extra]
katex = true
+++
## $\KaTeX$
[$\KaTeX$](https://katex.org/) es una biblioteca rápida y fácil de usar que permite la representación de notación matemática utilizando sintaxis LaTeX.
Puedes usar $\KaTeX$ **en línea** al envolver la expresión entre `$` o entre `\\(` y `\\)`.
Por ejemplo, `$ \sin(x) = \sum_{n=0}^{\infty} \frac{(-1)^n}{(2n + 1)!} x^{2n + 1} $` se mostraría como: $ \sin(x) = \sum_{n=0}^{\infty} \frac{(-1)^n}{(2n + 1)!} x^{2n + 1} $
Para mostrar la expresión **en su propia línea y centrada**, envuélvela entre `$$` o entre `\\[` y `\\]`.
Por ejemplo, `\\[ r = \frac{\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum_{i=1}^{n}(x_i - \bar{x})^2}\sqrt{\sum_{i=1}^{n}(y_i - \bar{y})^2}} \\]` se mostraría como: \\[ r = \frac{\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum_{i=1}^{n}(x_i - \bar{x})^2}\sqrt{\sum_{i=1}^{n}(y_i - \bar{y})^2}} \\]
Para activar $\KaTeX$ en una publicación, incluye `katex = true` dentro de la sección `[extra]` del encabezado de la publicación. Para un mejor rendimiento y seguridad, el JavaScript, CSS y las fuentes se alojan localmente.
**Nota**: Después de habilitar $\KaTeX$, si deseas usar \$ sin representar una expresión matemática, escápalo con una sola barra invertida: `\$`.
## Tabla
Aquí tienes un ejemplo de una tabla[^1]. Los colores cambian dependiendo del tema actual.
| Símbolo | Elemento | Número atómico |
|---------|----------|----------------|
| H | Hidrógeno| 1 |
| C | Carbono | 6 |
| Fe | Hierro | 26 |
| Au | Oro | 79 |
## Bloque de código
```rust
fn main() {
println!("¡Hola, mundo!") -> ();
}
```
## Etiquetas de código
Lorem ipsum `dolor` sit amet, `consectetur adipiscing` elit.
`Lorem ipsum dolor sit amet, consectetur adipiscing elit.`
## Cita
> A mí me sobra el cuerpo, Orfeo, me sobra el cuerpo porque me falta alma.
>
> — Miguel de Unamuno, Niebla
[^1]: ¡Y aquí tienes un ejemplo de una nota al pie de página!

@ -0,0 +1,30 @@
+++
title = "Seguretat per defecte"
date = 2023-02-22
updated = 2023-04-29
description = "tabi té una Política de Seguretat de Contingut (CSP) fàcilment personalitzable amb valors segurs per defecte. Obtingues tranquil·litat i un A+ en l'Observatori de Mozilla."
[taxonomies]
tags = ["seguretat", "funcionalitat"]
+++
La configuració per defecte del tema obté una puntuació A+ a l'[Observatori de Mozilla](https://observatory.mozilla.org).[^1]
Això s'aconsegueix configurant programàticament les capçaleres de la Política de Seguretat de Contingut (CSP) basant-se en una llista de dominis permesos definida per l'usuari en el fitxer `config.toml`. Aquí tens la configuració per defecte i recomanada (pots eliminar l'última directiva si no vols inserir vídeos de YouTube):
```
[extra]
allowed_domains = [
{ directive = "font-src", domains = ["'self'", "data:"] },
{ directive = "img-src", domains = ["'self'", "https://*", "data:"] },
{ directive = "script-src", domains = ["'self'"] },
{ directive = "style-src", domains = ["'self'"] },
{ directive = "frame-src", domains = ["https://www.youtube-nocookie.com"] },
]
```
La llista `allowed_domains` especifica les URLs a les quals el lloc web hauria de poder connectar-se, i cada domini de la llista està associat amb una directiva CSP com `frame-src`, `connect-src` o `script-src`. El fitxer `templates/partials/header.html` genera dinàmicament la capçalera CSP basant-se en aquesta llista.
Aquesta funcionalitat permet personalitzar fàcilment les capçaleres de seguretat del lloc web per permetre casos d'ús específics, com ara inserir vídeos de YouTube, carregar scripts o tipografies remotes ([no recomanat](https://www.albertovarela.net/blog/2022/11/stop-using-google-fonts/)).
[^1]: Requereix una configuració adequada del servidor web (p. ex., redirigir el trànsit HTTP a HTTPS).

@ -0,0 +1,30 @@
+++
title = "Seguro por defecto"
date = 2023-02-22
updated = 2023-04-29
description = "tabi tiene una Política de Seguridad de Contenido (CSP) fácilmente personalizable con configuraciones seguras. Obtén tranquilidad y una calificación de A+ en Mozilla Observatory."
[taxonomies]
tags = ["seguridad", "funcionalidad"]
+++
La configuración predeterminada del tema obtiene una calificación de A+ en [Mozilla Observatory](https://observatory.mozilla.org).[^1]
Esto se logra configurando programáticamente las cabeceras de la Política de Seguridad de Contenido (CSP) en función de una lista de dominios permitidos definida por el usuario en el archivo `config.toml`. Aquí tienes la configuración predeterminada y recomendada (puedes eliminar la última directiva si no deseas insertar videos de YouTube):
```
[extra]
allowed_domains = [
{ directive = "font-src", domains = ["'self'", "data:"] },
{ directive = "img-src", domains = ["'self'", "https://*", "data:"] },
{ directive = "script-src", domains = ["'self'"] },
{ directive = "style-src", domains = ["'self'"] },
{ directive = "frame-src", domains = ["https://www.youtube-nocookie.com"] },
]
```
La lista `allowed_domains` especifica las URL a las que el sitio web debería poder conectarse, y cada dominio de la lista se asocia con una directiva CSP como `frame-src`, `connect-src` o `script-src`. El archivo `templates/partials/header.html` genera dinámicamente la cabecera CSP en función de esta lista.
Esta función permite personalizar fácilmente las cabeceras de seguridad del sitio web para permitir casos de uso específicos, como la incrustación de videos de YouTube, la carga de scripts o fuentes remotas ([no recomendado](https://www.albertovarela.net/blog/2022/11/stop-using-google-fonts/)).
[^1]: Requiere una configuración adecuada del servidor web (por ejemplo, redirigir el tráfico HTTP a HTTPS).

@ -1,7 +1,7 @@
+++
title = "Secure by default"
date = 2023-02-22
updated = 2023-04-29
updated = 2023-05-22
description = "tabi has an easily customizable Content Security Policy (CSP) with safe defaults. Get peace of mind and an A+ on Mozilla Observatory."
[taxonomies]
@ -25,6 +25,6 @@ allowed_domains = [
The `allowed_domains` list specifies the URLs that the website should be able to connect to, and each domain in the list is associated with a CSP directive such as `frame-src`, `connect-src`, or `script-src`. The `templates/partials/header.html` file dynamically generates the CSP header based on this list.
This feature allows you to easily customize the website's security headers to allow for specific use cases, such as embedding YouTube videos, loading remote fonts ([not recommended](https://www.albertovarela.net/blog/2022/11/stop-using-google-fonts/)) or scripts.
This feature allows you to easily customize the website's security headers to allow for specific use cases, such as embedding YouTube videos, loading scripts or remote fonts ([not recommended](https://www.albertovarela.net/blog/2022/11/stop-using-google-fonts/)).
[^1]: Requires proper webserver configuration (e.g. redirecting HTTP traffic to HTTPS).

@ -0,0 +1,78 @@
+++
title = "Shortcodes personalitzats"
date = 2023-02-19
updated = 2023-04-16
description = "Aquest tema inclou alguns shortcodes personalitzats útils que pots utilitzar per millorar les teves publicacions. Ja sigui per mostrar imatges que s'adapten als temes clar i fosc, o per donar format a una secció de referències amb un aspecte professional, aquests shortcodes personalitzats t'ajudaran."
[taxonomies]
tags = ["funcionalitat", "shortcodes"]
+++
## Shortcodes d'imatge
### Imatges per a temes duals
Útil si vols utilitzar una imatge diferent pels temes clar i fosc:
{{ dual_theme_image(light_src="img/paris_day.webp", dark_src="img/paris_night.webp" alt="La Torre Eiffel") }}
#### Ús
```
{{/* dual_theme_image(light_src="img/paris_day.webp", dark_src="img/paris_night.webp" alt="La Torre Eiffel") */}}
```
### Imatge invertible
Útil per a gràfics, dibuixos de línies, diagrames… Inverteix els colors de la imatge. La imatge original s'utilitzarà per al tema clar.
{{ invertible_image(src="img/graph.webp", alt="Gràfic invertible") }}
#### Ús
```
{{/* invertible_image(src="img/graph.webp", alt="Gràfic invertible") */}}
```
### Imatge regulable
Les imatges amb massa brillantor o contrast poden ser molestes en un fons fosc. Aquí tens un exemple d'una fotografia que s'atenua quan s'activa el tema fosc.
{{ dimmable_image(src="img/desert_by_oskerwyld.webp", alt="Fotografia d'un desert, cel celestial") }}
#### Usage
```
{{/* dimmable_image(src="img/desert_by_oskerwyld.webp", alt="Fotografia d'un desert, cel celestial") */}}
```
## Referències
### Sagnat invertit
Aquest shortcode formata una secció de referència amb un sagnat invertit de la següent manera:
{% references() %}
Alderson, E. (2015). Ciberseguretat i justícia social: Una crítica a la hegemonia corporativa en un món digital. *New York Journal of Technology, 11*(2), 24-39. [https://doi.org/10.1007/s10198-022-01497-6](https://doi.org/10.1007/s10198-022-01497-6).
Funkhouser, M. (2012). Les normes socials d'indecència: Un anàlisi del comportament desviat a la societat contemporània. *Los Angeles Journal of Sociology, 16*(3), 41-58. [https://doi.org/10.1093/jmp/jhx037](https://doi.org/10.1093/jmp/jhx037).
Schrute, D. (2005). La revolució de l'agricultura de remolatxa: Un anàlisi de la innovació agrícola. *Scranton Agricultural Quarterly, 38*(3), 67-81.
Steinbrenner, G. (1997). L'anàlisi cost-benefici de George Costanza: Un anàlisi del comportament de presa de riscos en el lloc de treball. *New York Journal of Business, 12*(4), 112-125.
Winger, J. A. (2010). L'art del debat: Un examen de la retòrica en el model de les Nacions Unides del Greendale Community College. *Colorado Journal of Communication Studies, 19*(2), 73-86. [https://doi.org/10.1093/6seaons/1movie](https://doi.org/10.1093/6seaons/1movie).
{% end %}
#### Ús
```
{%/* references() */%}
Les teves referències van aquí.
Cada una en una nova línia. Es renderitzarà el Markdown (enllaços, cursiva…).
{%/* end */%}
```

@ -0,0 +1,79 @@
+++
title = "Shortcodes personalizados"
date = 2023-02-19
updated = 2023-05-21
description = "Este tema incluye algunos shortcodes personalizados útiles que puedes utilizar para mejorar tus publicaciones. Puedes mostrar imágenes que se adapten a los temas claro y oscuro, dar formato a una sección de referencias con un aspecto profesional, y más."
[taxonomies]
tags = ["funcionalidad", "shortcodes"]
+++
## Shortcodes de imágenes
### Imágenes de doble tema
Útil si deseas usar una imagen diferente para los temas claro y oscuro:
{{ dual_theme_image(light_src="img/paris_day.webp", dark_src="img/paris_night.webp" alt="La Torre Eiffel") }}
#### Uso
```
{{/* dual_theme_image(light_src="img/paris_day.webp", dark_src="img/paris_night.webp" alt="La Torre Eiffel") */}}
```
### Imagen invertible
Ideal para gráficos, dibujos lineales, diagramas... Invierte los colores de la imagen. La imagen de origen se utilizará para el tema claro.
{{ invertible_image(src="img/graph.webp", alt="Gráfico invertible") }}
#### Uso
```
{{/* invertible_image(src="img/graph.webp", alt="Gráfico invertible") */}}
```
### Imagen atenuable
Las imágenes con demasiado brillo o contraste pueden ser impactantes en un fondo oscuro. Aquí tienes un ejemplo de una fotografía que se atenúa cuando el tema oscuro está activo.
{{ dimmable_image(src="img/desert_by_oskerwyld.webp", alt="Fotografía de un desierto, cielo celestial") }}
#### Uso
```
{{/* dimmable_image(src="img/desert_by_oskerwyld.webp", alt="Fotografía de un desierto, cielo celestial") */}}
```
## Referencias
### Sangría francesa
Este shortcode formatea una sección de referencias con sangría francesa de la siguiente manera:
{% references() %}
Alderson, E. (2015). Ciberseguridad y justicia social: Una crítica a la hegemonía corporativa en un mundo digital. *New York Journal of Technology, 11*(2), 24-39. [https://doi.org/10.1007/s10198-022-01497-6](https://doi.org/10.1007/s10198-022-01497-6).
Funkhouser, M. (2012). Las normas sociales de indecencia: Un análisis del comportamiento desviado en la sociedad contemporánea. *Los Angeles Journal of Sociology, 16*(3), 41-58. [https://doi.org/10.1093/jmp/jhx037](https://doi.org/10.1093/jmp/jhx037).
Schrute, D. (2005). La revolución de la agricultura de remolacha: Un análisis de la innovación agrícola. *Scranton Agricultural Quarterly, 38*(3), 67-81.
Steinbrenner, G. (1997). El análisis costo-beneficio de George Costanza: Un examen del comportamiento de toma de riesgos en el lugar de trabajo. *New York Journal of Business, 12*(4), 112-125.
Winger, J. A. (2010). El arte del debate: Un examen de la retórica en el modelo de las Naciones Unidas del Greendale Community College. *Colorado Journal of Communication Studies, 19*(2), 73-86. [https://doi.org/10.1093/6seaons/1movie](https://doi.org/10.1093/6seaons/1movie).
{% end %}
#### Uso
```
{%/* references() */%}
Tus referencias van aquí.
Cada una en una línea nueva. Se renderizará Markdown (enlaces, cursivas…).
{%/* end */%}
```

@ -1,7 +1,7 @@
+++
title = "Custom shortcodes"
date = 2023-02-19
updated = 2023-04-16
updated = 2023-05-21
description = "This theme includes some useful custom shortcodes that you can use to enhance your posts. Whether you want to display images that adapt to light and dark themes, or format a professional-looking reference section, these custom shortcodes have got you covered."
[taxonomies]
@ -75,3 +75,4 @@ Your references go here.
Each in a new line. Markdown (links, italics…) will be rendered.
{%/* end */%}
```

@ -0,0 +1,48 @@
+++
title = "Taula de contingut"
date = 2022-11-01
description = "Una publicació que mostra la taula de contingut opcional."
[taxonomies]
tags = ["funcionalitat", "markdown"]
[extra]
toc = true
+++
# Capçalera 1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
## Capçalera 2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
### Capçalera 3.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Capçalera 4.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Capçalera 4.2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
### Capçalera 3.2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Capçalera 4
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Capçalera 4
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
---
## Capçalera 2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
### Capçalera 3.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Capçalera 4.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Capçalera 4.2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
### Capçalera 3.2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Capçalera 4.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Capçalera 4.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.

@ -0,0 +1,48 @@
+++
title = "Tabla de contenido"
date = 2022-11-01
description = "Una publicación que muestra la tabla de contenido opcional."
[taxonomies]
tags = ["funcionalidad", "markdown"]
[extra]
toc = true
+++
# Encabezado 1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
## Encabezado 2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
### Encabezado 3.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Encabezado 4.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Encabezado 4.2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
### Encabezado 3.2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Encabezado 4
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Encabezado 4
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
---
## Encabezado 2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
### Encabezado 3.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Encabezado 4.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Encabezado 4.2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
### Encabezado 3.2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Encabezado 4.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.
#### Encabezado 4.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed mollis augue, vel efficitur lacus. Pellentesque eu egestas mi. Etiam ultrices lectus sit amet aliquet ullamcorper. Praesent in erat quis est sagittis finibus. Etiam nec sapien in nulla interdum faucibus. Integer iaculis lorem quis arcu lobortis, non malesuada neque vehicula. Aenean nec tellus eu metus bibendum tempus. Sed rutrum urna ut commodo tempor. Vestibulum aliquet elit posuere turpis maximus condimentum. Sed urna libero, ornare eu tellus vitae, laoreet condimentum risus. Aenean elit lectus, mattis quis nibh nec, faucibus rutrum sapien. Sed iaculis consectetur mi, eget posuere turpis finibus et.

@ -0,0 +1,5 @@
+++
title = "Projectes"
sort_by = "weight"
template = "cards.html"
+++

@ -0,0 +1,5 @@
+++
title = "Proyectos"
sort_by = "weight"
template = "cards.html"
+++

@ -0,0 +1,9 @@
+++
title = "chu"
description = "Aplicació Flask per a càrregues de fitxers segures, amb eliminació de metadades, compressió, protecció amb contrasenya i més."
weight = 2
[extra]
local_image = "img/chu.webp"
link_to = "https://github.com/welpo/chu"
+++

@ -0,0 +1,9 @@
+++
title = "chu"
description = "Aplicación Flask para subir archivos de forma segura, con eliminación de metadatos, compresión, protección con contraseña y más."
weight = 2
[extra]
local_image = "img/chu.webp"
link_to = "https://github.com/welpo/chu"
+++

@ -0,0 +1,11 @@
+++
title = "nani"
description = "Script Bash per crear URL públiques a partir de fitxers o text en servidors remots."
weight = 3
date = 2015-10-30
updated = 2023-02-12
[extra]
local_image = "img/nani.webp"
link_to = "https://github.com/welpo/nani"
+++

@ -0,0 +1,11 @@
+++
title = "nani"
description = "Script Bash para crear URL públicas desde archivos o texto en servidores remotos."
weight = 3
date = 2015-10-30
updated = 2023-02-12
[extra]
local_image = "img/nani.webp"
link_to = "https://github.com/welpo/nani"
+++

@ -0,0 +1,10 @@
+++
title = "spectro"
description = "Script de Bash per generar espectrogrames, comparar-los, carregar-los a Imgur i proporcionar els corresponents URLs per compartir-los."
weight = 4
date = 2015-10-29
[extra]
local_image = "img/spectro.webp"
link_to = "https://github.com/welpo/spectro"
+++

@ -0,0 +1,10 @@
+++
title = "spectro"
description = "Script de Bash para generar espectrogramas, compararlos, subirlos a Imgur y proporcionar las correspondientes URLs para compartir."
weight = 4
date = 2015-10-29
[extra]
local_image = "img/spectro.webp"
link_to = "https://github.com/welpo/spectro"
+++

@ -1,8 +1,9 @@
+++
title = "spectro"
description = "Bash script to generate spectrograms, compare them, upload them to Imgur, and provide the corresponding BBCode URLs for sharing."
description = "Bash script to generate spectrograms, compare them, upload them to Imgur, and provide the corresponding URLs for sharing."
weight = 4
date = 2015-10-29
updated = 2023-05-22
[extra]
local_image = "img/spectro.webp"

@ -0,0 +1,9 @@
+++
title = "tabi"
description = "Un tema de Zola ràpid, lleuger i modern amb JavaScript opcional."
weight = 1
[extra]
local_image = "img/tabi.webp"
link_to = "https://github.com/welpo/tabi"
+++

@ -0,0 +1,9 @@
+++
title = "tabi"
description = "Un tema de Zola rápido, ligero y moderno con JavaScript opcional."
weight = 1
[extra]
local_image = "img/tabi.webp"
link_to = "https://github.com/welpo/tabi"
+++

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 425 KiB

@ -196,6 +196,14 @@ p {
font-size: 1em;
}
.centered-text {
text-align: center;
}
.subheader {
margin-bottom: 2rem;
}
@media only screen and (max-width: 1000px) {
.content {
max-width: var(--normal-layout-width);

@ -2,8 +2,7 @@ code {
background-color: var(--bg-1);
padding: 0.1em 0.2em;
font-family: var(--code-font);
font-weight: 350;
font-size: 0.92em;
font-size: 0.9em;
}
pre {

@ -21,16 +21,6 @@ header {
margin: 4rem 0px 1rem 0px;
}
.centered-header {
font-family: var(--header-font);
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
font-size: 1em;
}
.navbar {
max-width: var(--max-layout-width);
display: flex;
@ -48,9 +38,10 @@ header {
ul {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 1px;
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0;
@ -107,6 +98,51 @@ header {
font-size: 0.92rem;
}
.language-switcher {
margin-left: 0.5rem;
margin-right: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.language-switcher-icon {
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 96 960 960'><path d='M480 976q-84 0-157-31.5T196 859q-54-54-85-127.5T80 574q0-84 31-156.5T196 291q54-54 127-84.5T480 176q84 0 157 30.5T764 291q54 54 85 126.5T880 574q0 84-31 157.5T764 859q-54 54-127 85.5T480 976Zm0-58q35-36 58.5-82.5T577 725H384q14 60 37.5 108t58.5 85Zm-85-12q-25-38-43-82t-30-99H172q38 71 88 111.5T395 906Zm171-1q72-23 129.5-69T788 725H639q-13 54-30.5 98T566 905ZM152 665h159q-3-27-3.5-48.5T307 574q0-25 1-44.5t4-43.5H152q-7 24-9.5 43t-2.5 45q0 26 2.5 46.5T152 665Zm221 0h215q4-31 5-50.5t1-40.5q0-20-1-38.5t-5-49.5H373q-4 31-5 49.5t-1 38.5q0 21 1 40.5t5 50.5Zm275 0h160q7-24 9.5-44.5T820 574q0-26-2.5-45t-9.5-43H649q3 35 4 53.5t1 34.5q0 22-1.5 41.5T648 665Zm-10-239h150q-33-69-90.5-115T565 246q25 37 42.5 80T638 426Zm-254 0h194q-11-53-37-102.5T480 236q-32 27-54 71t-42 119Zm-212 0h151q11-54 28-96.5t43-82.5q-75 19-131 64t-91 115Z'/%3E%3C/svg%3E%0A");
background: var(--text-color);
align-self: center;
width: 1.2rem;
height: 1.2rem;
position: relative;
cursor: pointer;
}
.dropdown {
display: inline-block;
position: relative;
z-index: 99;
font-size: 0.8rem;
}
.dropdown-content {
display: none;
position: absolute;
padding-left: 0.5rem;
padding-right: 0.5rem;
z-index: 99;
left: 50%;
transform: translateX(-50%);
text-align: center;
background: var(--background-color);
a {
display: block;
}
}
.dropdown:hover .dropdown-content {
display: block;
}
@media only screen and (max-width: 1000px) {
.navbar {
max-width: var(--normal-layout-width);
@ -129,6 +165,7 @@ header {
.navbar {
flex-direction: column;
align-items: center;
justify-content: center;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 163 KiB

File diff suppressed because one or more lines are too long

@ -1,10 +1,19 @@
{% extends "page.html" %}
{% block main_content %}
<main class="centered-header">
<main class="centered-text">
{{ macros_page_header::page_header(title="404")}}
<span>not found</span>
<div class="subheader">not found</div>
{# Default English message #}
<p>The page you requested doesn't seem to exist{%- if config.languages | length > 1 %} or has not been translated to your language yet{%- endif -%}. Check the URL for errors, or <a href="{{ config.base_url }}">return to the homepage</a>.</p>
{# Iterate through each language and display the localised 404 message along with a "Go Home" link #}
{%- for language_name, language in config.languages -%}
{%- if language_name != config.default_language -%}
<p>{{ trans(key="404_error", lang=language_name) }} <a href="{{ config.base_url }}/{{ language_name }}/">{{ trans(key="go_home", lang=language_name) }}</a>.</p>
{%- endif -%}
{%- endfor -%}
</main>
{% endblock main_content %}

@ -4,30 +4,42 @@
{{ macros_page_header::page_header(title=section.title) }}
{# Set locale for date #}
{%- if lang != config.default_language %}
{% set date_locale = trans(key="date_locale" | safe, lang=lang) %}
{% else %}
{% set date_locale = "en_GB" %}
{% endif %}
<div class="archive">
<ul class="list-with-title">
{% set section_item = get_section(path="blog/_index.md") %} {% for year, posts in
section_item.pages | group_by(attribute="year") %} {% if posts | length > 0
%}
<li>
<h2 class="listing-title">{{ year }}</h2>
<ul class="listing">
{% for post in posts %}
<li class="listing-item">
<div class="post-time">
<span class="date">
{{ post.date | date(format="%d %b") }}
</span>
</div>
<a href="{{ post.permalink }}" title="{{ post.title }}"
>{{ post.title }}</a
>
<ul class="list-with-title">
{%- if lang == config.default_language %}
{%- set section_item = get_section(path="blog/" ~ "_index.md") %}
{%- else %}
{%- set section_item = get_section(path="blog/" ~ "_index." ~ lang ~ ".md") %}
{%- endif %}
{% for year, posts in
section_item.pages | group_by(attribute="year") %} {% if posts | length > 0
%}
<li>
<h2 class="listing-title">{{ year }}</h2>
<ul class="listing">
{% for post in posts %}
<li class="listing-item">
<div class="post-time">
<span class="date">
{{ post.date | date(format="%d %b", locale=date_locale) }}
</span>
</div>
<a href="{{ post.permalink }}" title="{{ post.title }}"
>{{ post.title }}</a>
</li>
{% endfor %}
</ul>
{% endif %} {% endfor %}
</li>
{% endfor %}
</ul>
{% endif %} {% endfor %}
</li>
</ul>
</ul>
</div>
{% endblock main_content %}

@ -10,19 +10,19 @@
<ul class="meta">
{% if page.draft %}
<li class="draft-label">DRAFT</li>
<li class="draft-label">{%- if lang != config.default_language %} {{ trans(key="draft" | safe, lang=lang) }} {% else %} DRAFT {% endif %}</li>
{% endif %}
{% if page.date %}
<li>{{ macros_format_date::format_date(date=page.date, short=true) }} {{ separator }}</li>
{% endif %}
<li title="{{ page.word_count }} words">&nbsp;{{ page.reading_time }} min read</li>
<li title="{{ page.word_count }} {%- if lang != config.default_language %} {{ trans(key="words" | safe, lang=lang) }} {% else %} words {% endif %}">&nbsp;{{ page.reading_time }}{%- if lang != config.default_language %} {{ trans(key="min_read" | safe, lang=lang) }} {% else %} min read {% endif %}</li>
{% if page.taxonomies and page.taxonomies.tags %}
<li>&nbsp;{{ separator }}&nbsp;Tags:&nbsp;</li>
<li>&nbsp;{{ separator }}&nbsp;{%- if lang != config.default_language -%}{{ trans(key="tags" | safe, lang=lang) | capitalize }}{% else %}Tags{%- endif -%}:&nbsp;</li>
{% for tag in page.taxonomies.tags %}
<li><a href={{ get_taxonomy_url(kind='tags' , name=tag) | safe }}>{{ tag }}</a>
<li><a href={{ get_taxonomy_url(kind='tags', name=tag, lang=lang) | safe }}>{{ tag }}</a>
{%- if not loop.last -%}
,&nbsp;
{%- endif -%}
@ -31,7 +31,7 @@
{% endif %}
{% if page.updated %}
</ul><ul class="meta last-updated"><li>Last updated on {{ macros_format_date::format_date(date=page.updated, short=true) }}</li>
</ul><ul class="meta last-updated"><li>{%- if lang != config.default_language %} {{ trans(key="last_updated_on" | safe, lang=lang) }} {% else %} Last updated on {% endif %} {{ macros_format_date::format_date(date=page.updated, short=true) }}</li>
{% endif %}
</ul>
@ -46,7 +46,7 @@
{% if page.extra.toc | default(value=false) %}
{% if page.toc %}
<div class="toc-container">
<h3>Table of Contents</h3>
<h3>{%- if lang != config.default_language %} {{ trans(key="table_of_contents" | safe, lang=lang) }} {% else %} Table of Contents {% endif %}</h3>
<ul>
{% for h1 in page.toc %}
<li>

@ -1,10 +1,15 @@
{% macro format_date(date, short) %}
{% if config.extra.short_date_format and short %}
{{ date | date(format=config.extra.short_date_format) }}
{% elif config.extra.long_date_format and not short %}
{{ date | date(format=config.extra.long_date_format) }}
{# Set locale #}
{%- if lang != config.default_language %}
{% set date_locale = trans(key="date_locale" | safe, lang=lang) %}
{% else %}
{% set date_locale = "en_GB" %}
{% endif %}
{% if config.extra.long_date_format and not short %}
{{ date | date(format=config.extra.long_date_format, locale=date_locale) }}
{% elif not config.extra.short_date_format and date_locale == "en_GB" %}
{% set day = date | date(format='%-d') | int %}
{% if day in [11, 12, 13] %}
@ -29,6 +34,8 @@
{% else %}
{{ date | date(format="%B %Y") }}
{% endif %}
{% else %}
{{ date | date(format="%d %b %Y", locale=date_locale) }}
{% endif %}
{% endmacro %}

@ -25,7 +25,7 @@
{% if post.taxonomies.tags %}
<div class="bloglist-tags">
{% for tag in post.taxonomies.tags %}
<a class="tag" href="{{ get_taxonomy_url(kind="tags", name=tag) }}">{{ tag }}</a>
<a class="tag" href="{{ get_taxonomy_url(kind='tags', name=tag, lang=lang) | safe }}">{{ tag }}</a>
{% endfor %}
</div>
{% endif %}
@ -38,14 +38,14 @@
{% endif %}
</div>
<a class="readmore" href={{ post.permalink }}>Read more →</a>
<a class="readmore" href={{ post.permalink }}>{%- if lang != config.default_language %} {{ trans(key="read_more" | safe, lang=lang) }} {% else %} Read more {% endif %}</a>
</div>
</section>
{% endif %}
{% if not loop.last %}
{% if loop.index == max %}
<div class="all-posts">
<a href="{{ get_url(path="/blog/") }}">All posts </a>
<a href="{{ get_url(path="blog", lang=lang) }}/">{%- if lang != config.default_language %} {{ trans(key="all_posts" | safe, lang=lang) }} {% else %} All posts {% endif %}</a>
</div>
{% endif %}
{% endif %}

@ -4,25 +4,25 @@
<ul class="pagination">
{% if paginator.previous %}
<li class="page-item page-prev">
<a href="{{ paginator.previous }}" class="page-link" aria-label="Previous page">← Prev</a>
<a href="{{ paginator.previous }}" class="page-link" aria-label="{%- if lang != config.default_language %} {{ trans(key="prev" | safe, lang=lang) }}{% else %} Prev {%- endif -%}">← {%- if lang != config.default_language %} {{ trans(key="prev" | safe, lang=lang) }}{% else %} Prev {%- endif -%}</a>
</li>
{% else %}
<li class="page-item page-prev">
<span class="page-link disabled" aria-disabled="true" aria-label="Previous (disabled)">← Prev</span>
<span class="page-link disabled" aria-disabled="true" aria-label="{%- if lang != config.default_language %} {{ trans(key="prev" | safe, lang=lang) }}{% else %} Prev {%- endif -%} (disabled)">← {%- if lang != config.default_language %} {{ trans(key="prev" | safe, lang=lang) }}{% else %} Prev {%- endif -%}</span>
</li>
{% endif %}
<li class="page-item page-numbers">
{{ paginator.current_index }} of {{ paginator.number_pagers }}
{{ paginator.current_index }} {%- if lang != config.default_language %} {{ trans(key="of" | safe, lang=lang) }}{% else %} of {%- endif %} {{ paginator.number_pagers }}
</li>
{% if paginator.next %}
<li class="page-item page-next">
<a href="{{ paginator.next }}" class="page-link" aria-label="Next page">Next</a>
<a href="{{ paginator.next }}" class="page-link" aria-label="{%- if lang != config.default_language %} {{ trans(key="next" | safe, lang=lang) }}{% else %} Next {%- endif -%}">{%- if lang != config.default_language %} {{ trans(key="next" | safe, lang=lang) }}{% else %} Next {%- endif %}</a>
</li>
{% else %}
<li class="page-item page-next">
<span class="page-link disabled" aria-disabled="true" aria-label="Next page (disabled)">Next</span>
<span class="page-link disabled" aria-disabled="true" aria-label="{%- if lang != config.default_language %} {{ trans(key="next" | safe, lang=lang) }}{% else %} Next {%- endif -%} (disabled)">{%- if lang != config.default_language %} {{ trans(key="next" | safe, lang=lang) }}{% else %} Next {%- endif %}</span>
</li>
{% endif %}
</ul>

@ -5,7 +5,14 @@
{%- set custom_separator = config.extra.separator | default(value="•") -%}
{%- set separator = " " ~ custom_separator ~ " " -%}
{%- if current_path and current_path == "/" -%}
{# Get the base path for the current language. #}
{%- if lang != config.default_language %}
{%- set base_path = "/" ~ lang ~ "/" %}
{%- else -%}
{%- set base_path = "/" %}
{%- endif %}
{%- if current_path and current_path == base_path -%}
{%- set suffix = "" -%}
{%- set separator = "" -%}
{% elif title %}
@ -19,9 +26,13 @@
{%- set suffix = term.name -%}
{% elif taxonomy.name %}
{# List of tags. #}
{%- set suffix = taxonomy.name | capitalize -%}
{%- if lang != config.default_language -%}
{%- set suffix = trans(key=taxonomy.name | safe, lang=lang) | capitalize -%}
{% else %}
{%- set suffix = taxonomy.name | capitalize -%}
{% endif %}
{% else %}
{%- set suffix = "Lost?" %}
{%- set suffix = "404" %}
{%- endif -%}
{# Return the final concatenated string. #}

@ -14,7 +14,7 @@
{% endif %}
</nav>
<div class="credits">
<small>Powered by <a href="https://www.getzola.org" target="_blank">Zola</a> & <a href="https://github.com/welpo/tabi" target="_blank">tabi</a></small>
<small>{%- if lang != config.default_language %} {{ trans(key="powered_by" | safe, lang=lang) }} {% else %} Powered by {% endif %} <a href="https://www.getzola.org" target="_blank">Zola</a> {%- if lang != config.default_language %} {{ trans(key="and" | safe, lang=lang) }} {% else %} & {% endif %} <a href="https://github.com/welpo/tabi" target="_blank">tabi</a></small>
</div>
</section>
</footer>

@ -1,17 +1,67 @@
<header>
<nav class="navbar">
<div class="nav-title">
<a class="home-title" href={{ config.base_url }}>{{ config.title }}</a>
<a class="home-title" href={{ get_url(path="/", lang=lang) }}>{{ config.title }}</a>
</div>
{%- if config.extra.menu %}
<div class="nav-navs">
<ul>
{% for menu in config.extra.menu %}
<li>
<a class="nav-links no-hover-padding" href={{ menu.url | safe | replace(from="$BASE_URL" , to=config.base_url) }}>{{ menu.name }}</a>
{%- if config.extra.menu %}
{% for menu in config.extra.menu %}
<li>
<a class="nav-links no-hover-padding" href={{ get_url(path=menu.url, lang=lang) | safe | replace(from="$BASE_URL", to=config.base_url) }}/>
{%- if lang != config.default_language -%}
{{ trans(key=menu.name | safe, lang=lang) }}
{%- else -%}
{{ menu.name | safe }}
{%- endif -%}
</a>
</li>
{% endfor %}
{%- endif -%}
{# Language switcher #}
{# Display the language switcher only if more than one language is available #}
{%- if config.languages | length > 1 %}
<li class="language-switcher">
<div class="dropdown" type="Button">
<div class="language-switcher-icon"></div>
{# Display the current language first in the dropdown #}
<div class="dropdown-content">
{%- if lang != config.default_language %}
{{ trans(key="language_name" | safe, lang=lang) }}
{% else %}
{{ config.extra.language_name.en }}
{%- endif %}
{# Loop through all the available languages in the config #}
{%- for lcode, language_name in config.extra.language_name -%}
{# Skip the current language to avoid linking to the current page #}
{%- if lang != lcode -%}
{# Check if the language code matches the default language #}
{%- if lcode == config.default_language -%}
{# If it does, link to the root path (no language code in URL) #}
<a type="Button" href="{{ current_url | replace(from=current_path | default(value="") | truncate(length=4, end=""), to="/") }}">{{ language_name }}</a>
{%- else -%}
{# Check if the current language is the default language #}
{%- if lang == config.default_language -%}
{# If it is, append the language code to the base URL #}
<a type="Button" href="{{ config.base_url }}/{{ lcode }}{{ current_path | default(value="/") | safe }}">{{ language_name }}</a>
{%- else -%}
{# If it's not, replace the current language code in the URL with the new one #}
<a type="Button" href="{{ current_url | replace(from='/' ~ lang ~ '/', to='/' ~ lcode ~ '/') }}"/>{{ language_name }}</a>
{%- endif -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
</div>
</div>
</li>
{% endfor %}
{%- endif %}
{# Theme switcher #}
{%- if config.extra.theme_switcher == true -%}
<li class="theme-switcher-wrapper">
<div class="theme-switcher"></div>

@ -2,14 +2,26 @@
{% block main_content %}
{{ macros_page_header::page_header(title="All tags")}}
{%- if lang != config.default_language %}
{% set title = trans(key="tags_title" | safe, lang=lang) %}
{% else %}
{% set title = "All tags" %}
{% endif %}
{{ macros_page_header::page_header(title=title)}}
<div class="tag-cloud">
<ul class="tags">
{%- for term in terms -%}
<li class="tags-item" id "{{ term.name }}"><a href="{{ term.permalink | safe }}">
{{ term.name }}</a>
{{ term.pages | length }} post{{ term.pages | length | pluralize }}
{{ term.pages | length }}{%- if term.pages | length == 1 %}
{# Only one post. Singular. #}
{%- if lang != config.default_language %} {{ trans(key="post" | safe, lang=lang) }}{% else %} post {%- endif -%}
{% elif term.pages | length > 1 %}
{# More than one post per tag. Plural. #}
{%- if lang != config.default_language %} {{ trans(key="posts" | safe, lang=lang) }}{% else %} posts {%- endif -%}
{%- endif -%}
</li>
{%- endfor -%}
</ul>

@ -9,7 +9,7 @@
<ul class="pagination">
<li class="page-item">
<a class="all-tags" href="{{ get_url(path="/tags/") }}">← All tags</a>
<a class="all-tags" href="{{ get_url(path="tags", lang=lang) }}/">← {%- if lang != config.default_language %} {{ trans(key="all_tags" | safe, lang=lang) }}{% else %} All tags {%- endif -%}</a>
</li>
</ul>

@ -1,5 +1,5 @@
name = "tabi"
description = "A fast, lightweight, and modern Zola theme with optional JavaScript, and a perfect Lighthouse score."
description = "A fast, lightweight, and modern Zola theme with multi-language support, optional JavaScript, and a perfect Lighthouse score"
license = "MIT"
homepage = "https://github.com/welpo/tabi"
@ -19,6 +19,12 @@ homepage = "https://osc.garden"
# be merged with user data, some kind of prefix or nesting is preferable
# Use snake_casing to be consistent with the rest of Zola
[extra]
# Languages of your site.
# You'll need to add the language translations for each non-English language.
# See the config.toml file for an example.
language_name.ca = "Català"
language_name.en = "English"
language_name.es = "Español"
# Enable JavaScript theme toggler for dark/light mode (and automatic switching).
# The default setting is the light theme.

Loading…
Cancel
Save