box.matto.nl
Enjoying Open Source Software

Translate Texinfo files using po4a

Texinfo

Texinfo is a brilliant hypertext system that predates HTML. You access it with info, which is a text mode application. The content consists of a number of nodes. Each node is an information element. You access the nodes through menus, hyperlinks, and indices. Also you can "walk" through a info file with keys like [ and ]. See info info.

Leveraging the power of TeX, Texinfo files can be used to produce gorgeous looking PDF files, like the GNU books. Compared to generating PDF files from LaTeX, generating PDF files from Texinfo is incredible fast.

po4a

The po4a (PO for anything) eases documentation translations and their maintenance.

This is what this website says:

It extracts the translatable material from the original document, and places it into a PO file that is adapted to the translation process. Once this PO file is updated by the translator, po4a re-injects the translation into the structure of the original document to generate a translated document.

po4a supports several documentation formats, including Texinfo.

Workflow

In general, the workflow is something like this:

  • Create a PO-file from the orginal Texinfo file.
  • Add the translations to the PO-file.
  • Generate a translated Texinfo file.
  • Use the translated Texinfo file like any other Texinfo file, e.g., create an Info file and a PDF with it.

When the original Texinfo file changes:

  • Update the PO-file.
  • Add or change translations where needed.
  • Generate a new translated Texinfo file.

Store the original Texinfo file

In order for the update of the PO-file to work, you'll need both the original Texinfo file as well as the new version of the Texinfo file. It is therefor important to keep a copy of the original Texinfo file.

In the examples below, two versions of the Texinfo file are mentioned:

  • testinfo-v1.texi: this represents the original file to be translated.
  • testinfo-v2.texi: this represents a new version of the file to be translated.

Create a PO-file

To create the PO-file from the original Texinfo file, using the following command.

po4a-updatepo \
--format texinfo \
--master testinfo-v1.texi \
--po testinfo.po

Replace testinfo-v1.texi with the name of the original Texinfo file.

Replace testinfo.po with the desired name of the PO-file.

Translating the PO-file

The PO-file contains a lot of strings and paragraphs to be translated.

Translating strings

The entry for a string in the PO-file is something like this:

#. type: menuentry
#: testinfo-v2.texi:30
msgid "Fundamental stuff"
msgstr ""

The first line containing the type differs for the roles of the string in the Texinfo document.

  • The line starting with msgid is a line to be translated.
  • The line starting with msgstr is where the translation must be paced.

Translating paragraphs

The entry for a paragraph looks something likes this:

#. type: Plain text
#: testinfo.texi:24
msgid ""
"In this chapter I define the concepts that will be used throughout the rest "
"of the document.  Moreover, measures of efficiencies as well as bounds of "
"complexity will be introduced."
msgstr ""

The translation of the paragraph must be placed right below the line starting with msgstr:

#. type: Plain text
#: testinfo.texi:24
msgid ""
"In this chapter I define the concepts that will be used throughout the rest "
"of the document.  Moreover, measures of efficiencies as well as bounds of "
"complexity will be introduced."
msgstr ""
"In dit hoofdstuk definieer ik de concepten die gebruikt worden in de rest "
" ... "

