×
Startseite RSS-Feed Info
i18n - Teil 3 von 3 aufeinander aufbauenden Anleitungen für das Erstellen einer Hugo multilingualen Website.

Hugo - Anleitung Teil 3 - i18n multilinguale Site erstellen

Hugo - Anleitung Teil 3 - i18n multilinguale Site erstellen

i18n - Teil 3 von 3 Anleitungen für das Erstellen einer Hugo multilingualen Website. Mit den folgenden Themen: Die T (Translate) Funktion von Hugo, das Umschalten der Sprache auf der Website inkl. Sourcecode und SCSS, Hugo Probleme mit multilingualen Tags, Tag Wolke als Themenübersicht, Tag Beitragsliste inkl. Sourcecode und SCSS, interne Verlinkung.

Beitrag überprüft am 07.06.2023

Ich habe den Inhalt dieses Beitrags überprüft. Der Inhalt ist noch aktuell.

Strings übersetzen mit der i18n- oder T-Funktion

Noch eine Übersetzungsfunktion? Ja und nein. 99% der Texte werden bei mir in der Markdown Datei index.en.md übersetzt. Die restlichen 1% können durch die i18n oder T Funktion übersetzt werden.

Die Hugo T-Funktion ist ein Alias für die Hugo i18nFunktion. Ich habe mich für die Abkürzung T = Translate entschieden. Diese Übersetzungsfunktion kann in Templates, Partials oder Shortcodes sehr nützlich sein. Also im Sourcecode.

Hugo verwaltet übersetzte Strings ähnlich wie die in PHP bekannten .po “portable object” Dateien.

Die Dateien werden nach dem Länderkürzel mit der Extension yaml, toml oder json benannt. Nachfolgend ein Auszug aus meiner themes/tekki/i18n/de.toml:

..
[LeseMehr]
  other = "Weiterlesen"
[naechsterBeitrag]
  other = "Nächster Beitrag"
[noscriptJS]
  other = "Kommentare können ohne JavaScript nicht angezeigt werden."
[readingTime]
  one = "1 Minute Lesezeit"
  other = "{{.Count}} Minuten Lesezeit"
[rssDescription]
  other = "Aktuelle Inhalte auf - "
[rssDescriptionTags]
  other = "Aktuelle Inhalte für das Tag - "
..

Und der gleiche Auszug aus der themes/tekki/i18n/en.toml:

..
[LeseMehr]
  other = "Read more"
[naechsterBeitrag]
  other = "Next post"
[noscriptJS]
  other = "Comments cannot be displayed without JavaScript."
[readingTime]
  one = "One minute to read"
  other = "{{.Count}} minutes to read"
[rssDescription]
  other = "Recent content on - "
[rssDescriptionTags]
  other = "Current content for the tag - "
..

Der Aufruf der TFunktion in einem Partial sieht dann wie folgt aus:

..
<div class="readingtime">
  {{- T "readingTime" .ReadingTime -}}
</div>
..

Das Ergebnis sieht in deutsch so aus:

Readingtime in deutsch
ReadingTime in deutsch

Und in englisch:

Readingtime in englisch
ReadingTime in englisch

Sprachumschalter auf der Webseite

Da ich nur 2 Sprachen auf meiner Website unterstütze, benötige ich kein Menü um die gewünschte Sprache auszuwählen. Wenn eine deutsche Webseite angezeigt wird, zeige ich die englische Flagge an. Bei englischen Texten die deutsche Flagge.

Am Anfang hatte ich Schwierigkeiten die Ausgabe von {{ range .Translations }} wirklich zu verstehen. Ich hatte erwartet, dass die Sprache der aktuellen Webseite ausgegeben wird. Dem ist nicht so. Die Ausgabe des Sprachkürzels im range habe ich als go-Kommentar im unten angezeigten Sourcecode stehen lassen.

Sprachumschalter Sourcecode

Aber erst einmal der Sourcecode. Man könnte daraus ein extra Partial machen, habe ich aber nicht. Bei mir steht der Sourcecode im Partial themes/tekki/layouts/partials/header.html:

..
<ul class="i18n">
  {{ if .IsTranslated }}
    {{ range .Translations }}
      {{/* .Site.Language.Lang */}}
      {{ if eq .Site.Language.Lang "en" }}
        {{ $imgpath := printf "%s%s" .Site.BaseURL "img/flag-great-britain.png" }}
        <li><a title="English" href="{{ .RelPermalink }}"><img id="trans-flag" src={{ $imgpath }} alt="Flag Great Britain" /></a></li>
      {{ else }}
        {{ $imgpath := printf "%s%s" .Site.BaseURL "img/flag-germany.png" }}
        <li><a title="Deutsch" href="{{ .RelPermalink }}"><img id="trans-flag" src={{ $imgpath }} alt="Flag Germany" /></a></li>
      {{ end }}
    {{ end }}
  {{ end }}
