Multilingual Static Sites with Hugo — i18n & Localization Guide
In this tutorial, you'll learn about Multilingual Static Sites with Hugo. We cover key concepts, practical examples, and best practices.
Multilingual static sites with Hugo let you serve content in multiple languages using built-in internationalization — configure language-specific URLs, translation files, and hreflang tags with no external dependencies.
What You'll Learn
Why It Matters
International audiences expect content in their native language. A multilingual site expands your reach, improves user engagement, and signals relevance to search engines through hreflang annotations. Hugo's i18n system handles URL structure, content translation, template localization, and language-specific configuration — all within a single codebase and build step.
Real-World Use
A SaaS company documents their product in English, Spanish, French, and German using Hugo's multilingual mode. A news site serves region-specific content with automatic language redirection. An e-learning platform translates course content while maintaining shared navigation and layouts across all languages.
Multilingual Architecture
flowchart TD A[Hugo Config] --> B[Language Definitions] B --> C1[English Content] B --> C2[Spanish Content] B --> C3[French Content] C1 --> D1[/en/ Pages] C2 --> D2[/es/ Pages] C3 --> D3[/fr/ Pages] D1 --> E[Merged Sitemap] D2 --> E D3 --> E E --> F[hreflang Tags] F --> G[Search Engines] style A fill:#f90,color:#fff style E fill:#09f,color:#fff
Configuration
Language Setup
# hugo.toml — Multilingual configuration
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = false
[languages]
[languages.en]
languageCode = "en-US"
languageName = "English"
weight = 1
title = "DodaTech Tutorials"
[languages.en.params]
description = "Programming and security tutorials"
[languages.es]
languageCode = "es-ES"
languageName = "Espanol"
weight = 2
title = "Tutoriales de DodaTech"
[languages.es.params]
description = "Tutoriales de programacion y seguridad"
[languages.fr]
languageCode = "fr-FR"
languageName = "Francais"
weight = 3
title = "Tutoriels DodaTech"
[languages.fr.params]
description = "Tutoriels de programmation et de securite"
[languages.de]
languageCode = "de-DE"
languageName = "Deutsch"
weight = 4
title = "DodaTech Tutorials"
[languages.de.params]
description = "Programmier- und Sicherheits-Tutorials"
Expected behavior: Hugo generates separate output directories for each language (/en/, /es/, /fr/, /de/). The defaultContentLanguageInSubdir controls whether the default language gets a subdirectory or stays at the root. For more on SEO across languages, refer to the dedicated sitemap and SEO guide.
Content Translation Strategies
Translation by Filename
Hugo matches translated content files by filename convention:
content/
├── _index.md # English (default)
├── _index.es.md # Spanish
├── _index.fr.md # French
├── _index.de.md # German
├── getting-started/
│ ├── _index.md
│ ├── _index.es.md
│ ├── installation.md
│ └── installation.es.md
Translation Table with i18n
For UI strings, Hugo uses YAML translation files:
# i18n/en.yaml
read_more:
other: "Read more"
subscribe:
other: "Subscribe"
search_placeholder:
other: "Search tutorials..."
# i18n/es.yaml
read_more:
other: "Leer mas"
subscribe:
other: "Suscribirse"
search_placeholder:
other: "Buscar tutoriales..."
Using Translations in Templates
<nav>
<a href="{{ "/about" | relLangURL }}">{{ i18n "about" }}</a>
<a href="{{ "/blog" | relLangURL }}">{{ i18n "blog" }}</a>
<form action="{{ "/search" | relLangURL }}">
<input type="search" placeholder="{{ i18n "search_placeholder" }}">
</form>
</nav>
Expected behavior: The i18n function looks up the string key in the current language's translation file. relLangURL prepends the language prefix to URLs, generating language-correct paths.
Language Switcher
{{/* layouts/partials/language-switcher.html */}}
{{ if .Site.IsMultiLingual }}
<ul class="language-switcher">
{{ range .Site.Languages }}
<li>
<a href="{{ .LanguagePrefix }}{{ $.RelPermalink }}" hreflang="{{ .Lang }}">
{{ .LanguageName }}
</a>
</li>
{{ end }}
</ul>
{{ end }}
Expected behavior: The language switcher renders a list of links to the current page in each available language. Hugo's .Translations also provides direct access to translated versions of the current page.
SEO for Multilingual Sites
Hreflang Tags
{{/* layouts/partials/head.html — hreflang generation */}}
{{ if .Site.IsMultiLingual }}
{{ range .AllTranslations }}
<link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
{{ end }}
<link rel="alternate" hreflang="x-default" href="{{ .Site.BaseURL }}">
{{ end }}
Canonical URLs per Language
<link rel="canonical" href="{{ .Permalink }}">
{{ if .Translations }}
{{ range .Translations }}
<link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
{{ end }}
{{ end }}
RTL Language Support
For right-to-left languages like Arabic or Hebrew, the HTML dir attribute must switch to rtl:
# Add to hugo.toml
[languages.ar]
languageCode = "ar-SA"
languageName = "العربية"
weight = 5
title = "دروس DodaTech"
[languages.ar.params]
description = "دروس البرمجة والأمان"
rtl = true
<html lang="{{ .Site.Language.Lang }}" dir="{{ if .Site.Params.rtl }}rtl{{ else }}ltr{{ end }}">
Comparison of i18n Approaches
| Approach | Setup Complexity | Content Reuse | SEO Support | Use Case |
|---|---|---|---|---|
| Translation by filename | Low | Per-page | Full hreflang | Full site translation |
| i18n translation files | Low | UI strings only | None | Navigation, forms |
| Single-site, multi-language | Medium | Templates only | Limited | Language-specific views |
| Separate site per language | High | None | Complex | Independent language sites |
| Hybrid (filename + i18n) | Medium | Both | Full | Recommended approach |
Common Errors
1. Missing i18n Translation Key
When a translation key is missing, Hugo outputs the key name as plain text (e.g., [read_more]). To prevent this, define a fallback in hugo.toml:
[module]
[module.hugoVersion]
extended = true
[Languages]
defaultContentLanguage = "en"
enableMissingTranslationPlaceholders = false
2. Incorrect defaultContentLanguageInSubdir
Setting this to false (default) keeps the primary language at the root domain. If you want every language under a subdirectory (e.g., /en/, /es/), set it to true. Mixing this setting incorrectly after launch breaks all existing URLs.
3. Broken Language Switcher Links
If the language switcher links produce 404 errors, ensure .RelPermalink is used with the correct language context. Use relLangURL for page references and absLangURL for absolute URLs.
4. Inconsistent Content Across Languages
Google may penalize a site if the translated content differs significantly from the default language. Maintain equivalent content structure across all translations to preserve SEO value.
5. Untranslated UI Elements in Templates
Hardcoded strings in templates bypass the i18n system. Always use {{ i18n "key" }} for any user-facing text, even in language-specific layouts. Audit templates with hugo --printI18nWarnings to find missing translations.
Practice Questions
1. What filename convention does Hugo use for translated content?
Translated content files use the language code as a suffix: page.es.md for Spanish, page.fr.md for French. The default language file has no suffix.
2. How do you render a language switcher in Hugo templates?
Use {{ range .Site.Languages }} to iterate available languages and link to the current page's translation using .LanguagePrefix with $.RelPermalink.
3. What are hreflang tags and why are they important?
Hreflang tags tell search engines which language version of a page to serve based on the user's language preference and region. Without them, Google may index only one language version.
4. What is the purpose of defaultContentLanguageInSubdir?
It controls whether the default language content is served from the root URL (/) or from a language subdirectory (/en/). Changing this after launch breaks existing URLs.
5. Challenge: Create a multilingual Hugo site with English and Spanish. Add a language switcher, hreflang tags, translated navigation strings, and a page that exists in both languages. Verify the Spanish version loads at /es/ with correct translations.
Mini Project: International Documentation Site
Build a multilingual documentation site:
- Configure Hugo with three languages: English (default), Spanish, and French
- Create i18n translation files for navigation UI elements
- Translate three documentation pages into each language
- Add a language switcher partial that shows flags or language names
- Implement hreflang tags in the head partial
- Add a sitemap that includes all language versions
Test the multilingual setup:
hugo server -D --disableFastRender
# Visit /en/, /es/, and /fr/ to verify each language works
Verify the HTML output includes correct hreflang:
<link rel="alternate" hreflang="en" href="https://example.com/en/page/">
<link rel="alternate" hreflang="es" href="https://example.com/es/page/">
<link rel="alternate" hreflang="x-default" href="https://example.com/">
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro