La creación de este blog: Hugo, agregar al tema Hyde soporte de varios idiomas y soporte de Latex

Etiquetas: Configuración del blog , Hugo , Katex

(English: Creating this Blog: Hugo, making the Hyde theme multilingual, and adding Latex support)

Este blog no se encuentra en una plataforma comercial para bloggers. Es lo que se llama técnicamente un “sitio estático”, lo cual significa que es solo una colección de archivos HTML que están vinculados entre sí, junto con algunos otros archivos que dan formato (por ejemplo, archivos css). Como tal, este blog se puede alojar en cualquier lugar y no está atado a ninguna plataforma en particular. Además, se creó con una fantástica herramienta de código abierto llamada Hugo. En esta entrada de blog explico cómo lo creé.

Todo empezó con la excelente entrada del blog de Hui Gong sobre como crear un blog personal usando GitHub Pages y Hugo. Sabía sobre las páginas de GitHub, pero no sobre Hugo. Siguiendo los pasos en la entrada del blog, tuve en poco tiempo una versión esqueleto de este blog con el tema Hyde.

El tema Hyde es extremadamente minimal (lo cual me gusta mucho), pero quería tres cosas importantes para mi blog que el tema no tenía:

  • Navegación por etiquetas: Permitir que los lectores puedan encontrar otras entradas del blog sobre temas relacionados (en lugar de ver solo una lista de entradas organizada por fecha).
  • Multilingüe: Publicaciones y navegación en inglés y español.
  • Soporte de $\LaTeX$: Poder escribir entradas de blog con símbolos matemáticos usando $\LaTeX$ (que para mí sigue siendo la única manera sensata de escribir matemáticas).

Lograr agregar esta funcionalidad al tema Hyde me permitió entender cómo funciona Hugo. ¡Debo decir que me ha gustado mucho el sistema! Hugo ahora se encarga todo aparte del contenido, y eso me parece genial.

¡Esto no puede haber sido más sencillo! Hugo tiene dos formas predeterminadas de categorizar el contenido: Etiquetas (tags) y Categorías (categories). El tema Hyde viene configurado para mostrarlos en el menú lateral. Uno solo necesita definir las entradas del menú en el archivo config.toml que controla la configuración global del sitio y los temas.

Solo tuve que agregar las siguientes líneas a ese archivo, ¡y estaba todo listo!

[[menu.main]]
    name = "Tags"
    identifier = "tags"
    weight = 300
    url = "/tags/"

[[menu.main]]
    name = "Categories"
    identifier = "categories"
    weight = 300
    url = "/categories/"

Ahora solo me tengo que asegurar que el encabezado de cada entrada de blog incluya la lista de etiquetas y categorías, como el siguiente ejemplo:

---
title: "Creating this Blog"
date: 2021-12-06T22:49:34-05:00
draft: false
tags : ["Blog setup"]
categories: ["Hugo", "Coding"]
---

Las páginas Categorías y Etiquetas vinculadas desde el menú se construyen automáticamente a partir de los archivos de las entradas de blog mismas. ¡Genial!

Configurar soporte multilingüe

Esto requirió mucha prueba y error, pero finalmente lo logré hacer funcionar.

Configuración inicial

Le agregué al archivo config.toml, que controla la configuración global del sitio y los temas, el siguiente código para permitir cambiar entre inglés y español:

defaultContentLanguage = 'en'

[languages]
  [languages.en]
    baseURL= 'https://enriqueacosta.github.io/blog/en/'
    title = 'Enrique Acosta Jaramillo'
    languageName = 'English'
    weight = 1
    description = 'Personal Blog'

  [languages.es]
    baseURL= 'https://enriqueacosta.github.io/blog/es/'
    title = 'Enrique Acosta Jaramillo'
    languageName = 'Español'
    weight = 2
    description = 'Blog personal'

El código se asegura de que Hugo exporte el las páginas HTML de una manera muy simétrica: si hay una entrada de blog (por ejemplo esta, posts/creating-this-blog.md) con su versión en español correspondiente (posts/creating-this-blog.es.md), entonces las entradas tendrán URL paralelos:

  • https://enriqueacosta.github.io/blog/en/creating-this-blog/
  • https://enriqueacosta.github.io/blog/es/creating-this-blog/