</ul>
..

Aus Gewohnheit habe ich den Sprachumschalter in ein ul HTML-Tag eingeschlossen. Die Liste wird bei 2 Sprachen aber immer nur ein Listenelement enthalten. Mit {{ if .IsTranslated }} wird abgefragt ob für die aktuelle Webseite eine Übersetzung existiert. Wenn nicht, wird auch keine Flagge angezeigt und die Symbole links neben der Flagge rutschen dann ganz nach rechts.

Wenn eine Übersetzung nicht vorhanden ist, wird sie auch in den Übersichten nicht angezeigt. Nicht in der Beitragsliste, bei der angezeigten Anzahl der Beiträge in der Tag Wolke usw. Was ich damit sagen will - es ist nicht schlimm, der nicht übersetzte Beitrag fehlt einfach.

{{ range .Translations }} stellt in jedem Loop, neben anderen Dingen, die .Site.Language.Lang der weiteren Sprachen für die aktuelle Webseite zur Verfügung. In meinem Fall, bei 2 Sprachen, wird logischerweise nur die andere Sprache signalisiert. Im ersten Moment war dies für mich unlogisch, aber wenn man darüber etwas nachdenkt kann es gar nicht anders gemacht werden.

In der folgenden if-Anweisung frage ich ob die Sprache englisch ist. Wenn ja, wird die englische Flagge angezeigt. Wenn nein, die deutsche.

Die png-Dateien der Flaggen habe ich im Verzeichnis static/img gespeichert. Mit printf bastel ich mir den Link, zu der jeweiligen Flagge, mit der BaseURL und dem Dateinamen zusammen. Danach folgt der in ein li HTML-Tag eingepackte Link zu der übersetzten Webseite. Dem Link wird kein Text sondern die Flagge als image übergeben.

{{ range .Translations }} übergibt in jedem Loop also alle Daten der übersetzten Webseite. Deshalb ist mit .RelPermalink der Zugriff auf die übersetzte Seite möglich. Wenn man die Logik dahinter erst einmal verstanden hat, ist es eigentlich einfach.

Sprachumschalter SCSS

.i18n {
	padding-left: 0;
	margin-bottom: 0;
	list-style: none;

	li {
		padding-left: 1.0rem;

		a {
			background-image: none;
		}
		#trans-flag {
			display: flex;
			height: 1.3rem;
 			margin-top: 0.9rem; 
			transition: margin-top 2.0s;
 			border: 1px solid var(--wk-accent-border-color);
		}
	}			
}

Beim a HTML-Tag wird das background-image ausgeschaltet. Dadurch wird die Animation, die ansonsten durch die Berührung mit dem Maus-Cursor ausgelöst wird, abgeschaltet.

Hugo und multilinguale Tag Probleme

Am Anfang sind mir die Probleme mit multilingualen Tags gar nicht aufgefallen. In meinem Blog schreibe ich nur über IT-Themen. Englische IT-Begriffe übersetzt man nicht, da einen sonst keiner mehr versteht. Die Probleme traten erst auf als ich das Tag #kurztipp auf der deutschen Seite und das Gegenstück dazu #quick-tip auf der englischen Seite erstellt habe. Mit diesem Tag wollte ich Blogbeiträge kennzeichnen, die ein Thema kurz darstellen und eine Problemlösung dazu anbieten.

Meine Menüpunkte für die Navigation auf dieser Website beschränken sich auf das allernötigste. Ich habe das Theme von Anfang an so konzipiert, dass die eigentliche Navigation über eine Tag-Wolke erfolgen kann. Wenn der Besucher überhaupt navigieren möchte.

Obwohl ich auf den englischen Webseiten im Front Matter das Tag #quick-tip angegeben habe, wurde in der englischen Tag-Wolke #kurztipp ausgegeben. Im englischen Teaser und der Beitragsseite ebenfalls. In der Hugo Dokumentation findet man dazu gar nichts. Und eine Google Recherche ergibt auch nur ganz, ganz spärliche Hinweise.

Kurz und gut, Joe Mooring aus dem Hugo Team hat mir in diesem Fall mal wieder geholfen. Meine Anfrage im Hugo Forum finden Sie hier: Multilingual Tags - bypass default linking

