Language switcher used twice generating different

I have the following language switcher as a partial included in both my header and footer. My issue is that the version used in the footer is generating different urls for the wrong pages. It looks pretty random. The one in the header is correct.

It’s the same code, the same context (current page). Yet it produces different urls somehow.

<!-- Language List -->
{{ $class := .Class }}
{{ $context := .Context }}
{{ $pageLang := $context.Lang }}
{{ $base:= urls.Parse site.Home.Permalink }}
{{ $siteLanguages := site.Home.AllTranslations }}
{{ $pageLink := replace (replace $context.RelPermalink (add $pageLang "/") "") $base.Path "/" }}

<div class="flex flex-row gap-2">
{{ if $context.IsTranslated }}
{{ with (index $siteLanguages 0) }}
  {{ if eq (string $pageLang) (string .Language) }}
    <p><a class="font-extrabold text-green-400" href="{{ replace (add .RelPermalink $pageLink) `//` `/` }}">{{ .Language.LanguageName }}</a></p>

  {{ else }}
    <p><a href="{{ replace (add .RelPermalink $pageLink) `//` `/` }}">{{ .Language.LanguageName }}</a></p>

  {{ end }}
{{ end }}
<p>/</p>
{{ with (index $siteLanguages 1) }}
  {{ if eq (string $pageLang) (string .Language) }}
    <p><a class="font-extrabold text-green-400" href="{{ replace (add .RelPermalink $pageLink) `//` `/` }}">{{ .Language.LanguageName }}</a></p>

  {{ else }}
    <p><a href="{{ replace (add .RelPermalink $pageLink) `//` `/` }}">{{ .Language.LanguageName }}</a></p>

  {{ end }}
{{ end }}
{{ else }}
<p><a href="/">EN</a> / <a href="/de/">DE</a></p>
{{ end }}

</div>

In both the header and the footer, it’s included like this:

{{ partial "components/language-switcher" (dict "Context" . "Class" "dark:bg-darkmode-theme-light") }}

and both the header and the footer are included from baseof.html like this:

    {{ partial "essentials/header.html" . }}
    ....
    {{ partial "essentials/footer.html" . }}

This somehow produces different results in the footer than in the header.

For example on my imprint page, I have the header correctly providing links to /imprint and /de/impint. And then the footer ends up having links to /categories and /de/categories.

Am I doing something wrong or am I somehow triggering a bug in hugo where it just randomly changes the context to whatever. This looks like the footer just ends up using the context of a completely different page. How is this even possible?

Is there a way to workaround this?

My hugo env:

hugo v0.123.8-5fed9c591b694f314e5939548e11cc3dcb79a79c+extended darwin/arm64 BuildDate=2024-03-07T13:14:42Z VendorInfo=brew
GOOS="darwin"
GOARCH="arm64"
GOVERSION="go1.22.1"
github.com/sass/libsass="3.6.5"
github.com/webmproject/libwebp="v1.3.2"

Without looking into the details, this seems a bit complicated and fragile. Is there some reason you aren’t just ranging over Page.Translations or Page.AllTranslations?

@jmooring I adapted the language switcher that came with hugoplate, which is what we are adapting for our website.

Do you have the same problem using the “un-adapted” switcher?

Yes, it seems to make no difference.

for reference, this is the original from hugoplate:

<!-- Language List -->
{{ $class := .Class }}
{{ $context := .Context }}
{{ $pageLang := $context.Lang }}
{{ $base:= urls.Parse site.Home.Permalink }}
{{ $siteLanguages := site.Home.AllTranslations }}
{{ $pageLink := replace (replace $context.RelPermalink (add $pageLang "/") "") $base.Path "/" }}

{{ if $context.IsTranslated }}
  <select class="{{ $class }}" onchange="location = this.value">
    {{ range $siteLanguages }}
      {{ if eq (string $pageLang) (string .Language) }}
        <option
          id="{{ .Language }}"
          value="{{ replace (add .RelPermalink $pageLink) `//` `/` }}"
          selected>
          {{ .Language.LanguageName }}
        </option>
      {{ else }}
        <option
          id="{{ .Language }}"
          value="{{ replace (add .RelPermalink $pageLink) `//` `/` }}">
          {{ .Language.LanguageName }}
        </option>
      {{ end }}
    {{ end }}
  </select>
{{ end }}

