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, from slug)

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:

  1. :Hugo new creates the bundle with index.md in the default language.
  2. Duplicate index.md to index.<lang>.md by hand.
  3. Translate title.
  4. Set slug: explicitly so the default-language slug isn’t inherited.
  5. Translate description.
  6. Translate cover.alt, if it’s there.
  7. 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.