Es ist also so, dass sich Beiträge über den Front Matter Parameter translationKey gegenseitig finden. Die Übersetzung von Tags funktioniert dadurch aber nicht. Wie auch? Wie soll der Hugo Generator einen Zusammenhang zwischen den Tag-Begriffen kurztipp und quick-tip herstellen? Das geht nur über eine vom Programmierer zur Verfügung gestellte Verbindung. Dazu weiter unten mehr.

Fazit: Ohne eine Verbindungsdatei stellt der Hugo Generator den Beitrag der Hauptsprache, in meinem Fall der deutsche Artikel mit den “deutschen” Tag-Begriffen, als die oberste Instanz fest und schmeißt Tag-Begriffe im verbundenen englischen Artikel, die nicht übereinstimmen raus und übernimmt die Tag-Begriffe der obersten Instanz.

Aber das Problem zieht weitere Kreise. Ich habe mich vor kurzem mit Hugos RSS-Feeds beschäftigt. Und siehe da, meine multilingualen Probleme mit kurztipp/quick-tip und bilder/images sind wieder da. Ich hatte noch keine Zeit mich darum zu kümmern. Deshalb können Sie den Fehler auf meiner Seite - RSS-Feed Informationen - nachvollziehen. Rufen Sie zuerst die deutsche Seite auf und schauen Sie sich den XML Channel Title von #bilder oder #kurztipp an. Und danach die englische Version.

Tags in einer Hugo Website

Wenn der Zugriff auf Informationen für den suchenden strukturierter angeboten werden soll, ist eine Taxonomie sehr hilfreich. Eine Suchmaschine geht noch einen Schritt weiter und tiefer. Es kommt darauf an was man mit einer Taxonomie erreichen möchte. In meinem Fall, für eine schnelle Themenübersicht, darf eine Taxonomie nicht zu üppig sein. Ansonsten wird sie unübersichtlich. Ich neige selber dazu, zu viele Tags zu vergeben.

Für diese Website habe ich mich gegen Kategorien entschieden. Tags erfüllen in diesem Fall detailierter den Wunsch nach einer schnellen Themenübersicht.

hugo.toml Änderung

Hugo erstellt automatisch Taxonomien für Kategorien und Tags. Wenn man dies nicht möchte, kann man es komplett abstellen oder wie in meinem Fall in der hugo.toml anpassen:

..
[taxonomies]
  tag = "tags"
..

Tags im Front Matter der Webseite

Die Tags für diese Webseite werden im Front Matter der index.md bzw. index.en.md wie folgt festgelegt:

---
..
tags: ["hugo", "multilingual", "webdesign"]
..
---

Die Lösung des Problems mit den multilingualen Tags

Das weiter oben beschriebene Problem, dass Tags der Hauptsprache nicht übersetzt werden, kann durch das zur Verfügung stellen von Markdown Dateien gelöst werden. Diese Dateien enthalten nur wenige Front Matter Angaben.

Die Platzierung der Struktur im content Verzeichnis ist sehr wichtig. Da es sich um Tags handelt, muss der Verzeichnisname auch tags lauten.

content/
  ├── tags/
        ├── bilder/
              ├── index.en.md
              └── index.md
        ├── kurztipp/
              ├── _index.en.md
              └── _index.md

Für die zu übersetzenden Tags, zurzeit sind das bei mir die Begriffe bilder und kurztipp, muss jeweils ein Verzeichnis mit gleichlautendem Namen unterhalb von tags angelegt werden.

Die Markdown Datei für die Hauptsprache content/tags/kurztipp/index.md, beinhaltet den Tagnamen im title, in meinem Fall in deutsch. Mehr Angaben stehen nicht in der Markdown Datei.

---
title: "kurztipp"
draft: false
---

Das zu übersetzende Gegenstück in der Datei content/tags/kurztipp/index.en.md, beinhaltet im title den übersetzten Tagnamen. Zusätzlich wird die relative URL für den Link zur Tag Beitragsliste angegeben.

---
title: "quick-tip"
draft: false
url: 'tags/quick-tip'
---

In Kombination mit diesen “Übersetzungsdateien” wird der Tagname in der deutschen und englischen Version korrekt angezeigt.

Multilinguale Hugo Tag Wolke

Als ich noch mit dem CMS WordPress gearbeitet habe, war eine Tag Wolke immer Bestandteil meiner Websites. Wenn die Anzahl der verschiedenen Tags nicht zu umfangreich ist, sieht so etwas einfach gut aus.

