{
  "version": "https://jsonfeed.org/version/1",
  "title": "Marmite 0.4.0",
  "home_page_url": "https://marmite.blog",
  "feed_url": "https://marmite.blog/tag-i18n.json",
  "description": "Static Site Generator <br> <small>for blogs</small> <br> <small>made with ❤️ and 🦀</small>",
  "items": [
    {
      "id": "https://marmite.blog/language-streams.html",
      "url": "https://marmite.blog/language-streams.html",
      "title": "Language Streams - Multilingual Content",
      "content_html": "<p>Marmite supports multilingual sites through language streams. Each language becomes a stream with its own listing page and RSS feed, while translations are automatically cross-linked with &quot;Also available in&quot; navigation and hreflang SEO tags.</p>\n<h2><a href=\"#how-it-works\" aria-hidden=\"true\" class=\"anchor\" id=\"how-it-works\"></a>How It Works</h2>\n<p>Languages are auto-detected from content. Just set <code>language: xx</code> in your frontmatter or use subfolder naming conventions, and marmite handles the rest - no configuration required.</p>\n<p>The <code>language</code> field in <code>marmite.yaml</code> (defaults to <code>en</code>) determines the site's default language. Content in the default language stays on <code>index.html</code>. Other languages get their own stream pages (<code>pt.html</code>, <code>es.html</code>) and RSS feeds (<code>pt.rss</code>, <code>es.rss</code>).</p>\n<h3><a href=\"#optional-display-names\" aria-hidden=\"true\" class=\"anchor\" id=\"optional-display-names\"></a>Optional: Display Names</h3>\n<p>By default, language streams are labeled with their two-letter code (e.g., &quot;pt&quot;, &quot;es&quot;). To set human-readable names, add an optional <code>languages</code> section to <code>marmite.yaml</code>:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner language-yaml\"><a-pr>language</a-pr><a-p>:</a-p> <a-s>en</a-s>\n<a-pr>languages</a-pr><a-p>:</a-p>\n  <a-pr>pt</a-pr><a-p>:</a-p>\n    <a-pr>display_name</a-pr><a-p>:</a-p> <a-s>&quot;Portugues&quot;</a-s>\n  <a-pr>es</a-pr><a-p>:</a-p>\n    <a-pr>display_name</a-pr><a-p>:</a-p> <a-s>&quot;Espanol&quot;</a-s></code></pre>\n<p>This follows the same pattern as <code>streams:</code> and <code>series:</code> - the config is purely cosmetic. Sites without any language content are completely unaffected.</p>\n<h2><a href=\"#content-organization\" aria-hidden=\"true\" class=\"anchor\" id=\"content-organization\"></a>Content Organization</h2>\n<p>There are four ways to organize multilingual content. All produce flat HTML output.</p>\n<h3><a href=\"#option-1-subfolder-grouping-auto-discovery\" aria-hidden=\"true\" class=\"anchor\" id=\"option-1-subfolder-grouping-auto-discovery\"></a>Option 1: Subfolder Grouping (Auto-Discovery)</h3>\n<blockquote>\n<p><strong>RECOMMENDED</strong></p>\n</blockquote>\n<p>Group translations in a subfolder named after the base content's slug. Files prefixed with an ISO 639-1 language code are automatically detected and cross-linked:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner\">content/hello/\n  hello.md              # Default language (en)\n  pt-ola-mundo.md       # Portuguese translation\n  es-hola-mundo.md      # Spanish translation\n</code></pre>\n<p>This generates:</p>\n<ul>\n<li><code>hello.html</code> - English post, listed on <code>index.html</code></li>\n<li><code>pt-ola-mundo.html</code> - Portuguese post, listed on <code>pt.html</code></li>\n<li><code>es-hola-mundo.html</code> - Spanish post, listed on <code>es.html</code></li>\n</ul>\n<p>All three pages automatically show &quot;Also available in&quot; links to each other.</p>\n<div class=\"markdown-alert markdown-alert-tip\">\n<p class=\"markdown-alert-title\">Tip</p>\n<p>The subfolder can also have the date in it, e.g. <code>content/2026-07-02-hello/</code> this way you don't have to specify date on each translation frontmatter.</p>\n</div>\n<div class=\"markdown-alert markdown-alert-important\">\n<p class=\"markdown-alert-title\">Important</p>\n<p>A subfolder is only treated as a translation group when it contains exactly one non-prefixed file (the original) and one or more language-prefixed files. If the subfolder has multiple non-prefixed files, marmite cannot determine which one is the original, so all files are treated as independent content and no translation linking occurs. Use <code>translates:</code> frontmatter for manual linking in that case.</p>\n</div>\n<h3><a href=\"#option-2-mixed-flat--subfolder\" aria-hidden=\"true\" class=\"anchor\" id=\"option-2-mixed-flat--subfolder\"></a>Option 2: Mixed Flat + Subfolder</h3>\n<blockquote>\n<p><strong>RECOMMENDED</strong></p>\n</blockquote>\n<p>If you have an existing flat site and want to add translations without moving original files, create a subfolder matching the existing content's slug:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner\">content/\n  hello.md              # Existing flat file, slug: hello\n  hello/\n    pt-ola.md           # Portuguese translation, auto-linked\n</code></pre>\n<p>Marmite detects that the subfolder name <code>hello</code> matches the flat file's slug and links them as translations.</p>\n<div class=\"markdown-alert markdown-alert-important\">\n<p class=\"markdown-alert-title\">Important</p>\n<p>Subfolder names must match the original post's slug (not the filename, but the resolved slug, sometimes taken from the title) to be automatically linked as translations.</p>\n</div>\n<h3><a href=\"#option-3-translates-pointer\" aria-hidden=\"true\" class=\"anchor\" id=\"option-3-translates-pointer\"></a>Option 3: Translates Pointer</h3>\n<p>Each translation points to the original content's slug using the <code>translates</code> field. Marmite builds the full bidirectional link network automatically:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner language-yaml\"><a-p>---</a-p>\n<a-pr>title</a-pr><a-p>:</a-p> <a-s>Ola Mundo</a-s>\n<a-pr>date</a-pr><a-p>:</a-p> <a-s>2024-01-01</a-s>\n<a-pr>language</a-pr><a-p>:</a-p> <a-s>pt</a-s>\n<a-pr>translates</a-pr><a-p>:</a-p> <a-s>hello</a-s>\n<a-p>---</a-p></code></pre>\n<p>This is simpler than maintaining a <code>translations</code> list on every file. Just set <code>language</code> and <code>translates</code> on each translation and marmite connects everything. Setting <code>language</code> to a value different from the site default automatically places the content on the corresponding language stream.</p>\n<h3><a href=\"#option-4-frontmatter-translation-link\" aria-hidden=\"true\" class=\"anchor\" id=\"option-4-frontmatter-translation-link\"></a>Option 4: Frontmatter Translation Link</h3>\n<p>Set the language and translations explicitly in frontmatter:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner language-yaml\"><a-p>---</a-p>\n<a-pr>title</a-pr><a-p>:</a-p> <a-s>Hello World</a-s>\n<a-pr>date</a-pr><a-p>:</a-p> <a-s>2024-01-01</a-s>\n<a-pr>language</a-pr><a-p>:</a-p> <a-s>en</a-s> <a-c># can omit because it&#39;s the default language</a-c>\n<a-pr>translations</a-pr><a-p>:</a-p>\n  <a-p>-</a-p> <a-s>pt-ola</a-s>  <a-c># then you write a post with slug `ola` and language: set to `pt`</a-c>\n  <a-p>-</a-p> <a-s>es-hola</a-s>\n<a-p>---</a-p></code></pre>\n<p>The <code>translations</code> field accepts a list of slugs. Marmite resolves each slug to the actual content, fills in the language code and display name, and creates bidirectional links. If post A lists post B as a translation, post B automatically gets a link back to post A.</p>\n<div class=\"markdown-alert markdown-alert-important\">\n<p class=\"markdown-alert-title\">Important</p>\n<p>Prefer options 1 and 2 for auto discovery. Options 3 and 4 require explicit frontmatter but give you more control over the linking.</p>\n</div>\n<h2><a href=\"#frontmatter-fields\" aria-hidden=\"true\" class=\"anchor\" id=\"frontmatter-fields\"></a>Frontmatter Fields</h2>\n<h3><a href=\"#language\" aria-hidden=\"true\" class=\"anchor\" id=\"language\"></a><code>language</code></h3>\n<p>Explicitly set the content's language code:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner language-yaml\"><a-pr>language</a-pr><a-p>:</a-p> <a-s>pt</a-s></code></pre>\n<p>Usually not needed when using subfolder detection (Options 1 and 2). Use this when you need to set the language explicitly (Options 3 and 4).</p>\n<p>When <code>language</code> is set to a value different from the site default and no explicit <code>stream</code> is set, marmite automatically uses the language as the stream. A post with <code>language: pt</code> will be published to <code>pt.html</code>.</p>\n<h3><a href=\"#translates\" aria-hidden=\"true\" class=\"anchor\" id=\"translates\"></a><code>translates</code></h3>\n<p>Point a translation to the original content's slug:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner language-yaml\"><a-pr>translates</a-pr><a-p>:</a-p> <a-s>hello</a-s></code></pre>\n<p>Marmite creates bidirectional links between the source and all its translations. Not needed with subfolder auto-discovery. See Option 3 above for details.</p>\n<h3><a href=\"#translations\" aria-hidden=\"true\" class=\"anchor\" id=\"translations\"></a><code>translations</code></h3>\n<p>Manually link to translations by slug:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner language-yaml\"><a-pr>translations</a-pr><a-p>:</a-p>\n  <a-p>-</a-p> <a-s>en-hello-world</a-s>\n  <a-p>-</a-p> <a-s>es-hola-mundo</a-s></code></pre>\n<p>Not needed when using subfolder auto-discovery (Options 1 and 2) or <code>translates:</code> (Option 3), since translations are linked automatically in those modes.</p>\n<h2><a href=\"#template-output\" aria-hidden=\"true\" class=\"anchor\" id=\"template-output\"></a>Template Output</h2>\n<h3><a href=\"#translation-links\" aria-hidden=\"true\" class=\"anchor\" id=\"translation-links\"></a>Translation Links</h3>\n<p>Content pages with translations show an &quot;Also available in&quot; section with links to each translation:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner\">Also available in: English, Espanol\n</code></pre>\n<h3><a href=\"#seo-tags\" aria-hidden=\"true\" class=\"anchor\" id=\"seo-tags\"></a>SEO Tags</h3>\n<p>The <code>&lt;head&gt;</code> section of translated content pages includes hreflang alternate link tags:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner language-html\"><a-p>&lt;</a-p><a-tg>link</a-tg> <a-at>rel</a-at>=&quot;<a-s>alternate</a-s>&quot; <a-at>hreflang</a-at>=&quot;<a-s>en</a-s>&quot; <a-at>href</a-at>=&quot;<a-s>https://example.com/en-hello-world.html</a-s>&quot;<a-p>&gt;</a-p>\n<a-p>&lt;</a-p><a-tg>link</a-tg> <a-at>rel</a-at>=&quot;<a-s>alternate</a-s>&quot; <a-at>hreflang</a-at>=&quot;<a-s>es</a-s>&quot; <a-at>href</a-at>=&quot;<a-s>https://example.com/es-hola-mundo.html</a-s>&quot;<a-p>&gt;</a-p>\n<a-p>&lt;</a-p><a-tg>link</a-tg> <a-at>rel</a-at>=&quot;<a-s>alternate</a-s>&quot; <a-at>hreflang</a-at>=&quot;<a-s>pt</a-s>&quot; <a-at>href</a-at>=&quot;<a-s>https://example.com/hello.html</a-s>&quot;<a-p>&gt;</a-p></code></pre>\n<p>The <code>&lt;html lang=&quot;...&quot;&gt;</code> attribute is also set correctly per page.</p>\n<h3><a href=\"#stream-display-names\" aria-hidden=\"true\" class=\"anchor\" id=\"stream-display-names\"></a>Stream Display Names</h3>\n<p>Language stream pages use the configured language name as the display name. A stream named <code>pt</code> automatically displays as &quot;Portugues&quot; without needing a separate <code>streams.pt.display_name</code> entry. You can override this by defining both <code>languages</code> and <code>streams</code> entries for the same code.</p>\n<h2><a href=\"#languages-group-page\" aria-hidden=\"true\" class=\"anchor\" id=\"languages-group-page\"></a>Languages Group Page</h2>\n<p>Marmite generates a <code>languages.html</code> page that lists all content organized by language. This page is always generated, even on monolingual sites (showing just the default language).</p>\n<p>The page follows the same group template pattern as tags, authors, streams, series, and archives. Each language shows a collapsible list of its content with a link to the full language stream page.</p>\n<p>Languages are sorted alphabetically, with the site's default language appearing last in the list.</p>\n<h3><a href=\"#customization\" aria-hidden=\"true\" class=\"anchor\" id=\"customization\"></a>Customization</h3>\n<p>The page heading can be changed in <code>marmite.yaml</code>:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner language-yaml\"><a-pr>languages_title</a-pr><a-p>:</a-p> <a-s>&quot;Idiomas&quot;</a-s></code></pre>\n<p>In custom templates, the <code>language_display_name</code> Tera function returns the configured display name for a language code:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner language-html\">{{ language_display_name(language=&quot;pt&quot;) }}</code></pre>\n<p>The <code>group(kind=&quot;language&quot;)</code> Tera function returns all content grouped by language:</p>\n<pre class=\"marmite-code\"><code class=\"marmite-code-inner language-html\">{% for lang, posts in group(kind=&quot;language&quot;) %}\n  <a-p>&lt;</a-p><a-tg>h2</a-tg><a-p>&gt;</a-p>{{ language_display_name(language=lang) }}<a-p>&lt;/</a-p><a-tg>h2</a-tg><a-p>&gt;</a-p>\n  <a-p>&lt;</a-p><a-tg>p</a-tg><a-p>&gt;</a-p>{{ posts | length }} posts<a-p>&lt;/</a-p><a-tg>p</a-tg><a-p>&gt;</a-p>\n{% endfor %}</code></pre>\n<h2><a href=\"#how-it-works-internally\" aria-hidden=\"true\" class=\"anchor\" id=\"how-it-works-internally\"></a>How It Works Internally</h2>\n<ol>\n<li>During content collection, files in subfolders with an ISO 639-1 language code prefix (e.g., <code>en-</code>) are detected and assigned to that language stream</li>\n<li>Languages are auto-populated from all observed content - no config needed</li>\n<li>After all content is collected, a translation discovery phase groups content by subfolder, processes <code>translates:</code> pointers, and resolves frontmatter references</li>\n<li>All members of a translation group get cross-linked with <code>TranslationRef</code> entries containing the language code, display name, slug, and title</li>\n<li>Templates render translation links and hreflang tags from these references</li>\n</ol>\n<h2><a href=\"#compatibility-notes\" aria-hidden=\"true\" class=\"anchor\" id=\"compatibility-notes\"></a>Compatibility Notes</h2>\n<ul>\n<li>The default language's content uses stream <code>index</code> and appears on the main <code>index.html</code> page</li>\n<li>Language detection from filename prefixes only triggers inside subfolders, never for flat files at the content root (preventing false positives like <code>essential-guide.md</code> being detected as language <code>es</code>)</li>\n<li>A post can have both a <code>series</code> and a language stream - they work independently</li>\n<li>Slug collisions between languages are prevented by the stream prefix in slugs (<code>en-hello</code> vs <code>es-hola</code>)</li>\n<li>Pages (content without dates) can have <code>language</code> and <code>translations</code> for template display but do not appear on stream listing pages</li>\n</ul>\n<blockquote>\n<p>Please consider giving a ☆ on Marmite <a href=\"https://github.com/rochacbruno/marmite\">Github</a> repository, that helps a lot!</p>\n</blockquote>\n<!-- Contents from _markdown_footer.md are appended to every content -->\n<!-- _references.md is a file to gather all references together \nthis file is appended to every raw markdown.\n-->\n",
      "summary": "\"Write content in multiple languages with automatic translation linking, language stream pages, and hreflang SEO tags using marmite's language streams feature.\"",
      "date_published": "2026-07-02T00:00:00-00:00",
      "image": "media/language-streams/banner.jpg",
      "authors": [
        {
          "name": "Bruno Rocha",
          "url": "https://go.rocha.social/@bruno",
          "avatar": "https://github.com/rochacbruno.png"
        }
      ],
      "tags": [
        "docs",
        "features",
        "i18n",
        "multilingual"
      ],
      "language": "en"
    }
  ]
}