Multilingual Hugo sites have a handful of spots where things drift apart quietly if you’re not paying attention. URLs in the wrong language, cover texts that don’t fit, translations that fall through the cracks. hugo-cms.nvim helps with some of them. The rest is front-matter discipline.
The commands from the walkthrough series are assumed here, this post is about the language layer on top.
URLs per Language
Hugo derives the URL from the bundle’s path. In a multilingual bundle:
content/blog/2026/markdown-und-theme-features/
├── index.md # default language (de)
└── index.en.md # translation
Default URLs:
/blog/2026/markdown-und-theme-features/(de)/en/blog/2026/markdown-und-theme-features/(en, same slug)
The English page inherits the German slug. Bad for SEO, bad in the address bar.
The fix is slug: in the per-language front matter:
# index.en.md
---
title: "Template: Markdown & Theme Features"
slug: "markdown-and-theme-features"
---
Result:
/blog/2026/markdown-und-theme-features/(de, from the directory name)/en/blog/2026/markdown-and-theme-features/(en, fromslug)
The directory name still matters. It determines section membership, {{< ref >}} uses it as a path, and it keeps Git and the editor readable. Rule of thumb: directory name = default-language slug, set slug: only in the non-default-language files.
Translation Workflow
For a new translated post:
:Hugo newcreates the bundle withindex.mdin the default language.- Duplicate
index.mdtoindex.<lang>.mdby hand. - Translate
title. - Set
slug:explicitly so the default-language slug isn’t inherited. - Translate
description. - Translate
cover.alt, if it’s there. - Translate the body.
hugo-cms.nvim syncs language-independent fields across siblings automatically. :Hugo draft toggles the draft flag in every language, :Hugo tags and :Hugo categories write the same tag and category set across all files (taxonomies tend to be shared anyway), :Hugo media cover sets cover.image across all language siblings.
Cover Images with Per-Language Alt Text
Hugo page bundles share their resources. A cover.jpg at content/blog/2026/post/cover.jpg is the same file for every language.
cover.image points at that filename in every language file, :Hugo media cover writes it to all of them at once. cover.alt, on the other hand, should be per language, because alt text is descriptive prose. German alt text on the English page is an accessibility fail. The plugin only prompts for alt text in the current buffer’s language, and only when the field is already there in the front matter.
Wanting an entirely different cover image per language means separate bundles, which costs you the resource-sharing benefit. In practice, a shared image with language-specific alt text is the right trade-off almost every time.
Where Things Go From Here
That keeps translations tidy. What to check before publishing, the publishing loop and a few gotchas from practice live in the post on workflow and gotchas.