If you are having the same problem with the language switcher provided with the hugoplate theme, you should reach out to the theme author:
https://github.com/zeon-studio/hugoplate/issues

Will do, thanks. Do you have any suggestions for an alternative? You mentioned iterating over the page translations, which sounds like a good idea.

Follow the documentation links that I provided above. There is an example of a simple switcher on each page.

I am also facing the same issue with mine in the footer. The switcher in the docs works though. Any pointers?

{{- if .IsTranslated -}}
   {{ if site.IsMultiLingual }}
   <ul>
     <li>
       <a href="#">{{ $.Language }}</a>
       <ul>
         {{- $siteLanguages := site.Languages }}
         {{- range .Page.AllTranslations -}}
         {{- $translation := . -}}
         {{ range $siteLanguages -}}
         {{- if eq $translation.Lang .Lang -}}
         <li>
           <a href="{{ $translation.RelPermalink }}">{{ .LanguageName }}</a>
         </li>
         {{- end -}}
         {{- end -}}
         {{- end }}
       </ul>
     </li>
   </ul>
   {{- end }}
   {{- end }}

Is there some reason you’re not using that, or the examples here or here?

I tried the approach in the docs. Somehow .AllTranslations is empty on my page and .IsTranslated returns false; even though there is a translation. I’m not sure why but I guess it relates to something hugoplate is doing differently somehow.

I created an issue in their github: wrong translation links with language switcher in header and footer · Issue #103 · zeon-studio/hugoplate · GitHub

They closed my bug as I adapted their theme; fair enough. This looks potentially like a hugo bug where some kind of caching related logic ends up swapping my page object for that of a completely different page.

Is there anything that I can look for that might trigger that? Clearly something happens in between the header and the footer where the footer is basically receiving a completely different object than the header. Kind of worrying that that can happen at all.

Please share your project repository.

It’s a private repository: https://github.com/formation-res/website

I’ve just given you read access. Thanks for looking into this.

The preview of the website is live here: https://newww.tryformation.com

I actually gave the Translations one a try but it still generating different URLs when placed in the footer. On a new test site, it behaves as expected. I am still unsure how to narrow down the issue.

OK, I’ve cloned and built your site with hugo server. Which page should I visit, and how can I see that something’s wrong? And which of the three language switcher templates should I be looking at?

Guess the main reason is the different ways the hogoplate header and footer are included in baseof.html. They consider the footer to be a static one for each language. So they turn on caching of Partial for the footer in Production.

from baseof.html:

<!-- ... -->
    <!-- header (don't cache it) -->
    {{ partial "essentials/header.html" . }}
<!-- ... -->
    <!-- cache partial only in production -->
    {{ if hugo.IsProduction }}
      {{ partialCached "essentials/footer.html" . }}
      {{ partialCached "essentials/script.html" . }}
    {{ else }}
      {{ partial "essentials/footer.html" . }}
      {{ partial "essentials/script.html" . }}
    {{ end }}

So I guess in Development it will work, but in Production you get unpredictable links to the transalted pages

The footer is cached without variant, so the caching is not based on Context. Related to the parallel processing of all that stuff with goroutines you may not predict which pages footer makes it into the cache.

have a look at partials.IncludeCached

Each Site (or language) has its own partialCached cache, so each site will execute a partial once.

Hugo renders pages in parallel, and will render the partial more than once with concurrent calls to the partialCached function. After Hugo caches the rendered partial, new pages entering the build pipeline will use the cached result.

Possible Solutions:

  • turn of caching in production
  • pass an additional variant to the footer call in baseof.html.
    something like {{ partialCached "essentials/footer.html" . $ }}
    dunno if you will also turn of that caching for script.html
1 Like

Yuck.

For anyone else who stumbles across this, please don’t do what they did.

1 Like

Wow! I also added partialCached to the footer and removing it solves the issue! Might have been an oversight on my part when testing hugo --templateMetrics --templateMetricsHints since I made my own theme from scratch. I guess on Hugo Plate’s side, they should leave that decision to the user.

Can someone please log an issue against hugoplate advising them about what a bad idea this is?