Fill in the missing translations

Hi Hugo community,

According to the official website document, the purpose of lang.Merge is to combine pages in other languages, so that untranslated content can also be presented in other languages. For example, if the current Site Language is German, and a certain Page has only English version, then the lang.Merge function can be used to present English content.

But it’s confusing to use!
Any Idea how could I fill in the missing translations here?

<select id="select-language" onchange="location = this.value;" class="language-selector">
  {{ $siteLanguages := .Site.Languages}}
  {{ $pageLang := .Page.Lang}}
  {{ range .Page.AllTranslations }}
    {{ $translation := .}}
    {{ range $siteLanguages }}
      {{ if eq $translation.Lang .Lang }}
        {{ if eq $pageLang .Lang}}
          <option id="{{ $translation.Language }}" value="{{ $translation.URL }}" selected>{{ .LanguageName }}</option>
        {{ else }}
          <option id="{{ $translation.Language }}" value="{{ $translation.URL }}">{{ .LanguageName }}</option>
        {{ end }}
      {{ end }}
    {{ end }}
  {{ end }}
</select>

Your goal is unclear.

Assuming this site configuration:

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'

And assuming this content:

content/
├── en/
│   ├── foo.md
│   └── _index.md
├── es/
│   ├── foo.md
│   └── _index.md
└── fr/
    └── _index.md

When visiting http://localhost:1313/en/foo/

  • Should “French” appear in the <select> list?
  • If yes, which page should be shown when the user selects “French”?

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.

Use module mounts to fill in the gaps.
https://discourse.gohugo.io/t/do-all-page-bundles-need-localized-copies-once-you-add-a-new-language/37225/12

Oh, that’s a clever solution! Thank you, Joe.

This might work for me… however, I still need to flag when one of these “fill-ins” is not in the current language, so I can set appropriate JS or CSS flags for content editors. When such a flag is set, users will be able to request translations of the untranslated page, logging it to a lightweight Go API (this is a JAM stack, where most of the site is static – thank you, Hugo – but certain data, such as calendars, instance metadata, and preferences, must be dynamic and managed by users.

I’ll report back when I have a moment to create a POC on this fill-in method.