Auf der Website von Henrik Sommerfeld habe ich eine Portierung aus dem WordPress Umfeld gefunden. Danke dafür - Henrik Sommerfeld - Hugo Tag Cloud .

List Template für die i18n Hugo Tag Wolke

Da ich die Tag Wolke als zentrales Navigationselement nutze, habe ich den Sourcecode in das List Template themes/tekki/layouts/taxonomy/list.html übernommen.

{{ define "main" }}
<section class="tag-content">
	<div class="container">
		<h1>{{ .Title }}</h1>
		<div class="tag-cloud">
			<!-- Original source:  https://www.henriksommerfeld.se/hugo-tag-could/ -->
			{{- if gt (len .Site.Taxonomies.tags) 0 -}}
				{{- $fontUnit := "rem" -}}
				{{- $largestFontSize := 1.3 -}}
				{{- $smallestFontSize := 1.0 -}}
				{{- $fontSizeSpread := sub $largestFontSize $smallestFontSize -}}
				<!--<div>Font size unit: {{ $fontUnit }}</div>
				<div>Font min size: {{ $smallestFontSize }}</div>
				<div>Font max size: {{ $largestFontSize }}</div>
				<div>Font size spread: {{ $fontSizeSpread }}</div>-->

				{{- $maxCount := 3 -}}
				<!--<div>Max tag count: {{ $maxCount }}</div>-->
				{{- $minCount := 1 -}}
				<!--<div>Min tag count: {{ $minCount }}</div>-->
				{{- $countSpread := sub $maxCount $minCount -}}
				<!--<div>Tag count spread: {{ $countSpread }}</div>-->

				{{- $.Scratch.Set "sizeStep" 0 -}}
				{{- if gt $countSpread 0 -}}
						{{- $.Scratch.Set "sizeStep" ( div $fontSizeSpread $countSpread ) -}}
				{{- end -}}
				{{- $sizeStep := ( $.Scratch.Get "sizeStep" ) -}}
				<!-- <div>Font step: {{ $sizeStep }}</div> -->

				<div class="widget">
					<div class="tag-cloud-tags widget-content">
						{{- $taxonomy := "tags" -}}
						{{- range $term, $weightedPages := index site.Taxonomies $taxonomy -}}
							{{- $t := site.GetPage (printf "/%s/%s" $taxonomy $term) -}}
							{{- $currentFontSize := (add $smallestFontSize (mul (sub $weightedPages.Count $minCount) $sizeStep)) -}}
								<a href="{{ $t.RelPermalink }}" aria-label="{{ $t.Title }} ({{ $weightedPages.Count }} posts)" class="tag-link" style="font-size:{{- $currentFontSize -}}rem; color:{{- partial "shuffelColor.html" . -}}; transform:rotate({{- partial "shuffelRotate.html" . -}});">{{- $t.Title -}}
									<span style="font-size: {{- $smallestFontSize -}}rem"> ({{- $weightedPages.Count -}})</span></a>
						{{- end -}}
					</div>
				</div>
			{{- end -}}
		</div>
  </div>
</section>
{{end}}

Hinter dem Tag-Namen gebe ich die Anzahl der Beiträge für diesen Tag-Namen aus. Henrik Sommerfeld hat das Rotate der Tag-Namen mit JavaScript realisiert. Ich habe dazu in meinem Partial shuffleRotate.html Hugos Go-Funktionen genutzt. Im Partial shuffleColor.html weise ich ebenfalls per Zufall, eine der vorgegebenen Farben dem Tag-Namen zu.

Partial shuffleColor.html für die i18n Hugo Tag Wolke