(La asimetría de los nombres de archivo con el inglés que no necesita el .en. viene de usar el comando defaultContentlanguage = 'en' en el código anterior).

Esta configuración me parece excelente, pero por desgracia daña la forma en que Hugo procesa las imágenes. Solucionar ese problema requirió algunos ajustes adicionales que describo más abajo.

Agregar un enlace al otro idioma a cada entrada de blog

Quería que cada entrada de blog tuviera un enlace a su versión correspondiente en el otro idioma. ¡Esto resultó ser mucho más simple de lo que imaginaba!

El archivo themes/hyde/layouts/_default/single.html es la plantilla de las páginas de entrada de blog. Lo copié a layouts/_default/single.html, que me da una versión que puedo editar sin dañar los archivos del tema y que por su ubicación toma precedencia sobre el otro archivo.

En el archivo single.html, el {{ .content }} es lo que le dice a Hugo que en ese lugar debe ir el contenido de la entrada blog. Agregué el siguiente código antes de ese comando:

{{ if .IsTranslated }}
   {{ range .Translations }}
      <h4>(<a href="{{ .Permalink }}">{{ .Language.LanguageName }}: {{ .Title }}</a>)</h4>
   {{ end }}
{{ end }}
{{ .Content }}

Lo que hace es genial: el if comprueba si hay una traducción (que sabe a partir de la existencia de archivos con el mismo nombre con o sin el .es.). De ser así, hace un ciclo (este es el range) sobre las traducciones disponibles (que es el .Translations) y para cada una agrega el siguiente enlace dentro del encabezado h4 <h4> (....) </h4>:

<a href="{{ .Permalink }}">{{ .Language.LanguageName }}: {{ .Title }}</a>

Aquí:

  • {{ .Permalink }} es la ruta del archivo con la traducción (por eso se pone como la href).
  • {{ .Language.languageName }} se va a reemplazar con el nombre del idioma de la traducción (note que en las definiciones de los idiomas en el archivo config.toml hay una propiedad languageName =, ese es el nombre que se usa).
  • {{ .Title }} es el título de la entrada el blog en el idioma de la traducción.

¡Eso es todo! Hugo se encarga de agregar esos enlaces a todas las entradas de blog:

Screenshot of post translation link

Todo esto ayuda a entender cómo funciona Hugo: es una colección de “plantillas/fragmentos de HTML” que le dicen a Hugo cómo construir todas las páginas HTML para mostrar el contenido que uno escribe. ¡Muy bueno!

Hacer que las fechas cambien de idioma automáticamente.

Todas las fechas en las entradas del Blog se muestran por defecto en inglés, incluso si la entrada está en español. Arreglar esto también resultó ser bastante simple. La línea original en el archivo index.html que le dice a Hugo que debe agregar la fecha de fecha de la entrada de blog es la siguiente:

<time datetime={{ .Date.Format "2006-01-02T15:04:05Z0700" }} class="post-date">{{ .Date.Format "Mon, Jan 2, 2006" }}</time>

Aquí, el {{ .Date.Format "Mon, Jan 2, 2006" }} es lo que pone la fecha. Después de una búsqueda en Internet, descubrí que lo podía cambiar por un comando que era “consciente del idioma” dado por {{ .Date | time.Format ":date_long" }}.

De acuerdo al idioma {{ .Date | time.Format ":date_long" }} mostrará (note que el mes va correctamente con mayúscula en inglés):

  • December 6, 2021
  • 6 de diciembre de 2021

Por lo tanto, la entrada de la fecha de publicación en el archivo index.html ahora se ve así:

<time datetime={{ .Date.Format "2006-01-02T15:04:05Z0700" }} class="post-date">{{ .Date | time.Format ":date_long" }}</time>

¡Y eso es todo! Ahora cambian de idioma las fechas de las entradas del blog según el idioma.

Arreglar el manejo de las imágenes con la configuración multilingüe

