Internationalization (i18n) in Qgoda has to be explicitely activated in the
configuration file _qgoda.yaml
. Although there are many options
available to customize your multilingual setup, you can get started with
setting just two configuration variables linguas
and po.textdomain
.
linguas
po.textdomain
po.msgid-bugs-address
po.copyright-holder
po.reload
po.mdextra
po.views
po.xgettext-tt2
po.xgettext
po.msgfmt
po.msgmerge
po.qgoda
asset.name
A typical multilanguage configuration in _qgoda.yaml
looks like this:
linguas: [en-us, bg, de]
po:
textdomain: net.qgoda.www
msgid-bugs-address: Guido Flohr <guido.flohr@cantanea.com>
copyright-holder: cantanea EOOD <http://www.cantanea.com/>
reload: 1
linguas
This is a list of language identifiers complying to RFC4647 section 2.1 but without any asterisk (*
) characters. An asterisk would not make sense here anyway.
The first language is the default language of your site.
The variable is mandatory!
po.textdomain
An arbitrary identifier for your website. The only restriction is that it has to be a valid filename for your operating system. Popular choices are the reverse hostname of your site (like net.qgoda.www
) or just a simple string like messages
. It really doesn't matter much.
This variable is also mandatory.
po.msgid-bugs-address
An address where translators should report problems with the original text to translate. The variable is optional.
po.copyright-holder
The copyright holder of the original content. The variable is optional.
po.reload
Set to 1 if you want translations to be visible without a restart of Qgoda after they have been compiled into .mo
files and installed.
Normally translations are loaded only, when Qgoda starts and then cached. Setting po.reload
to 1 results in a little performance penalty but may be useful while you are translating the site.
po.mdextra
A list of file name patterns for additional markdown files. By default, all documents with front matter are potentially considered translatable. If your site has other markdown files that should also be searched, you can list them here.
This variable is optional and rarely used.
po.views
A list of file name patterns for template files to search for translatable strings. It defaults to just _views
or whatever the configuration variable paths.views
points to.
po.xgettext-tt2
The location of the xgettext-tt2
program if it is not in $PATH
. The xgettext-tt2
program ships with Template-Plugin-Gettext which is automatically installed as a Qgoda dependency.
po.xgettext
The location of the xgettext
program if it is not in $PATH
. The xgettext
program is normally installed as part of a software package called gettext
or gettext-tools
.
po.msgfmt
The location of the msgmerge
program if it is not in $PATH
. The msgmerge
program is normally installed as part of a software package called gettext
or gettext-tools
.
po.msgmerge
The location of the msgmerge
program if it is not in $PATH
. The msgmerge
program is normally installed as part of a software package called gettext
or gettext-tools
.
po.qgoda
The location of the qgoda
program if it is not in $PATH
.
Qgoda has to know which language a particular document is written in. This means in practice that you have to set the asset.lingua
to an appropriate value, that is a language identifier complying to RFC4647 section 2.1 but without any asterisk (*
) characters.
You have several options to set asset.lingua
:
You can set the language in the document front matter:
---
title: Kalevala
lingua: fi
name: kalevala
---
You can save typing by specifying defaults for asset.lingua
in the global configuration file _qgoda.yaml
:
defaults:
- files:
- /en
- *.en.md
values:
lingua: en
This would set asset.lingua
to en
for all files in the top-level directory /en
and addtionally for all files that have names ending in .en.md
. See Defaults and Pattern Lists for more information.
asset.name
All language variants of the same content should share a common property that acts as a link between them. Although you are free to use any variable name you like, asset.name
is used throughout this documentation.
For obvious reasons, this identifier will always be configured in the document front matter.
Take for example a markdown file special-relativity.md
:
---
title: Special Relativity
lingua: en
name: special-relativity
...
If the German translation of that file is spezielle-relativitaetstheorie.md
, that translation's front matter will look like this:
---
title: Spezielle Relativitätstheorie
lingua: de
name: special-relativity
...
Both documents are alternate versions of the same content. They differ in the property asset.lingua
but they share a common value for the asset.name
property.
Now it is trivial to link from the English version of that document to the German version:
<a href="[% q.link(name=asset.name lingua='fr') %]>German version</a>
And you can link from other documents to the document about Einstein's special relativity regardless of the language like this:
See [% q.anchor(name='special-relativity' lingua=asset.lingua) %]!
And since you almost always want to link to documents in the same language as the current one, there is a shortcut version for the above:
See [% q.lanchor(name='special-relativity') %]!
By using q.lanchor()
instead of q.anchor()
you can omit the lingua
parameter as it is implied. See Referencing Languages for more information about this.
There are many different conventions for the directory structure and the file names on internationalized sites:
/en
, /de
, /fr
and
so on. This is a simple and robust approach.index.html
but for example index.html.en
or even index.en.html
.In a multilingual site, every document can be available in one or more languages. Unfortunately, there is only one /index.html
and therefore the sites home page or start page can exist in only one language.
One way to solve this problem is to use your site's default language for /index.html
. But there are smarter options:
You can use JavaScript to determine the user's preferred language and redirect them:
---
location: index.html
view: raw
---
<!DOCTYPE html>
<html>
<head>
<title>Qgoda Static Site Generator</title>
</head>
<body>
<script>
var lingua,
default_lingua = '[% config.linguas.0 %]',
supported = {};
[% FOREACH lingua IN config.linguas %]
supported['[% lingua %]'] = true;
[% END %]
for (i = 0;
navigator.languages != null && i < navigator.languages.length;
++i) {
var lang = navigator.languages[i].substr(0, 2);
if (supported[lang]) {
lingua = lang;
}
}
if (lingua == null) {
lingua = navigator.language || navigator.userLanguage;
if (lingua != null) {
lingua = lingua.substr(0, 2);
}
}
if (!supported[lingua])
lingua = default_lingua;
// This is based on the assumption that the start URI for language 'xy'
// is '/xy'. Change that to your needs!
document.location.href = '/' + lingua + '/';
</script>
<noscript>
<p>Please select your preferred language:</p>
<ul>
[% FOREACH lingua IN config.linguas %]
<li><a href="/[% lingua %]/">[% lingua %]</a></li>
[% END %]
</ul>
</noscript>
</body>
</html>
The above code assumes that you have top-level per-language directories like /en/
, /fr/
, and so on. If you choose a different directory layout, you have to change the link targets in lines 40 and 46 accordingly.
The best solution is to let the server redirect the user to their preferred language. Read on about content negotiation below.
Content negotiation happens when a web server (nginx, apache, ...) selects the appropriate language version of a piece of content based on the visitor's preference laid forth in their browser language preferences. See Simple Content Negotiation for Nginx for all the gory details of that. Contrary to what the name of the article suggests, it will shed sufficient light on the topic also for users of other web servers than nginx.
Content negotiation can be made arbitrarily complicated. In practice there is one single valid strategy: A visitor of your start page http://YOURDOMAIN
should be redirected to the start page in their preferred language and from that moment on they should be pinpointed to that language until they change it. That means that you have to implement content negotiation only for the start page which simplifies things a lot. This is because there is only room for one /index.html
and the best strategy is to make that just an entry point that brings you to the content in your preferred language.
Server-side solutions are described at the above mentioned blog post Simple Content Negotiation for Nginx but as a fallback and short of better ideas feel free to steal the start page for this site from https://github.com/gflohr/qgoda-site/blob/main/index.html.