mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-29 22:29:56 +02:00
Add multilingual support in Hugo
Implements: * support to render: * content/post/whatever.en.md to /en/2015/12/22/whatever/index.html * content/post/whatever.fr.md to /fr/2015/12/22/whatever/index.html * gets enabled when `Multilingual:` is specified in config. * support having language switchers in templates, that know where the translated page is (with .Page.Translations) (when you're on /en/about/, you can have a "Francais" link pointing to /fr/a-propos/) * all translations are in the `.Page.Translations` map, including the current one. * easily tweak themes to support Multilingual mode * renders in a single swift, no need for two config files. Adds a couple of variables useful for multilingual sites Adds documentation (content/multilingual.md) Added language prefixing for all URL generation/permalinking see in the code base. Implements i18n. Leverages the great github.com/nicksnyder/go-i18n lib.. thanks Nick. * Adds "i18n" and "T" template functions..
This commit is contained in:
committed by
Bjørn Erik Pedersen
parent
faa3472fa2
commit
ec33732fbe
238
docs/content/content/multilingual.md
Normal file
238
docs/content/content/multilingual.md
Normal file
@@ -0,0 +1,238 @@
|
||||
---
|
||||
date: 2016-01-02T21:21:00Z
|
||||
menu:
|
||||
main:
|
||||
parent: content
|
||||
next: /content/example
|
||||
prev: /content/summaries
|
||||
title: Multilingual Mode
|
||||
weight: 68
|
||||
toc: true
|
||||
---
|
||||
|
||||
Since version 0.17, Hugo supports a native Multilingual mode. In your
|
||||
top-level `config.yaml` (or equivalent), you define the available
|
||||
languages in a `Multilingual` section such as:
|
||||
|
||||
```
|
||||
Multilingual:
|
||||
en:
|
||||
weight: 1
|
||||
title: "My blog"
|
||||
params:
|
||||
linkedin: "english-link"
|
||||
fr:
|
||||
weight: 2
|
||||
|
||||
title: "Mon blog"
|
||||
params:
|
||||
linkedin: "lien-francais"
|
||||
copyright: "Tout est miens"
|
||||
|
||||
copyright: "Everything is mine"
|
||||
```
|
||||
|
||||
Anything not defined in a `[lang]:` block will fall back to the global
|
||||
value for that key (like `copyright` for the `en` lang in this
|
||||
example).
|
||||
|
||||
With the config above, all content, sitemap, RSS feeds, paginations
|
||||
and taxonomy pages will be rendered under `/en` in English, and under
|
||||
`/fr` in French.
|
||||
|
||||
Only those keys are read under `Multilingual`: `weight`, `title`,
|
||||
`author`, `social`, `languageCode`, `copyright`, `disqusShortname`,
|
||||
`params` (which can contain a map of several other keys).
|
||||
|
||||
|
||||
### Translating your content
|
||||
|
||||
Translated articles are picked up by the name of the content files.
|
||||
|
||||
Example of translated articles:
|
||||
|
||||
1. `/content/about.en.md`
|
||||
2. `/content/about.fr.md`
|
||||
|
||||
You can also have:
|
||||
|
||||
1. `/content/about.md`
|
||||
2. `/content/about.fr.md`
|
||||
|
||||
in which case the config variable `DefaultContentLanguage` will be
|
||||
used to affect the default language `about.md`. This way, you can
|
||||
slowly start to translate your current content without having to
|
||||
rename everything.
|
||||
|
||||
If left unspecified, the value for `DefaultContentLanguage` defaults
|
||||
to `en`.
|
||||
|
||||
By having the same _base file name_, the content pieces are linked
|
||||
together as translated pieces. Only the content pieces in the language
|
||||
defined by **.Site.CurrentLanguage** will be rendered in a run of
|
||||
`hugo`. The translated content will be available in the
|
||||
`.Page.Translations` so you can create links to the corresponding
|
||||
translated pieces.
|
||||
|
||||
|
||||
### Language switching links
|
||||
|
||||
Here is a simple example if all your pages are translated:
|
||||
|
||||
```
|
||||
{{if .IsPage}}
|
||||
{{ range $txLang := .Site.Languages }}
|
||||
{{if isset $.Translations $txLang}}
|
||||
<a href="{{ (index $.Translations $txLang).Permalink }}">{{ i18n ( printf "language_switcher_%s" $txLang ) }}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .IsNode}}
|
||||
{{ range $txLang := .Site.Languages }}
|
||||
<a href="/{{$txLang}}">{{ i18n ( printf "language_switcher_%s" $txLang ) }}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
```
|
||||
|
||||
This is a more complete example. It handles missing translations and will support non-multilingual sites. Better for theme authors:
|
||||
|
||||
```
|
||||
{{if .Site.Multilingual}}
|
||||
{{if .IsPage}}
|
||||
{{ range $txLang := .Site.Languages }}
|
||||
{{if isset $.Translations $txLang}}
|
||||
<a href="{{ (index $.Translations $txLang).Permalink }}">{{ i18n ( printf "language_switcher_%s" $txLang ) }}</a>
|
||||
{{else}}
|
||||
<a href="/{{$txLang}}">{{ i18n ( printf "language_switcher_%s" $txLang ) }}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .IsNode}}
|
||||
{{ range $txLang := .Site.Languages }}
|
||||
<a href="/{{$txLang}}">{{ i18n ( printf "language_switcher_%s" $txLang ) }}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
```
|
||||
|
||||
This makes use of the **.Site.Languages** variable to create links to
|
||||
the other available languages. The order in which the languages are
|
||||
listed is defined by the `weight` attribute in each language under
|
||||
`Multilingual`.
|
||||
|
||||
This will also require you to have some content in your `i18n/` files
|
||||
(see below) that would look like:
|
||||
|
||||
```
|
||||
- id: language_switcher_en
|
||||
translation: "English"
|
||||
- id: language_switcher_fr
|
||||
translation: "Français"
|
||||
```
|
||||
|
||||
and a copy of this in translations for each language.
|
||||
|
||||
As you might notice, node pages link to the root of the other
|
||||
available translations (`/en`), as those pages do not necessarily have
|
||||
a translated counterpart.
|
||||
|
||||
Taxonomies (tags, categories) are completely segregated between
|
||||
translations and will have their own tag clouds and list views.
|
||||
|
||||
|
||||
### Translation of strings
|
||||
|
||||
Hugo uses [go-i18n](https://github.com/nicksnyder/go-i18n) to support
|
||||
string translations. Follow the link to find tools to manage your
|
||||
translation workflows.
|
||||
|
||||
Translations are collected from the `themes/[name]/i18n/` folder
|
||||
(built into the theme), as well as translations present in `i18n/` at
|
||||
the root of your project. In the `i18n`, the translations will be
|
||||
merged and take precedence over what is in the theme folder. Files in
|
||||
there follow RFC 5646 and should be named something like `en-US.yaml`,
|
||||
`fr.yaml`, etc..
|
||||
|
||||
From within your templates, use the `i18n` function as such:
|
||||
|
||||
```
|
||||
{{ i18n "home" }}
|
||||
```
|
||||
|
||||
to use a definition like this one in `i18n/en-US.yaml`:
|
||||
|
||||
```
|
||||
- id: home
|
||||
translation: "Home"
|
||||
```
|
||||
|
||||
|
||||
### Multilingual Themes support
|
||||
|
||||
To support Multilingual mode in your themes, you only need to make
|
||||
sure URLs defined manually (those not using `.Permalink` or `.URL`
|
||||
variables) in your templates are prefixed with `{{
|
||||
.Site.LanguagePrefix }}`. If `Multilingual` mode is enabled, the
|
||||
`LanguagePrefix` variable will equal `"/en"` (or whatever your
|
||||
`CurrentLanguage` is). If not enabled, it will be an empty string, so
|
||||
it is harmless for non-multilingual sites.
|
||||
|
||||
|
||||
### Multilingual index.html and 404.html
|
||||
|
||||
To redirect your users to their closest language, drop an `index.html`
|
||||
in `/static` of your site, with the following content (tailored to
|
||||
your needs) to redirect based on their browser's language:
|
||||
|
||||
```
|
||||
<html><head>
|
||||
<meta http-equiv="refresh" content="1;url=/en" /><!-- just in case JS doesn't work -->
|
||||
<script>
|
||||
lang = window.navigator.language.substr(0, 2);
|
||||
if (lang == "fr") {
|
||||
window.location = "/fr";
|
||||
} else {
|
||||
window.location = "/en";
|
||||
}
|
||||
|
||||
/* or simply:
|
||||
window.location = "/en";
|
||||
*/
|
||||
</script></head><body></body></html>
|
||||
```
|
||||
|
||||
An even simpler version will always redirect your users to a given language:
|
||||
|
||||
```
|
||||
<html><head>
|
||||
<meta http-equiv="refresh" content="0;url=/en" />
|
||||
</head><body></body></html>
|
||||
```
|
||||
|
||||
You can do something similar with your `404.html` page, as you don't
|
||||
know the language of someone arriving at a non-existing page. You
|
||||
could inspect the prefix of the navigator path in Javascript or use
|
||||
the browser's language detection like above.
|
||||
|
||||
|
||||
### Sitemaps
|
||||
|
||||
As sitemaps are generated once per language and live in
|
||||
`[lang]/sitemap.xml`. Write this content in `static/sitemap.xml` to
|
||||
link all your sitemaps together:
|
||||
|
||||
```
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<sitemap>
|
||||
<loc>https://example.com/en/sitemap.xml</loc>
|
||||
</sitemap>
|
||||
<sitemap>
|
||||
<loc>https://example.com/fr/sitemap.xml</loc>
|
||||
</sitemap>
|
||||
</sitemapindex>
|
||||
```
|
||||
|
||||
and explicitly list all the languages you want referenced.
|
@@ -38,7 +38,7 @@ each content piece are located in the usual place
|
||||
|
||||
<ul id="tags">
|
||||
{{ range .Params.tags }}
|
||||
<li><a href="{{ "/tags/" | relURL }}{{ . | urlize }}">{{ . }}</a> </li>
|
||||
<li><a href="{{ "/tags/" | relLangURL }}{{ . | urlize }}">{{ . }}</a> </li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
@@ -110,7 +110,8 @@ The following example displays all tag keys:
|
||||
|
||||
<ul id="all-tags">
|
||||
{{ range $name, $taxonomy := .Site.Taxonomies.tags }}
|
||||
<li><a href="{{ "/tags/" | relURL }}{{ $name | urlize }}">{{ $name }}</a></li>
|
||||
<<<<<<< HEAD
|
||||
<li><a href="{{ "/tags/" | relLangURL }}{{ $name | urlize }}">{{ $name }}</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
@@ -120,7 +121,7 @@ This example will list all taxonomies, each of their keys and all the content as
|
||||
<section>
|
||||
<ul>
|
||||
{{ range $taxonomyname, $taxonomy := .Site.Taxonomies }}
|
||||
<li><a href="{{ "/" | relURL}}{{ $taxonomyname | urlize }}">{{ $taxonomyname }}</a>
|
||||
<li><a href="{{ "/" | relLangURL}}{{ $taxonomyname | urlize }}">{{ $taxonomyname }}</a>
|
||||
<ul>
|
||||
{{ range $key, $value := $taxonomy }}
|
||||
<li> {{ $key }} </li>
|
||||
@@ -135,4 +136,3 @@ This example will list all taxonomies, each of their keys and all the content as
|
||||
{{ end }}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
|
@@ -29,7 +29,7 @@ Taxonomies can be ordered by either alphabetical key or by the number of content
|
||||
<ul>
|
||||
{{ $data := .Data }}
|
||||
{{ range $key, $value := .Data.Taxonomy.Alphabetical }}
|
||||
<li><a href="{{ $data.Plural }}/{{ $value.Name | urlize }}"> {{ $value.Name }} </a> {{ $value.Count }} </li>
|
||||
<li><a href="{{ .Site.LanguagePrefix }}/{{ $data.Plural }}/{{ $value.Name | urlize }}"> {{ $value.Name }} </a> {{ $value.Count }} </li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
@@ -38,7 +38,7 @@ Taxonomies can be ordered by either alphabetical key or by the number of content
|
||||
<ul>
|
||||
{{ $data := .Data }}
|
||||
{{ range $key, $value := .Data.Taxonomy.ByCount }}
|
||||
<li><a href="{{ $data.Plural }}/{{ $value.Name | urlize }}"> {{ $value.Name }} </a> {{ $value.Count }} </li>
|
||||
<li><a href="{{ .Site.LanguagePrefix }}/{{ $data.Plural }}/{{ $value.Name | urlize }}"> {{ $value.Name }} </a> {{ $value.Count }} </li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
|
@@ -435,6 +435,13 @@ e.g.
|
||||
|
||||
## Strings
|
||||
|
||||
### printf
|
||||
|
||||
Format a string using the standard `fmt.Sprintf` function. See [the go
|
||||
doc](https://golang.org/pkg/fmt/) for reference.
|
||||
|
||||
e.g., `{{ i18n ( printf "combined_%s" $var ) }}` or `{{ printf "formatted %.2f" 3.1416 }}`
|
||||
|
||||
### chomp
|
||||
Removes any trailing newline characters. Useful in a pipeline to remove newlines added by other processing (including `markdownify`).
|
||||
|
||||
@@ -726,7 +733,6 @@ CJK-like languages.
|
||||
<!-- outputs a content length of 8 runes. -->
|
||||
```
|
||||
|
||||
|
||||
### md5
|
||||
|
||||
`md5` hashes the given input and returns its MD5 checksum.
|
||||
@@ -752,6 +758,23 @@ This can be useful if you want to use Gravatar for generating a unique avatar:
|
||||
<!-- returns the string "c8b5b0e33d408246e30f53e32b8f7627a7a649d4" -->
|
||||
```
|
||||
|
||||
## Internationalization
|
||||
|
||||
### i18n
|
||||
|
||||
This translates a piece of content based on your `i18n/en-US.yaml`
|
||||
(and friends) files. You can use the
|
||||
[go-i18n](https://github.com/nicksnyder/go-i18n) tools to manage your
|
||||
translations. The translations can exist in both the theme and at the
|
||||
root of your repository.
|
||||
|
||||
e.g.: `{{ i18n "translation_id" }}`
|
||||
|
||||
|
||||
### T
|
||||
|
||||
`T` is an alias to `i18n`. E.g. `{{ T "translation_id" }}`.
|
||||
>>>>>>> Add multilingual support in Hugo
|
||||
|
||||
## Times
|
||||
|
||||
@@ -763,7 +786,6 @@ This can be useful if you want to use Gravatar for generating a unique avatar:
|
||||
* `{{ (time "2016-05-28").YearDay }}` → 149
|
||||
* `{{ mul 1000 (time "2016-05-28T10:30:00.00+10:00").Unix }}` → 1464395400000 (Unix time in milliseconds)
|
||||
|
||||
|
||||
## URLs
|
||||
|
||||
### absURL, relURL
|
||||
|
@@ -89,7 +89,7 @@ content tagged with each tag.
|
||||
<ul>
|
||||
{{ $data := .Data }}
|
||||
{{ range $key, $value := .Data.Terms }}
|
||||
<li><a href="{{ $data.Plural }}/{{ $key | urlize }}">{{ $key }}</a> {{ len $value }}</li>
|
||||
<li><a href="{{ .Site.LanguagePrefix }}/{{ $data.Plural }}/{{ $key | urlize }}">{{ $key }}</a> {{ len $value }}</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -109,7 +109,7 @@ Another example listing the content for each term (ordered by Date):
|
||||
|
||||
{{ $data := .Data }}
|
||||
{{ range $key,$value := .Data.Terms.ByCount }}
|
||||
<h2><a href="{{ $data.Plural }}/{{ $value.Name | urlize }}">{{ $value.Name }}</a> {{ $value.Count }}</h2>
|
||||
<h2><a href="{{ .Site.LanguagePrefix }}/{{ $data.Plural }}/{{ $value.Name | urlize }}">{{ $value.Name }}</a> {{ $value.Count }}</h2>
|
||||
<ul>
|
||||
{{ range $value.Pages.ByDate }}
|
||||
<li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
|
||||
@@ -140,7 +140,7 @@ Hugo can order the meta data in two different ways. It can be ordered:
|
||||
<ul>
|
||||
{{ $data := .Data }}
|
||||
{{ range $key, $value := .Data.Terms.Alphabetical }}
|
||||
<li><a href="{{ $data.Plural }}/{{ $value.Name | urlize }}">{{ $value.Name }}</a> {{ $value.Count }}</li>
|
||||
<li><a href="{{ .Site.LanguagePrefix }}/{{ $data.Plural }}/{{ $value.Name | urlize }}">{{ $value.Name }}</a> {{ $value.Count }}</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -158,7 +158,7 @@ Hugo can order the meta data in two different ways. It can be ordered:
|
||||
<ul>
|
||||
{{ $data := .Data }}
|
||||
{{ range $key, $value := .Data.Terms.ByCount }}
|
||||
<li><a href="{{ $data.Plural }}/{{ $value.Name | urlize }}">{{ $value.Name }}</a> {{ $value.Count }}</li>
|
||||
<li><a href="{{ .Site.LanguagePrefix }}/{{ $data.Plural }}/{{ $value.Name | urlize }}">{{ $value.Name }}</a> {{ $value.Count }}</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
|
@@ -58,6 +58,8 @@ matter, content or derived from file location.
|
||||
**.IsPage** Always true for page.<br>
|
||||
**.Site** See [Site Variables]({{< relref "#site-variables" >}}) below.<br>
|
||||
**.Hugo** See [Hugo Variables]({{< relref "#hugo-variables" >}}) below.<br>
|
||||
**.Translations** A map to other pages with the same filename, but with a different language-extension (like `post.fr.md`). Populated only if `Multilingual` is enabled in your site config.
|
||||
**.Lang** Taken from the language extension notation. Populated only if `Multilingual` is enabled for your site config.
|
||||
|
||||
## Page Params
|
||||
|
||||
@@ -119,9 +121,9 @@ includes taxonomies, lists and the homepage.
|
||||
**.Site** See [Site Variables]({{< relref "#site-variables" >}}) below.<br>
|
||||
**.Hugo** See [Hugo Variables]({{< relref "#hugo-variables" >}}) below.<br>
|
||||
|
||||
### Taxonomy Term Variables
|
||||
### Taxonomy Terms Node Variables
|
||||
|
||||
[Taxonomy Terms](/templates/terms/) pages are of the type "node" and have the following additional variables.
|
||||
[Taxonomy Terms](/templates/terms/) pages are of the type "node" and have the following additional variables. These are available in `layouts/_defaults/terms.html` for example.
|
||||
|
||||
**.Data.Singular** The singular name of the taxonomy<br>
|
||||
**.Data.Plural** The plural name of the taxonomy<br>
|
||||
@@ -132,14 +134,25 @@ includes taxonomies, lists and the homepage.
|
||||
|
||||
The last two can also be reversed: **.Data.Terms.Alphabetical.Reverse**, **.Data.Terms.ByCount.Reverse**.
|
||||
|
||||
### Taxonomies elsewhere
|
||||
|
||||
The **.Site.Taxonomies** variable holds all taxonomies defines site-wide. It is a map of the taxonomy name to a list of its values. For example: "tags" -> ["tag1", "tag2", "tag3"]. Each value, though, is not a string but rather a [Taxonomy variable](#the-taxonomy-variable).
|
||||
|
||||
#### The Taxonomy variable
|
||||
|
||||
The Taxonomy variable, available as **.Site.Taxonomies.tags** for example, contains the list of tags (values) and, for each of those, their corresponding content pages.
|
||||
|
||||
|
||||
|
||||
## Site Variables
|
||||
|
||||
Also available is `.Site` which has the following:
|
||||
|
||||
**.Site.BaseURL** The base URL for the site as defined in the site configuration file.<br>
|
||||
**.Site.RSSLink** The URL for the site RSS.<br>
|
||||
**.Site.Taxonomies** The [taxonomies](/taxonomies/usage/) for the entire site. Replaces the now-obsolete `.Site.Indexes` since v0.11.<br>
|
||||
**.Site.Pages** Array of all content ordered by Date, newest first. Replaces the now-deprecated `.Site.Recent` starting v0.13.<br>
|
||||
**.Site.Taxonomies** The [taxonomies](/taxonomies/usage/) for the entire site. Replaces the now-obsolete `.Site.Indexes` since v0.11. Also see section [Taxonomies elsewhere](#taxonomies-elsewhere).<br>
|
||||
**.Site.Pages** Array of all content ordered by Date, newest first. Replaces the now-deprecated `.Site.Recent` starting v0.13. This array contains only the pages in the current language.<br>
|
||||
**.Site.AllPages** Array of all pages regardless of their translation.<br>
|
||||
**.Site.Params** A container holding the values from the `params` section of your site configuration file. For example, a TOML config file might look like this:
|
||||
|
||||
baseurl = "http://yoursite.example.com/"
|
||||
@@ -152,7 +165,7 @@ Also available is `.Site` which has the following:
|
||||
**.Site.Menus** All of the menus in the site.<br>
|
||||
**.Site.Title** A string representing the title of the site.<br>
|
||||
**.Site.Author** A map of the authors as defined in the site configuration.<br>
|
||||
**.Site.LanguageCode** A string representing the language as defined in the site configuration.<br>
|
||||
**.Site.LanguageCode** A string representing the language as defined in the site configuration. This is mostly used to populate the RSS feeds with the right language code.<br>
|
||||
**.Site.DisqusShortname** A string representing the shortname of the Disqus shortcode as defined in the site configuration.<br>
|
||||
**.Site.GoogleAnalytics** A string representing your tracking code for Google Analytics as defined in the site configuration.<br>
|
||||
**.Site.Copyright** A string representing the copyright of your web site as defined in the site configuration.<br>
|
||||
@@ -160,6 +173,10 @@ Also available is `.Site` which has the following:
|
||||
**.Site.Permalinks** A string to override the default permalink format. Defined in the site configuration.<br>
|
||||
**.Site.BuildDrafts** A boolean (Default: false) to indicate whether to build drafts. Defined in the site configuration.<br>
|
||||
**.Site.Data** Custom data, see [Data Files](/extras/datafiles/).<br>
|
||||
**.Site.Multilingual** Whether the site supports internationalization of the content. With this mode enabled, all your posts' URLs will be prefixed with the language (ex: `/en/2016/01/01/my-post`)<br>
|
||||
**.Site.CurrentLanguage** This indicates which language you are currently rendering the website for. When using `Multilingual` mode, will render the site in this language. You can then run `hugo` again with a second `config` file, with the other languages. When using `i18n` and `T` template functions, it will use the `i18n/*.yaml` files (in either `/themes/[yourtheme]/i18n` or the `/i18n`, translations in the latter having precedence).<br>
|
||||
**.Site.LanguagePrefix** When `Multilingual` is enabled, this will hold `/{{ .Site.CurrentLanguage}}`, otherwise will be an empty string. Using this to prefix taxonomies or other hard-coded links ensures your keep your theme compatible with Multilingual configurations.
|
||||
**.Site.Languages** An ordered list of languages when Multilingual is enabled. Used in your templates to iterate through and create links to different languages.<br>
|
||||
|
||||
## File Variables
|
||||
|
||||
|
Reference in New Issue
Block a user