Good morning, Joe and Rozhan
This is a common problem for multilingual sites whose content is internationalized. Here is my scenario – similar to Rozhan’s:
hugo.toml
defaultContentLanguage = 'en'
defaultContentLanguageInSubdir = true
[languages.en]
weight = 1
languageName = 'English'
contentDir = 'content/en'
[languages.es]
weight = 2
languageName = 'Español'
contentDir = 'content/es'
[languages.fr]
weight = 3
languageName = 'Français'
contentDir = 'content/fr'
content folders
content/
├── en/
│ ├── foo.md
│ └── _index.md
├── es/
│ ├── foo.md
│ └── _index.md
└── fr/
└── _index.md
i18n folder
en.yaml
es.yaml
fr.yaml
- The base static site is localized using i18n translation files in the i18n folder, where static and navigation text is contained. A new language requires a native speaker to translate a single file, and the site is live in the new language. I am building tooling against an API built in Go to facilitate translations in a two step, pull-and-build process (future).
- Imagine that there are a 100 pages of core content in English, that are essential to the use of any new instance.
- Of these 100, 85 pages have been translated into Spanish. Since French is a brand new site translation, none of those pages have yet been translated into French. But it is going to take some months for all those pages to be updated, in the nuances and correct grammar of that language.
- While posts have not yet been translated into a target language, we need to serve the base language content, so that vital information is not missing, even if it requires the reader to struggle along in English.
- Fifteen pages of English will appear in the Spanish website. And All one hundred English pages will appear in the French site.
- As translations are completed, we will recompile and push to the server. This way, we will incrementally populate a new language site.
Right now, I am working through the Multilingual section of the .Site.GetPage method. What we need to add to the documentation is the best methodology for this common “content fill” scenario. Instead of saying “give me language X”, what we really need is “give me the default content if mine doesn’t exist”.
When I am referencing a specific page, I have figured out the following code. Here is a request within a partial to inject the content of the “about” page:
{{ $base := .Site.Sites.First }}
{{ $path := "/home/about" }}
{{ $about := default ($base.GetPage $path) (.Site.GetPath $path) }}
. . .
<div class="modalDialog">
{{ with $about }}{{ .Content }}{{ end }}
</div>
I would like to leave my French content directory empty except for the pages that have been translated. Much neater. When I control the context – as in the example above, requesting a specific existing page; or within a range clause, where I can intervene using lang.Merge and RegularPages – I can redirect to the content of the base language.
But the site visitors aren’t going to be happy with that. They have selected the French version of the site, and don’t want to be rerouted to /en-us/, to be stuck ion the English language version, where all the links will be to en-US pages.
I see that I have two types of content: the web site core content, pages I expect to exist in any language for the site to be complete; and dynamic content such as blog posts, which can be allowed to exist or not exist. To create a new language in the site, I will create a shell utility to create a new content directory (“es-VZ”, for example), and copy and paste the entire base-language content directory there. This way, hugo will generate valid pages for all languages, and we will never leave the language context. [is there a way to do this without generating these stub files??]
We will rely on a new front matter tag “translated” to know if a file has been translated or not. When a translator (or their tech helper) translates an .md file in a language folder, they will add “translated: true”. Since base language content may change while a language is being translates, we will not going rely on the file contents until that flag is set; instead, we will inject the base language content:
{{ $base := .Site.Sites.First }} <!-- base language always weight = 1 -->
{{ $translated := or (eq .Language.Lang $base.Language.Lang) (default false .Params.translated) }}
{{ $page := cond $translated .Page ($base.GetPage .Path) }}
. . .
{{- if not $translated -}}
<div class="tr-missing" onclick="tr.Request(this, '{{ .Path }}');">
<span>{{ T "trRequest" }}</span>
</div>
{{- end -}}
. . .
<p>{{ with $page }}{{ .Content }}{{ end }}</p>
This allows us to present a translation request button to the visitor, and to disregard the existing content if a translation has not occurred, injecting the base language content in its place.
By the way, the JS Translation class, whose instance is tr, accepts translation requests for any tagged object. This is a JAM stack, with a lightweight go api handling some minimal lifting across the user base. Our request method tr.Request(target, path, text) can be wired up to any DOM element, to store an authorized translator’s suggestion for those elements in an indexedDb table (so they can be overlaid in the app) and write them to the same “translation request” table in the api. In this way, they can perform their translation on line and in context; one of our dev utilities will pull new translations from the DB and inject them into the i18n files.
For security and for performance, I really do not want to move away from static pages. Since these pages are truly part of the website, there is no need for dynamic translation or database-driven dynamic content. The database should have trivial activity (such as storing a user’s site bookmarks, and community calendar entries).
Comments very welcome, especially where they shine light on techniques of aspects of go/hugo that I may have missed.