{{ $randomColor := shuffle (slice "#143a84" "#6f42c1" "#d63384" "#fd7e14" "#0dcaf0" "#198754" "#b56b00") }}
{{ range first 1 $randomColor }}
{{- . -}}
{{ end }

Die Go-Funktion slice erstellt ein Array mit 7 verschiedenen Farbcodes. shuffle vermischt die Reihenfolge der Farbcodes und im range wird der erste Eintrag, aus dem vermischten Array, von dem Partial an die aufrufende Stelle zurückgegeben.

Partial shuffleRotate.html für die i18n Hugo Tag Wolke

{{ $randomRotate := shuffle (slice "-3deg" "0deg" "3deg") }}
{{ range first 1 $randomRotate }}
{{- . -}}
{{ end }}

Die Go-Funktion slice erstellt ein Array mit 3 verschiedenen Gradangaben. shuffle vermischt das Array und range gibt den ersten Array Eintrag an die aufrufende Stelle zurück.

SCSS für die i18n Hugo Tag Wolke

.tag-cloud {
	.tag-link {
		display: inline-block;
		margin-right: 1.5rem;
	}
}

Sehr übersichtlich, aber absolut notwendig. Ohne display: inline-block; wird das Rotate nicht angezeigt.

Tag Template - alle Teaser eines Tags

Wenn in der Tag Wolke, im Beitragsteaser oder in einer Beitragsseite auf ein Tag geklickt wird, werden alle relevanten Beitragsteaser für dieses Tag angezeigt.

Tag Template - für ein Tag

Das Template für die Anzeige habe ich unter themes/tekki/layouts/taxonomy/tag.html gespeichert.

{{- define "main" -}}
<section class="container tag">
	<h1>
		<span class="tag-icon">
      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-tags" viewBox="0 0 16 16">
        <path d="M3 2v4.586l7 7L14.586 9l-7-7H3zM2 2a1 1 0 0 1 1-1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 2 6.586V2z"/>
        <path d="M5.5 5a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0 1a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM1 7.086a1 1 0 0 0 .293.707L8.75 15.25l-.043.043a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 0 7.586V3a1 1 0 0 1 1-1v5.086z"/>
      </svg>
    </span>
		#{{- .Title -}}
	</h1>
	<div class="tagArticle">
		<p class="tagHint">{{- T "tagHint1" -}}<span>#{{- .Title -}}</span>{{- T "tagHint2" -}}</p>
	</div>
</section>
{{- partial "tagTeaser.html" . -}}
{{- end -}}

Das Template ruft mit {{ partial "tagTeaser.html" . }} das Partial tagTeaser.html auf.

Partial tagTeaser.html - der Aufruf der Artikel Teaser

Das Partial tagTeaser.html benötigt den Bezug zu Tags und ruft in einem range wiederum das Partial articleTeaser.html auf, welches den eigentlichen Teaser ausgibt.

{{- range .Paginator.Pages.ByLastmod.Reverse -}}
{{- partial "articleTeaser.html" . -}}
{{- end -}}
{{- template "_internal/pagination.html" . -}}

Dieser zweite Schritt ist notwendig, weil ich das Partial articelTeaser.html nicht nur für Tags sondern auch für Beiträge benutze. articelTeaser.html erkläre ich in dieser Serie nicht, da dort keine multilingualen Besonderheiten verarbeitet werden.

SCSS für das Tag Template

.tag {
	margin-bottom: 2.0rem;

	.tagHint {
		span {
			font-size: 1.2rem;
    	font-weight: 600;
    	margin: 0 0.5rem;
		}
	}
}

Interne Verlinkung von i18n Webseiten

Die interne Verlinkung der Webseiten stelle ich mit einem konstruierten Beispiel dar. Der Variablen $url wird die URL des Teil 1 dieser Serie zugewiesen. Danach wird ein a HTML-Tag mit dieser Variablen aufgerufen.

$url := "/hugo-i18n-howto-teil-1/"
<a href={{ $url }}>Hugo - Anleitung Teil 1 - i18n multilinguale Site erstellen</a>

Das Ergebnis ist folgender Link: Hugo - Anleitung Teil 1 - i18n multilinguale Site erstellen

Damit das Ganze nicht nur lokal funktioniert, muss in der hugo.toml der Parameter baseURL mit Ihrem Domainnamen angegeben werden. Mein Eintrag sieht so aus:

baseURL = "https://tekki-tipps.de/"
$url := "/en/hugo-i18n-howto-part-1/"
<a href={{ $url }}>Hugo - Tutorial Part 1 - Create i18n multilingual site</a>

Das Ergebnis ist folgender Link: Hugo - Tutorial Part 1 - Create i18n multilingual site

Fazit

Eine multilinguale Website macht Arbeit. Die Reichweite wird dadurch aber enorm erhöht. Damit ist das Ende dieser Serie erreicht. Ich hoffe, dass Ihnen diese Informationen weiterhelfen. Wenn das der Fall ist wäre ein Kommentar nett.

Linkliste zu diesem Beitrag

Das könnte Sie auch interessieren

Update:  |
15 Minuten Lesezeit
0
Dieser Beitrag wurde mit der Hugo-Version 0.115.2 erstellt.

Kommentare werden bei deutscher Spracheinstellung nicht in der englischen Variante der Webseite angezeigt und umgekehrt.

© 2023 - Frank Kunert  -  Ich über mich