Propuestas para una gestión multilingüe en Org Mode (un primer apunte)
Estado de la cuestión
A lo largo de mi trabajo en Org Mode —especialmente cuando lo uso como interfaz de alto
nivel para LATEX—, una de las cosas que más vengo echando en falta es un
consistente soporte multilingüe, especialmente asociado a las posibilidades de
exportación. Cuando nos referimos a «soporte multilingüe» queremos decir un sistema
parecido a lo que hace el paquete babel
en LATEX: definir una lengua principal
para el documento junto a la posibilidad de definir también una serie de lenguas
«secundarias» para activarlas o desactivarlas a voluntad sobre tramos de texto
determinados. Por desgracia, la situación dista mucho de ser ésa.
Los documentos de Org, en efecto, son monolingües de fábrica. Podemos indicar una lengua
principal mediante la directiva #+LANGUAGE:
, y según sea la lengua que hayamos escogido,
Org tomará una serie de decisiones a la hora de exportar el documento. Dichas decisiones
pueden concertarse perfectamente con la opción de exportación ':t
, que se encargará de
escoger las comillas adecuadas, de primer o segundo nivel, para la lengua en cuestión: una
suerte de funcionalidad de «comillas inteligentes», aunque no tan poderosa como la que
ofrece el paquete para LATEX csquotes.
Por supuesto, Org ofrece también los recursos y herramientas necesarias para salvar estas
limitaciones. Una cosa que yo venía usando bastante, especialmente para los comandos del
paquete babel
de LATEX \foreignlanguage{lengua}{texto}
, cuando se trata de texto
extranjero dentro de un párrafo, eran las polivalentes macros de sustitución. Por ejemplo,
podemos definir esta macro lg
que evalúa un condicional en Elisp. Si la exportación es a
LATEX, toma como segundo argumento (el primero es la lengua escogida) el tramo de
texto a encerrar dentro del segundo argumento de la macro \foreignlanguage
. Si la
exportación es a otro formato, de momento, devuelve sólo el segundo argumento, es decir,
el texto. Si luego queremos afinar más con otras salidas, podemos aplicar un
multi-condicional del estilo cond
, pero no es el caso pues aquí lo que necesitaba era la
salida a LATEX:
Así, podríamos poner en nuestro documento algo como esto:
Lorem ipsum dolor sit amet, {{{lg(english,consectetuer adipiscing elit)}}}.
que nos entregaría este esperable resultado en LATEX:
Lorem ipsum dolor sit amet, \foreignlanguage{english}{consectetuer adipiscing elit}.
Bien, este sistema, aunque nos viene a hacer el apaño, adolece de ciertos inconvenientes que sólo con el uso continuado he ido más o menos solventando. A saber:
Obsérvese que en la definición de la macro, justo antes y después del segundo argumento, he concatenado el carácter Unicode
U+200B
(ZERO WIDTH SPACE), para evitar que cualquier marca de énfasis de Org se exporte literalmente (lo que no se pretende) en contacto con las llaves de inicio o cierre del argumento en LATEX1. O sea, que esto:{{{lg(english,/consectetuer/ adipiscing elit)}}}.
entregue esto:
\foreignlanguage{english}{\emph{consectetuer} adipiscing elit}.
y no esto:
\foreignlanguage{english}{/consectetuer/ adipiscing elit}.
Otro inconveniente, éste mucho más engorroso, es el hecho de que el propio signo de separación de macros en Org, la coma, puede interferir claramente en el segundo argumento de la macro, al ser de naturaleza textual. Si queremos incluir allí una coma literal, debemos precederla de una barra invertida como carácter de escape:
{{{lg(english,consectetuer adipiscing elit\, y mucho texto más)}}}.
o, de lo contrario, la macro se exportará mal y el tramo de texto que queda tras la coma no aparecerá. Tener que escapar un signo tan habitual como una coma es una pesadez, y no es raro que nos olvidemos más de una vez de hacerlo. Para evitar estos lapsus (que nos pueden destrozar sin querer el documento de salida), tuve que apañármelas con esta función:
(defun arregla-comas-macro-org () (interactive) (let ((x (make-marker)) (y (make-marker))) (save-excursion (goto-char (point-min)) (while (re-search-forward "{{{lg(\\b[a-z]+\\b," nil t) (set-marker x (point)) (re-search-forward ")}}}" nil t) (set-marker y (point)) (save-restriction (narrow-to-region x y) (reemplaza "\\([^\\\\]\\)," "\\1\\\\,"))))))
y anclarla, siquiera de manera local, a un
before-save-hook
.
En fin, todo este sistema de las macros, si bien funciona perfectamente cuando se van limando contratiempos, no deja de ser un remiendo, en parte porque estamos llevando el recurso de las macros de sustitución casi al límite. De modo que me puse a barajar otras posibilidades.
Acotar texto multilingüe en Org: propuestas
A mi modo de ver, las soluciones al problema multilingüe de Org deben repartirse entre dos niveles diferenciados: un nivel intra-párrafo (in line) y un nivel supra-párrafo, necesario para acotar bloques de texto individualizados dentro del flujo de texto general.
Nivel intra-párrafo
Es decir, aquí nos interesa, una vez más, acotar segmentos de texto dentro de un párrafo
de tal forma que se exporten dentro de la macro de LATEX
\foreignlanguage{lengua}{texto}
. Lo primero que me vino a la mente fue idear algún
sistema de marcas complementario y exportar todo mediante un filtro escrito ad hoc.
Simple, pero más complejidad añadida innecesariamente. Es en ese momento cuando uno cae en
que no hay por qué inventar la rueda, cuando ya dentro de Org tenemos un recurso tan útil
como org-link-set-parameters
, que nos permite definir nuevos tipos de enlaces
añadiéndoles un formato de exportación arbitrario. Un ejemplo extenso de las posibilidades
y potencia de ese recurso de Org lo puede encontrar el lector en esta otra Gnuta donde
hablábamos de cómo generar «subfiguras» en Org para la exportación a LATEX y
HTML
2.
Así pues, podemos definir un primer tipo de enlace que tome la etiqueta «lang» y que se despliegue completamente. La estructura sería entonces esta:
Lorem ipsum dolor sit amet, [[lang:en][consectetuer adipiscing elit]].
Como se ve, podemos indicar la lengua mediante su correspondiente código ISO (para lo cual
nos viene de perlas la lista asociativa ya definida en la biblioteca ox-latex
org-latex-babel-language-alist) o de forma literal, tal y como lo entiende babel
en
LATEX, pero precedido de «!»:
Lorem ipsum dolor sit amet, [[lang:!english][consectetuer adipiscing elit]].
Para llegar a este tipo de enlace, entonces, tuve que escribir el código que sigue3:
(org-link-set-parameters "lang" :display 'full :face 'magit-header-line-key :export (lambda (ruta desc format) (cond ((eq format 'latex) (let ((langs org-latex-babel-language-alist)) (concat "\\foreignlanguage{" (if (string-match-p "!" ruta) (replace-regexp-in-string "!" "" ruta) (cdr (assoc ruta langs))) "}{" desc "}"))) ((or (eq format 'html) (eq format 'odt)) (format "%s" desc)))))
Obsérvese que dejo abierta en el condicional las posibilidades de un distinto tratamiento
para la salida a HTML
u odt
(véase esta línea del código precedente), pero de momento,
como no es una prioridad para mí, lo que devuelve es el texto acotado sin más.
Pero aún podemos afinar más las cosas. Como casi siempre estos fragmentos de texto
extranjero se ponen entrecomillados, podemos definir otro nuevo tipo de enlace
(complementario al anterior) que aprovecha la macro del paquete de LATEX csquotes
\hyphentextquote{lengua}{texto}
, que entrecomilla un pasaje y le carga las normas de
guionado correspondiente a la lengua escogida4.
(org-link-set-parameters "qlang" :display 'full :face 'magit-header-line-key :export (lambda (ruta desc format) (cond ((eq format 'latex) (let ((langs org-latex-babel-language-alist)) (concat "\\hyphentextquote{" (if (string-match-p "!" ruta) (replace-regexp-in-string "!" "" ruta) (cdr (assoc ruta langs))) "}{" desc "}"))) ((or (eq format 'html) (eq format 'odt)) ;; ojo, aquí habría que arreglar ;; lo de las comillas interiores (format "«%s»" desc)))))
Como sucede en el caso anterior, en este bloque también se deja abierta la posibilidad
para el formato en salidas distintas de LATEX. Pero habría que añadir alguna
solución ad hoc para el caso de las comillas interiores, de haberlas, ya que aquí no nos
podemos beneficiar de los automatismos de csquotes
(véase esta línea en el bloque
precedente).
Nivel supra-párrafo
A este nivel, donde queremos bloques de texto diferenciados para ser tratados en la
exportación como lenguas distintas de la principal, en la salida a LATEX nos
interesan los entornos de babel
otherlanguage{lengua}
y otherlanguage*{lengua}
(véase la documentación del paquete).
Org ya viene de fábrica con la útil funcionalidad de los bloques «especiales», que a
LATEX se exportan como entornos y a HTML
como DIVS. Así pues, podemos poner en
nuestro documento algo como esto (con un pasaje de Balzac):
#+begin_otherlanguage Nous exagérons le malheur et le bonheur de manière égale, nous ne sommes jamais aussi mauvais ni aussi heureux que nous le disons #+end_otherlanguage
Pero aún podemos hacer las cosas más afinadas, como veremos.
Bloques de código Org como bloques multilingües
Hace unos meses (a fecha de publicación de este artículo), en la lista de desarrollo de
Org Mode, un usuario hizo una propuesta que me pareció bastante interesante: ¿por qué no
aplicarles a los bloques de código de Org un argumento de cabecera :lang
? Esto tiene la
evidente ventaja de que podemos escribir Org dentro de Org (por decirlo de manera gruesa),
pero toda esa parte estaría tratada en la exportación como una lengua diferente. Sobre
esta base me puse a ensayar algo de código. Aplicar un nuevo argumento de cabecera a los
bloques Org no es algo complicado en principio. Bastaría con hacer algunas redefiniciones
del código original.
Primero escribí este par de funciones. Ambas encierran el segundo argumento (body
) en el
entorno de LaTeX adecuado, con la salvedad de que la segunda añade además la macro de
csquotes
\EnableQuotes
dentro del entorno:
(defun my-lang-org-backend (lang body) (cond ((org-export-derived-backend-p org-export-current-backend 'latex) (format "@@latex:\\begin{otherlanguage}{%s}@@\n%s\n@@latex:\\end{otherlanguage}@@" lang body)) ((or (org-export-derived-backend-p org-export-current-backend 'html) (org-export-derived-backend-p org-export-current-backend 'odt)) (format "%s" body)))) (defun my-lang-csquotes-org-backend (lang body) (cond ((org-export-derived-backend-p org-export-current-backend 'latex) (format "@@latex:\\begin{otherlanguage*}{%s}\n\\EnableQuotes@@\n%s\n@@latex:\\end{otherlanguage*}@@" lang body)) ((or (org-export-derived-backend-p org-export-current-backend 'html) (org-export-derived-backend-p org-export-current-backend 'odt)) (format "%s" body))))
Y a continuación, mi definición del bloque de código org
, que tomará dos parámetros:
lang
y lang-quotes
(para la versión con \EnableQuotes
):
(defun org-babel-execute:org (body params) "Execute a block of Org code with. This function is called by `org-babel-execute-src-block'." (let ((result-params (split-string (or (cdr (assq :results params)) ""))) (lang (cdr (assq :lang params))) (lang-quotes (cdr (assq :lang-quotes params))) (body (org-babel-expand-body:org (replace-regexp-in-string "^," "" body) params))) (cond (lang (my-lang-org-backend lang body)) (lang-quotes (my-lang-csquotes-org-backend lang-quotes body)) ((member "latex" result-params) (org-export-string-as (concat "#+Title: \n" body) 'latex t)) ((member "html" result-params) (org-export-string-as body 'html t)) ((member "ascii" result-params) (org-export-string-as body 'ascii t)) (t body))))
Un ejemplo de uso, con un texto de Hegel:
#+begin_src org :lang german :results replace :exports results Eine Erklärung, wie sie einer Schrift in einer Vorrede nach der Gewohnheit vorausgeschickt wird ---über den Zweck, den der Verfasser sich in ihr vorgesetzt, sowie über die Veranlassungen und das Verhältnis, worin er sie zu andern frühern oder gleichzeitigen Behandlungen desselben Gegenstandes zu stehen glaubt--- scheint bei einer philosophischen Schrift nicht nur überflüssig, sondern um der Natur der Sache willen sogar unpassend und zweckwidrig zu sein. #+end_src
Bloques `quote’ con atributos de LATEX.
Para la versión de desarrollo de Org (que se puede descargar desde su repositorio Git) me
aceptaron un parche que escribí para que los bloques quote
aceptaran opciones y
atributos de LATEX5. Dado que muchos pasajes independientes en lengua extranjera son
citas en bloque dentro de un documento, aprovechar el bloque quote
nos viene bien para
mantener la coherencia. Mediante mi parche podemos, pues, definir cosas como estas
(aprovechándonos, una vez más, de las facilidades del paquete csquotes
: en este caso del
entorno foreigndisplayquote
):
#+begin_quote Eine Erklärung, wie sie einer Schrift in einer Vorrede nach der Gewohnheit vorausgeschickt wird ---über den Zweck, den der Verfasser sich in ihr vorgesetzt, sowie über die Veranlassungen und das Verhältnis, worin er sie zu andern frühern oder gleichzeitigen Behandlungen desselben Gegenstandes zu stehen glaubt--- scheint bei einer philosophischen Schrift nicht nur überflüssig, sondern um der Natur der Sache willen sogar unpassend und zweckwidrig zu sein (Hegel). #+end_quote
∞
Publicado: 16/07/21
Última actualización: 16/08/23
Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.
Notas:
La inclusión de un carácter de espacio cero es un recurso bien conocido en Org para evitar malos contactos de las marcas de énfasis. Para más información, puede consultarse en el manual de Org la sección «16.11 Escape Character».
También hice un uso intensivo de ese recurso en mi paquete para Emacs
org-critical-edition
, que escribí para componer ediciones críticas desde Org Mode:
https://gitlab.com/maciaschain/org-critical-edition/-/tree/master
Si se quiere mostrar el enlace sin desplegar hay que eliminar el parémetro
:display
. El aspecto con que se mostrará el link lo controla el parámetro :face
. Aquí
uso, por comodidad, una «face» ya definida en el paquete magit
: magit-header-line-key
.
Puede definirse cualquier otra «face» nueva ad hoc o, simplemente, si queremos que los
links tengan el aspecto estándar, no incluir este parámetro.
Que nos cargue sólo los patrones de guionado y no todas las propiedades de una lengua es muy práctico en estos escenarios, ya que ciertas propiedades (como los espaciados en los signos de puntuación del francés) podrían producir efectos indeseados.
Por defecto, el bloque quote
sólo produce el entorno quote
en la salida a LATEX.