The lines in the translated paragraph start with a double quote (") and end with a space and a double quote.

The last line of the translated paragraph ends with a double quote without a space, just like the last line of the paragraph in the original language.

(Example text copied from Writing Documentation, Part IV: Texinfo on Linux Gazette.)

Texinfo commands in paragraphs

Texinfo commands require special formatting of the lines.

For this, we make use of the \n formatting in the strings and paragraphs:

String example:

#. type: smallexample
#: testinfo.texi:1421
#, no-wrap
msgid "Original line to be translated\n"
msgstr "Translated line\n"

Sometimes po4a has recognized the Texinfo command and treated as such, which can be seen at the type of the PO-element, as here above.

Other times the Texinfo command appears inside a paragraphs to be translated. Here we make uses of careful \n formatting in the translated paragraph:

" ... "
"Some text inside the paragraph\n"
"@ifnottex \n@xref{Some-ref}.\n@end ifnottex\n"
"@iftex \n@xref{Some-other-ref}. \n@end iftex\n"
"Some other text inside the paragraph. "
" ... "

Not: the \n is directly followed by a double quote, without a space before the double quote.

Update a PO-file

When later the original Texinfo file gets an update, we have to reflect those changes in our translated file.

Here's how to update the PO-file. Note the use of the name of the new version of the Texinfo file (testinfo-v2.texi) and the name of original file the PO-file was created with (testinfo-v1.texi).

po4a-updatepo \
  --format texinfo \
  --master testinfo-v2.texi \
  --previous testinfo-v1.texi \
  --po testinfo.po

The PO-file in the command above is the same file we created in the first step and has been filled with our translations.

How to recognize changes in the updated PO-file

After running the po4a-updatepo command, the PO-file with our translations is changed. po4a has merged the changes into it.

This means we must go through the PO-file again, to add or change translations. Below follows how to recognize new text, deleted text and changed text.

New text

New lines and paragraphs can be recognized by an empty msgstr. Example:

msgid "Very new chapter"
msgstr ""

Deleted text

Deleted text can be recognized by a pound char (#) at the start of the msgid and msgstr lines. Example:

#~ msgid "This line will be deleted in the next version."
#~ msgstr "Deze regel wordt in de volgende versie verwijderd."

Changed lines

Changed lines are preceded by a line with #| msgid at the start. Example:

#| msgid "This line will be changed in the next version."
msgid "This line is now changed in this version."
msgstr "Deze regel wordt in de nieuwe vesie gewijzigd."
  • The line starting with #| msgid is the line as it was in the previous version of the Texinfo file.
  • The line starting with msgid is the line as it is in the new version of the Texinfo file.
  • The msgstr contains the previous translation, is no longer valid and must be changed into a translation of the new text.

Create translated texinfo file

po4a-translate \
  --format texinfo \
  --master testinfo-v2.texi \
  --po testinfo.po \
  --localized testinfo-nl.texi

Here, the input file testinfo-v2.texi is used as an example. The output file is testinfo-nl.texi, the Texinfo file that is generated using the PO-file and the Texinfo file it was created from.

For convenience, create a Makefile for this:

testinfo-nl.texi:   testinfo.po
    po4a-translate \
      --format texinfo \
      --master testinfo-v2.texi \
      --po testinfo.po \
      --localized testinfo-nl.texi

When only a part of the translation has been done, po4a will refuse to generate the Texinfo output file. This can be configured by using the switch -k, e.g., -k 30 to generate an output file when at least 30% of the translation has been done. The default is 80%.

Generate a PDF file from the texinfo file with the translation

makeinfo --pdf testinfo-nl.texi

To add this to the Makefile, you could do something like this:

testinfo-nl.pdf:    testinfo-nl.texi testinfo.po
    makeinfo --pdf testinfo-nl.texi

testinfo-nl.texi:   testinfo-nl.po
    po4a-translate \
     --format texinfo \
     --master testinfo.texi \
     --po testinfo.po \
     --localized testinfo-nl.texi

When there are some non fatal errors, the makeinfo command can be extended with the switch ---force to generate an output file even when there are errors.

Editing PO-files with Emacs

Here follow some tips for working in Emacs.

Spell check

When working on the PO file and inserting translations, the file will contain text in two languages, the original language and the language we are translating to.

An easy way to spellcheck a single entry is by marking it as a region and run M-x flyspell-region.

First, set the dictionary to be used with M-x ispell-change-dictionary.

Flyspell-mode

Flyspell-mode checks your spelling while you type. Just enable it --after having changed to the right dictionary-- with: ````

M-x flyspell-mode

Curly braces

Texinfo uses a lot of curly braces. Make live easier and enable electric-pair-mode:

M-x electric-pair-mode

This will let Emacs insert the closing curly brace for you.

.dir-locals.el

Set several options automagically with a file named .dir-locals.el in your directory, for example:

((nil . ((ispell-local-dictionary . "nl")
     (abbrev-mode . t)
     (eval . (flyspell-mode))
     (eval . (electric-pair-mode))
     (eval . (company-mode)))))

Where nl is the Dutch dictionary.

View local info file in Info-mode

To open in a local info file in info-mode, use C-u C-h i.

Working with paragraphs

For me, this works convenient:

  • First, write the paragraph with the translation just as normal text.
  • Next, when needed, correct the paragraph using fill-paragraph, normally with M-q.
  • Third, surround each line with double quotes, with a space before the double at the end of the line.
  • Make sure the last line just ends with a double quote, not a space and double quote.

Some small Elisp functions can help:

(defun my/quote-region ()
  "Surround each line in the region with quotes."
  (interactive)
  (save-excursion
    (save-restriction
      (narrow-to-region (region-beginning) (region-end))
      (goto-char (point-min))
      (replace-regexp "^" "\"")
      (goto-char (point-min))
      (replace-regexp "$" " \""))))

(defun my/unquote-region ()
  "Remove quotes around the lines in the region."
  (interactive)
  (save-excursion
    (save-restriction
      (narrow-to-region (region-beginning) (region-end))
      (goto-char (point-min))
      (replace-regexp "^\"" "")
      (goto-char (point-min))
      (replace-regexp "\"$" ""))))

(defun my/unquote-region-and-fill-paragraph ()
  "Remove quotes around the lines in the region and fill-paragraph"
  (interactive)
  (save-excursion
    (save-restriction
      (narrow-to-region (region-beginning) (region-end))
      (goto-char (point-min))
      (replace-regexp "^\"" "")
      (goto-char (point-min))
      (replace-regexp "\"$" "")
      (goto-char (point-min))
      (fill-paragraph))))

Example key bindings:
(define-key global-map (kbd "C-c n q") #'my/quote-region)
(define-key global-map (kbd "C-c n Q") #'my/unquote-region)
(define-key global-map (kbd "C-c n m") #'my/unquote-region-and-fill-paragraph)

The function my/quote-region can be improved: it leaves a space before the double quote on the last line.

Tags:

⇽ Books of January 2026 Books of February 2026 ⇾


Webrings

netizen-ring-button

<<- random ->>


100% made with ♥ by a human — no cookies, no trackers.
Proud member of the 250kb.club, the no-JS.club, the Blogroll.Club, and the Bukmark.Club.
Don’t let a billionaire’s algorithm control what you read — use an RSS feed reader.