(Nota: Este hack es necesario para la versión V0.89.4 + extendida, puede que no sea necesario para versiones posteriores.)

El problema con las imágenes y la configuración multilingüe que mostré arriba viene del hecho de que quería que la URL base del blog fuera https://enriqueacosta.github.io/blog/, pero a Hugo no le gustan las URL de base que tienen estructura de directorios (/blog/ en este caso). El sistema estaba quitando el \blog\ de la href de las imágenes y esto hacía que el navegador buscara en el lugar equivocado en los archivos de imagen. Este es un problema conocido con Hugo que no se ha solucionado (ver acá y acá).

Nota: El hack/arreglo que sigue no es necesario si la URL base cambia a algo como https://enriqueacosta.github.io.

El arreglo involucró aprender sobre la etiqueta <base> de HTML. Si uno usa la etiqueta <base> en el <head> de cualquier archivo HTML, esta especifica la “ruta anterior” de todas las rutas relativas que se usan en la página (ver acá). Por lo tanto, si una imagen en HTML tiene un href="/aqui/alla/image.png", entonces la etiqueta <base> le dice al navegador que busque la imagen en {{ base }}/aqui/alla/image.png. Así, una forma de solucionar el problema que estaba teniendo era asegurarme que el siguiente código quedara dentro del <head> de las todas páginas HTML:

<base href="{{ .Site.BaseURL }}">

Esto se logra agregando esa línea al archivo hook_head_end.html en

/layouts/partials/hook_head_end.html

(este archivo se debe copiar del archivo con el mismo nombre en el tema).

/temes/hyde/layouts/partials/hook_head_end.html

El archivo hook_head_end.html se carga al final del archivo layouts/partials/head.html (es la forma en la que está organizado el tema).

Después de esto, puedo referenciar la imagen ubicada en static/fig-twistedCubic2.png en el código de markdown de una entrada de blog usando

![Twisted Cubic](fig-twistedCubic2.png)

Nota: esto es distinto de la instrucción regular para insertar imágenes en Hugo, ya que sin el hack de <base>, la referencia de la figura debe usar un /:

<!--- 
Regular Hugo syntax for imputing the 
image at "static/fig-twistedCubic2.png" 
-->
![Twisted Cubic](/fig-twistedCubic2.png)

Nota: Esto también (desafortunadamente) daña las tablas de contenidos en Hugo, ya que estas se generan usando enlaces relativos en la forma href="#encabezado-título" que debido al comando <base> apuntan a

href dañado: {{base url}}/#heading-title

en vez de

href correcto: {{base url}}/{{ Lang }}/posts/{{post title}}#heading-title

Nota: Todavía estoy buscando una mejor solución a este problema, porque el uso de una etiqueta <base> parece bastante radical y puede que dañe otras cosas aparte de la funcionalidad de la tablas de contenido.

Agregar a la barra lateral un selector de idioma

Tener los enlaces al otro idioma en cada publicación estaba bien, pero quería que el blog se sintiera realmente multilingüe y para eso me parecía que debía haber una parte en la barra lateral en la que uno seleccionaba el idioma de todo el blog. Quería que si se presionaba “inglés” o “español” en la barra lateral, toda la página cambiara de idioma.

Increíblemente, esto también resultó ser bastante simple. Debo decir que estoy muy impresionado por todas las cosas que hace Hugo por su propia cuenta.

La plantilla para la barra lateral está en layouts/partials/sidebar.html y a mi copia (que toma precedencia sobre la del tema) le agregué las líneas:

{{ if .IsTranslated }}
  <nav class="LangNav">
  {{ range .AllTranslations }}
    <a href="{{ .Permalink }}">{{ .Language.LanguageName }}</a>
  {{ end }}
  </nav>
{{ end }}

¡Y eso es todo lo que se necesitó! Parece que toda la estrategia que utiliza Hugo de identificar el idioma a partir del nombre del archivo hace que esos cambios de idioma sean fáciles de implementar.

Hacer que los menús cambien de idioma también

