Hugo does multilingual sites either by giving each language its own content directory (content/en and content/de, or content for the default language and content.de for the other) or by keeping every language in one directory and putting the language code in the filename (post.md, post.de.md). Emet needs the same mapping so it can switch between them, find an entry’s translations and work out URLs.
languages:
- code: en
name: English
contentDir: content/en
default: true
- code: de
name: Deutsch
contentDir: content/de| Key | What it does |
|---|---|
code |
The language code, as in your Hugo config. |
name |
What the language switcher calls it. |
contentDir |
That language’s content directory, relative to the Hugo root. |
default |
true on exactly one language. If none is marked, the first in the list is the default. |
Leave the block out and Emet assumes a single language, en, in content. The language switcher only appears once there are two or more.
Next to the block, one top-level key picks the layout:
| Key | What it does |
|---|---|
languages_layout |
directory (the default) for one content directory per language, or filename for language suffixes on filenames. See Translation by filename. |
How sections map across languages#
You define each section once, pointing at the default language’s folder:
sections:
blog:
folder: content/en/blogWhen you switch to German, Emet swaps the content/en prefix for content/de and looks in content/de/blog. The same swap is how it finds an entry’s translations and decides where to put a new one: everything after the language directory stays the same, so content/en/blog/2024/post.md pairs with content/de/blog/2024/post.md and the bundle content/en/blog/post/index.md with content/de/blog/post/index.md. The prefix is matched on whole path segments, so languages whose codes overlap (content/en and content/en-us, say) don’t get confused. For this to work the section’s folder has to start with a language’s contentDir; a folder like content/blog on a site whose languages live in content/en and content/de won’t be found in any language but the default.
Entries whose path doesn’t start with any contentDir are treated as belonging to the default language. Page URLs for a non-default language get a /<code>/ prefix, which matches Hugo’s default of putting only the non-default languages in a subdirectory.
If you’ve set base_path, each contentDir is prefixed with it, like every other path in the config.
Translation by filename#
Hugo’s other layout keeps every language in the same directory and marks the language in the filename, just before the extension: post.md and post.de.md, about/index.md and about/index.de.md, _index.md and _index.de.md. Files without a suffix belong to the default language. To use it, set languages_layout: filename and give every language the same contentDir:
languages_layout: filename
languages:
- code: en
name: English
contentDir: content
default: true
- code: de
name: Deutsch
contentDir: contentWith this layout a section’s folder is the same for every language, so switching the language in the header keeps you in the same directory and only changes which files you see: de shows the *.de.md files, the default language shows the files with no suffix. Translations are paired by directory and filename with the suffix removed, so content/posts/hello.md, content/posts/hello.de.md and content/posts/hello.fr.md are one page, and a new translation is created next to its source with the target language’s suffix (or no suffix, for the default language). New entries created while a non-default language is selected get its suffix too.
The suffix has to be one of the configured codes; anything else is just part of the filename, so release.v2.md is a default-language page called release.v2, and overlapping codes (en and en-us) are told apart because the whole segment has to match. Only the segment right before the extension counts.
The default language never carries a suffix in Emet, which is Hugo’s default too. If your Hugo config sets disableDefaultLanguageInSubdir or similar, that affects URLs, not filenames, and needs no change here; but a site whose default-language files do carry a suffix (post.en.md with en as the default) is not supported.
Linking translations with translationKey#
In both layouts Hugo also links pages whose frontmatter has the same translationKey, however they’re named. Emet does the same: when an entry has a translationKey, its translations are the entries in each other language’s corresponding directory (the same relative path in the directory layout, the same directory in the filename layout) with a matching key, and the usual path-based match is used only when no keyed match is found. Since the key, not the filename, does the linking, renaming a keyed entry doesn’t detach it, and the frontmatter panel doesn’t warn that it will.
Creating a translation of a keyed entry copies the key along with the rest of the frontmatter, and the copy still goes to the path-based location (content/de/posts/hello.md or posts/hello.de.md); you can rename it afterwards and the badge will keep following the key. Working this out means reading the frontmatter of the files in those directories once, so it’s done only for entries that actually have a key, and the result is cached alongside the directory listing.
What the setup wizard generates#
The setup wizard and the local dev server write this block for you when they detect a multilingual site, so the translation features appear without hand-editing. Your Hugo config is the source of truth:
-
If
hugo.toml(orconfig/_default/languages.toml) declares two or morelanguages, each one becomes an entry.namecomes fromlanguageName,contentDirfrom the language’scontentDir(defaulting tocontent/<code>when that folder exists), and the one matchingdefaultContentLanguageis markeddefault. Languages withdisabled = trueare left out; the rest are ordered byweight, default first. -
Without a
languagesmap, acontent/directory made up entirely of language-code folders (content/en,content/de, …) is treated as one folder per language. Names come from a built-in table of common ISO 639-1 codes (de→ German), falling back to the code. The default isdefaultContentLanguageif set, otherwiseenif present, otherwise the first alphabetically. -
A Hugo config that declares two or more
languageswithout per-language content directories, on acontent/tree that contains files with a language suffix (post.de.md) for at least one of those codes, is translation by filename. Every language getscontentDir: contentandlanguages_layout: filenameis written. A site that declares languages but has neither per-language directories nor suffixed files yet is treated as single-language; add the block and the switch by hand once you start adding translations.
Section folders are generated against the default language’s contentDir, so the swap described above works out of the box. A site that resolves to a single language, including one with only content/en, gets no languages block at all.
The wizard shows the detected languages on its final step before committing anything, and the block is plain YAML you can edit afterwards. See Internationalisation for the translation workflow once it’s configured.