├Ś
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.

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.

config.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

|
14 Minuten Lesezeit
0
Dieser Beitrag wurde mit der Hugo-Version 0.112.3 erstellt.

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

© 2023 - Frank Kunert  -  Ich ├╝ber mich