Lo último que faltaba para para que el sitio se sintiera verdaderamente multilingüe era hacer que el menú de la barra lateral también cambiara de idioma. Reemplacé las líneas que había agregado al archivo config.toml para definir el menú (ver arriba) por las siguientes líneas:


[[languages.en.menu.main]]
    name = "Tags"
    identifier = "tags"
    weight = 300
    url = "/tags/"

[[languages.en.menu.main]]
    name = "Categories"
    identifier = "categories"
    weight = 300
    url = "/categories/"

[[languages.es.menu.main]]
    name = "Etiquetas"
    identifier = "tags"
    weight = 300
    url = "/tags/"

[[languages.es.menu.main]]
    name = "Categorias"
    identifier = "categories"
    weight = 300
    url = "/categories/"

La clave con ese código es que la entrada de url = en inglés y español es la misma. Esto es lo que evita que Hugo muestre las etiquetas en español en la lista de etiquetas cuando el sitio está en inglés, y viceversa.

Agregar soporte de $\LaTeX$

Agregar soporte de $\LaTeX$ terminó siendo también más sencillo de lo que esperaba. Descubrí que había algunos inconvenientes al utilizar Mathjax con Hugo, como por ejemplo, tener que “escapar” los caracteres _, que se usan muy a menudo en el LaTeX, escribiendo \_. Al parecer, en algunas formas de implemetar soporte de Mathjax, también es necesario usar \\ en lugar de \ en todas partes (muy inconveniente, pues todos los comandos de LaTeX comienzan con \). Descubrí que la integración de Hugo con Katex tiene menos de estos inconvenientes, así que decidí usar Katex en vez de Mathjax a pesar de que Mathjax se usa más ampliamente que Katex.

Siguiendo esta entrada de blog, para habilitar el soporte de Katex, solo se necesita el siguiente código dentro de la etiqueta <head> en cualquier página que necesite cargar Katex, así que decidí agregarlo a mi archivo hook_head_end.html. Este es el código:

<!-- Katex Support-->
<!-- For a way to do this that only loads KaTeX if it is "invoked" in a post, see:
https://dzhg.dev/posts/2020/08/how-to-add-latex-support-in-hugo/ -->

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css" integrity="sha384-zB1R0rpPzHqg7Kpt0Aljp8JPLqbXI3bhnPWROx27a9N0Ll6ZP/+DiW/UqRcLbRjq" crossorigin="anonymous">

<!-- The loading of KaTeX is deferred to speed up page rendering -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js" integrity="sha384-y23I5Q6l+B6vatafAwxRu/0oK/79VlbSz7Q9aiSZUvyWYIYsd+qj+o24G5ZU2zJz" crossorigin="anonymous"></script>

<!-- To automatically render math in text elements, include the auto-render extension: -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/contrib/auto-render.min.js" integrity="sha384-kWPLUVMOks5AQFrykwIup5lo0m3iMkkHrD0uJ4H5cjeGihAutqP0yW0J6dpFiVkI"
crossorigin="anonymous"
onload='renderMathInElement(document.body);'></script>

<script>
    document.addEventListener("DOMContentLoaded", function() {
        renderMathInElement(document.body, {
            delimiters: [
                {left: "$$", right: "$$", display: true},
                {left: "\\[", right: "\\]", display: true},
                {left: "$", right: "$", display: false},
                {left: "\\(", right: "\\)", display: false}
            ]
        });
    });
</script>

Ya con esto, puedo escribir matemáticas en cualquier lugar usando $...$. Por ejemplo, $\int_{a_1}^{b_1} f_1(t)\thinspace dt$ se ve así: $\int_{a_1}^{b_1} f_1(t)\thinspace dt$.

También puedo usar “Matemáticas en modo ecuación” (centradas y en una nueva línea) con los símbolos usuales de LaTeX $$ ... $$. Por ejemplo, $$\int_{a_1}^{b_1} f_1(t)\thinspace dt$$ se ve así: $$\int_{a_1}^{b_1} f_1(t)\thinspace dt$$

Suscríbase

¿Quiere recibir un email cuando haya una nueva entrada de blog? Suscríbase acá.