Posts

Showing posts with the label content-architecture

Multilingual Docs with Jekyll Collections and Smart Navigation

Why Use Jekyll Collections for Documentation

Jekyll collections allow you to group content beyond standard posts and pages. They’re perfect for structured documentation because you can control order, metadata, and navigation more easily. Combining this with multilingual support creates an elegant and scalable system.

Core Benefits

  • Organized content structure (e.g. tutorials, changelogs, API docs)
  • Supports language segmentation
  • Enables dynamic TOC, related items, and search

Step 1: Define the Collection

Edit your _config.yml to define your multilingual documentation collections:

collections:
  docs:
    output: true
    permalink: /:collection/:path/
defaults:
  - scope:
      path: ""
      type: docs
    values:
      layout: doc
      lang: en

Now create folders for each language under _docs:

_docs/
├── en/
│   ├── getting-started.md
│   └── advanced-features.md
├── id/
│   ├── mulai.md
│   └── fitur-lanjutan.md

Each file includes front matter like:

---
title: "Getting Started"
lang: en
tags: [intro, basics]
order: 1
---

Step 2: Create Language-Aware Navigation

To render dynamic navigation, filter collections by language:

{% assign current_lang = page.lang %}
{% assign localized_docs = site.docs | where: "lang", current_lang | sort: "order" %}

<nav class="toc">
  <ul>
    {% for doc in localized_docs %}
    <li><a href="{{ doc.url }}">{{ doc.title }}</a></li>
    {% endfor %}
  </ul>
</nav>

Step 3: Enable Multilingual Related Content

Just like in previous articles, filter by language and shared tags:

{% assign related_docs = site.docs | where: "lang", page.lang %}
{% assign shared_tags = page.tags %}
{% assign suggestions = related_docs | where_exp: "item", "item.url != page.url and item.tags | intersection: shared_tags | size > 0" %}

Limit to 3–5 suggestions:

{% for suggestion in suggestions limit:5 %}
  <li><a href="{{ suggestion.url }}">{{ suggestion.title }}</a></li>
{% endfor %}

Step 4: Integrate Search for Documentation

As before, create a multilingual index for your docs collection:

---
layout: null
permalink: /search-docs-{{ page.lang }}.json
---
[
  {% assign docs = site.docs | where: "lang", page.lang %}
  {% for doc in docs %}
    {
      "title": "{{ doc.title | escape }}",
      "url": "{{ doc.url }}",
      "excerpt": "{{ doc.content | strip_html | truncate: 160 | escape }}",
      "tags": "{{ doc.tags | join: ', ' }}"
    } {% unless forloop.last %},{% endunless %}
  {% endfor %}
]

Load this into Lunr/ElasticLunr just like previous examples, but point to /search-docs-id.json or /search-docs-en.json.

Step 5: Add Section Anchors and Dynamic TOC

You can use JavaScript to generate a table of contents from headings within each doc:

<div id="auto-toc"></div>

<script>
const headings = document.querySelectorAll('h2, h3')
const toc = document.getElementById('auto-toc')

headings.forEach(h => {
  const id = h.id || h.textContent.trim().toLowerCase().replace(/\s+/g, '-')
  h.id = id
  const link = document.createElement('a')
  link.href = '#' + id
  link.textContent = h.textContent
  toc.appendChild(link)
})
</script>

Optional: Language Switcher

If you name your files similarly (e.g. getting-started.md and mulai.md), you can map languages manually using a YAML data file like _data/doc_links.yml:

getting-started:
  en: /docs/getting-started/
  id: /docs/mulai/

Then in layout:

{% assign doc_key = page.url | split: '/' | last %}
{% assign map = site.data.doc_links[doc_key] %}
{% if map %}
  <a href="{{ map.id }}">Bahasa Indonesia</a>
{% endif %}

Real Use Case: Building a Knowledge Base

Let’s say you run a multilingual SaaS product with English and Indonesian docs. This setup allows you to:

  • Link contextually between related topics in the same language
  • Offer dynamic search per language
  • Maintain a clean, SEO-optimized structure
  • Deploy easily on GitHub Pages with zero backend

Next Step: Interactive Elements and Feedback Loops

Next, we’ll explore how to capture user behavior using JavaScript — like tracking clicks on search results and related posts — to improve relevance over time and better understand what users engage with.

Siap lanjut ke bagian pelacakan interaksi pengguna?

Adding Multilingual Client-Side Search with Related Context

Now that your related post system supports multiple languages, let’s boost the user experience even further by allowing multilingual search that only shows results in the same language. This approach ensures both search and related posts remain contextual.

Why Use Client-Side Search in Jekyll

GitHub Pages is a static hosting platform. It doesn’t support server-side scripts like PHP or databases. So if you want search functionality, you need a static search engine — and Lunr.js or ElasticLunr are perfect for this.

Step 1: Add Lunr or ElasticLunr to Your Project

First, include the JavaScript library in your layout, usually in _layouts/default.html:

<script src="https://cdnjs.cloudflare.com/ajax/libs/lunr.js/2.3.9/lunr.min.js"></script>

If you need multilingual support (e.g., Indonesian), ElasticLunr or Lunr with language plugins may be better.

<script src="https://unpkg.com/[email protected]/elasticlunr.min.js"></script>

Step 2: Build a Language-Specific Search Index

Instead of one giant search index for all content, we generate separate JSON index files by language using a Jekyll collection.

Create a new template file in your repo like search-index.json:

---
layout: null
permalink: /search-index-{{ page.lang }}.json
---
[
  {% assign posts = site.posts | where: "lang", page.lang %}
  {% for post in posts %}
    {
      "title": "{{ post.title | escape }}",
      "url": "{{ post.url }}",
      "excerpt": "{{ post.excerpt | strip_html | strip_newlines | escape }}",
      "tags": "{{ post.tags | join: ', ' }}",
      "lang": "{{ post.lang }}"
    } {% unless forloop.last %},{% endunless %}
  {% endfor %}
]

Then create separate search pages for each language like /en/search.html and /id/search.html that load the correct JSON file.

Example: Loading Index for Bahasa Indonesia

<script>
fetch('/search-index-id.json')
  .then(response => response.json())
  .then(function(docs) {
    const idx = elasticlunr(function () {
      this.addField('title')
      this.addField('excerpt')
      this.setRef('url')

      docs.forEach(doc => {
        this.addDoc(doc)
      })
    })

    document.querySelector('#search-input').addEventListener('input', function() {
      const results = idx.search(this.value, { expand: true })
      const container = document.querySelector('#search-results')
      container.innerHTML = ''

      results.forEach(result => {
        const post = docs.find(p => p.url === result.ref)
        const item = document.createElement('div')
        item.innerHTML = '<a href="' + post.url + '">' + post.title + '</a><p>' + post.excerpt + '</p>'
        container.appendChild(item)
      })
    })
  })
</script>

Step 3: Connect Search Result with Related Posts

Once users land on a post from search, the related posts will already be filtered by language (thanks to our previous setup). However, we can also enhance the search page itself by offering “related tags” filters to narrow down results.

Creating Tag Filters on the Search Page

Extract all tags from your index and create filter buttons:

<script>
let currentTag = ''
function renderTags(docs) {
  const tagSet = new Set()
  docs.forEach(doc => {
    doc.tags.split(',').forEach(tag => tagSet.add(tag.trim()))
  })

  const tagContainer = document.querySelector('#tag-filters')
  tagSet.forEach(tag => {
    const button = document.createElement('button')
    button.textContent = tag
    button.onclick = () => {
      currentTag = tag
      document.querySelector('#search-input').dispatchEvent(new Event('input'))
    }
    tagContainer.appendChild(button)
  })
}
</script>

Update Your Search Function with Tag Filter

const results = idx.search(query, { expand: true })
  .map(r => docs.find(p => p.url === r.ref))
  .filter(post => !currentTag || post.tags.includes(currentTag))

Step 4: Display Language-Aware Suggestions

In addition to normal search, you can offer a “You might also like” section below search results, showing random posts from the same language with similar tags.

const suggestions = docs.filter(p => p.tags.includes(currentTag) && p.url !== currentUrl)
const sample = suggestions.slice(0, 4) // top 4 suggestions

Benefits of Language-Specific Search and Related Posts

  • Improves user engagement and reduces bounce rate
  • Respects user language preference
  • Makes your content structure more SEO-friendly
  • Works fully offline (all client-side)

What's Next

In the next part, we’ll explore how to use Jekyll data files and collections to build interactive documentation systems — like FAQs or tutorials — where search and related articles work in tandem with multilingual support.

Would you like to proceed with that?

Creating Language-Aware Related Posts

For multilingual Jekyll sites, showing related posts in the same language improves user experience significantly. Without it, readers might see content in a language they don’t understand — hurting engagement and bounce rate.

Step 1: Use Language Codes in Front Matter

Make sure each post includes a language key in its front matter, typically based on the folder structure or filename prefix:

---
title: "Membangun Halaman FAQ Interaktif"
tags: [faq, liquid]
categories: [panduan]
lang: id
---

This lang key will be used to filter posts that match the current page’s language.

Step 2: Build a Language-Aware Related Post Block

We combine the logic from previous examples (tag/category filtering) and add a filter for lang.

{% assign current_tags = page.tags %}
{% assign current_lang = page.lang %}
{% assign related = site.posts | where_exp: "p", "p.url != page.url and p.lang == current_lang" %}

{% assign filtered = "" | split: "" %}
{% for post in related %}
  {% assign intersect = post.tags | where_exp: "tag", "current_tags contains tag" %}
  {% if intersect != empty %}
    {% assign filtered = filtered | push: post %}
  {% endif %}
{% endfor %}

{% if filtered.size == 0 %}
  {% assign fallback = site.posts | where_exp: "p", "p.url != page.url and p.lang == current_lang and p.categories contains page.categories[0]" %}
  {% assign filtered = fallback %}
{% endif %}

{% if filtered.size > 0 %}
  <div class="related-posts multilingual">
    <h3>Postingan Terkait</h3>
    <ul>
    {% for post in filtered limit:4 %}
      <li><a href="{{ post.url }}">{{ post.title }}</a></li>
    {% endfor %}
    </ul>
  </div>
{% endif %}

Step 3: Automatically Assign Language Using Folder Structure

If you use a directory structure like:

/_posts/en/
  2025-06-01-my-first-post.md

/_posts/id/
  2025-06-01-post-pertama.md

You can extract the language from the path using front matter defaults in _config.yml:

defaults:
  - scope:
      path: "_posts/en"
    values:
      lang: "en"
  - scope:
      path: "_posts/id"
    values:
      lang: "id"

This avoids setting lang: manually in every post.

Step 4: Translate Section Headings Based on Page Language

To make your related post title (“Related Posts”, “Postingan Terkait”, etc.) dynamic, use a language-based dictionary in a data file like _data/strings.yml:

en:
  related: "Related Posts"
id:
  related: "Postingan Terkait"

Then in your layout:

<h3>{{ site.data.strings[page.lang].related }}</h3>

Optional: Multilingual Tag Mapping (Advanced)

Sometimes you might want the system to consider related posts with “equivalent tags” across languages. For example:

en: seo
id: optimasi-mesin-pencari

You can define such mappings in another data file like _data/tag-aliases.yml and preprocess tags to create a normalized matching array.

Visual Layout for Multilingual Related Posts

The visual grid from the previous article can be reused here. The only change is ensuring that thumbnails and excerpts are available in each language version of the post.

Multilingual Thumbnails and Excerpts

If each language version has its own images, you can use language-specific naming conventions:

thumbnail: "/assets/images/{{ page.lang }}/related-faq.png"

And in Liquid:

<img src="{{ post.thumbnail | default: '/assets/images/default.png' }}" alt="{{ post.title }}">

Conclusion

By combining tag/category filtering with a simple lang check, we’ve created a multilingual-aware related post section. This approach is scalable, doesn't require plugins, and keeps users inside the correct language context — improving their experience and reducing bounce rates.

Selanjutnya, kita akan membahas bagaimana mengintegrasikan related post multilingual ini dengan fitur search client-side, agar pengunjung juga bisa mencari artikel lain dalam bahasa yang sama melalui JavaScript dan data Liquid.

Enhancing Related Posts with Thumbnails and Excerpts

A related post section that’s both informative and visually engaging increases user engagement. Instead of just showing a list of titles, you can include post thumbnails, excerpts, and even categories to provide context.

Step 1: Add Thumbnail and Excerpt to Your Posts

In each post’s front matter, define a thumbnail image and (optionally) a custom excerpt:

---
title: "Creating Interactive FAQ Pages"
tags: [faq, liquid, interactivity]
categories: [jekyll]
thumbnail: "/assets/images/faq-cover.png"
excerpt: "Learn how to build interactive FAQ pages in Jekyll using collapsible sections and Liquid."
---

If excerpt isn’t provided, Jekyll will auto-generate one from the post’s content using the first paragraph.

Step 2: Update the Related Post Block to Display Visual Elements

{% assign current_tags = page.tags %}
{% assign current_categories = page.categories %}
{% assign related_posts = site.posts | where_exp: "post", "post.url != page.url" %}
{% assign filtered = "" | split: "" %}

{% for post in related_posts %}
  {% assign intersect = post.tags | where_exp: "tag", "current_tags contains tag" %}
  {% if intersect != empty %}
    {% assign filtered = filtered | push: post %}
  {% endif %}
{% endfor %}

{% if filtered.size == 0 %}
  {% assign fallback = site.posts | where_exp: "post", "post.url != page.url and post.categories contains current_categories[0]" %}
  {% assign filtered = fallback %}
{% endif %}

{% if filtered.size > 0 %}
  <div class="related-posts visual">
    <h3>Related Posts You Might Like</h3>
    <div class="related-grid">
    {% for post in filtered limit:4 %}
      <a href="{{ post.url }}" class="related-card">
        <img src="{{ post.thumbnail | default: '/assets/images/default-thumb.png' }}" alt="{{ post.title }}">
        <div class="related-content">
          <h4>{{ post.title }}</h4>
          <p>{{ post.excerpt | strip_html | truncate: 100 }}</p>
        </div>
      </a>
    {% endfor %}
    </div>
  </div>
{% endif %}

Step 3: Style the Visual Related Post Section

.related-posts.visual {
  margin-top: 3em;
}
.related-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1.2em;
}
.related-card {
  display: block;
  text-decoration: none;
  border: 1px solid #eee;
  border-radius: 8px;
  overflow: hidden;
  background: #fff;
  transition: box-shadow 0.3s;
}
.related-card:hover {
  box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.related-card img {
  width: 100%;
  height: 120px;
  object-fit: cover;
}
.related-content {
  padding: 0.8em;
}
.related-content h4 {
  font-size: 1em;
  margin-bottom: 0.5em;
  color: #333;
}
.related-content p {
  font-size: 0.9em;
  color: #666;
}

Why This Approach is Ideal for Beginners

  • No external plugins — everything is done with native Jekyll and Liquid
  • Progressive enhancement — fallbacks are provided if thumbnails or excerpts are missing
  • Flexible filtering — starts with tags, then uses categories as backup
  • Responsive design — uses CSS Grid for layout that works on all devices

Going Beyond: Weighted Matching for Better Accuracy

If you want more accurate related posts, consider weighting your filters:

  • +2 points if a tag matches
  • +1 point if in the same category
  • +0.5 point if the titles share words (advanced)

This approach requires more complex Liquid logic or even a pre-processing script in Ruby or Node.js, but the tag/category hybrid system already works great for many blogs.

Conclusion

You’ve now learned how to build a flexible, beginner-friendly related post system in Jekyll that adapts automatically using tags and categories. You’ve also seen how to upgrade it into a rich visual experience with thumbnails and excerpts, without needing plugins or YAML complexity.

In the next article, kita akan membahas pembuatan related post multilingual yang mempertimbangkan bahasa pengguna secara otomatis, cocok untuk proyek Jekyll multilingual dengan data-driven layout dan folder i18n seperti /en/, /id/, atau /de/.

automatic related posts in jekyll without using yaml

Why Skip YAML for Related Posts

Maintaining related posts manually in _data can be tedious, especially for large sites. Many beginners prefer a system that automatically suggests posts based on tags or categories. In this article, we’ll build a YAML-free, auto-related-posts setup using only front matter metadata and Liquid filters.

What You'll Get:

  • Automatic related post suggestions based on tags
  • Dynamic filtering using Liquid
  • Easy fallback logic to avoid empty results
  • No need for maintaining external YAML

Step 1: Tag Your Posts Properly

Ensure your posts have tags in the front matter:

---
title: "Optimizing Liquid Performance"
tags: [performance, liquid, optimization]
---

Tags are the most reliable and flexible metadata for content grouping in Jekyll. You can also add categories as an additional filter layer.

Step 2: Add Related Posts Block to Layout

Insert the following code inside your post layout (e.g., _layouts/post.html) or in an include:

{% assign current_tags = page.tags %}
{% assign related_posts = site.posts | where_exp: "post", "post.url != page.url" %}
{% assign filtered = "" | split: "" %}

{% for post in related_posts %}
  {% assign intersect = post.tags | where_exp: "tag", "current_tags contains tag" %}
  {% if intersect != empty %}
    {% assign filtered = filtered | push: post %}
  {% endif %}
{% endfor %}

{% if filtered.size > 0 %}
  <div class="related-posts">
    <h3>Related Posts</h3>
    <ul>
    {% for post in filtered limit:5 %}
      <li><a href="{{ post.url }}">{{ post.title }}</a></li>
    {% endfor %}
    </ul>
  </div>
{% endif %}

Explanation

  • where_exp filters out the current post
  • Loop compares each post's tags with the current post
  • If overlap exists, that post is added to filtered

Optional: Add Fallback Based on Category

If no related posts were found based on tags, you can fall back to category-based matching:

{% if filtered.size == 0 %}
  {% assign fallback = site.posts | where_exp: "post", "post.url != page.url and post.categories contains page.categories[0]" %}
  <div class="related-posts fallback">
    <h3>Other Posts You May Like</h3>
    <ul>
    {% for post in fallback limit:5 %}
      <li><a href="{{ post.url }}">{{ post.title }}</a></li>
    {% endfor %}
    </ul>
  </div>
{% endif %}

This logic uses the first category of the current post to find similar ones. You can expand it with more complex logic if needed.

Styling the Related Posts Block

You can style the related posts section using your preferred CSS:

.related-posts {
  border-top: 2px solid #ccc;
  padding-top: 1em;
  margin-top: 2em;
}
.related-posts h3 {
  font-size: 1.2em;
  margin-bottom: 0.5em;
}
.related-posts ul {
  list-style: none;
  padding-left: 0;
}
.related-posts li {
  margin-bottom: 0.4em;
}

Performance Tips

  • Limit the number of related posts shown (e.g., 3–5) to keep rendering fast
  • Ensure all posts are tagged to improve the match rate
  • Use where_exp for more flexible conditions than simple filters

Pros and Cons

Pros Cons
No external YAML to manage Less control than curated related groups
Updates automatically when tags change May include less-relevant posts
Easy for beginners to implement Can’t define custom titles or per-language groups

When to Use This Method

This approach is ideal if:

  • You’re just starting with Jekyll
  • You want an automated system
  • Your content uses consistent tagging

However, if your site requires curated control or multilingual filtering, using YAML remains the better option.

Conclusion

Building related posts without YAML is both beginner-friendly and powerful when you’re dealing with a growing content base. By leveraging Liquid filters and consistent tag usage, Jekyll can dynamically render relevant suggestions for every post, with minimal maintenance effort.

In the next article, we’ll look into **enhancing this system using text similarity detection**, external plugins (if using local builds), and hybrid strategies combining both automated and curated logic for maximum flexibility.

centralized related posts system in jekyll using yaml data

Why Use Centralized YAML for Related Posts?

Previously, we used series or pillar fields in post front matter. While effective, managing related content through front matter can get messy as your site grows. A centralized YAML file provides:

  • Cleaner control over relationships
  • Faster content updates without editing individual posts
  • Support for non-linear or manual groupings

Step 1: Create a Data File for Related Posts

Create a file _data/related.yml with structure like:

template-performance:
  title: "Template Optimization Series"
  posts:
    - /jekyll/template-basics/
    - /jekyll/layout-optimizations/
    - /jekyll/template-performance/
    - /jekyll/minification/

jekyll-site-structure:
  title: "Jekyll Site Structure Guide"
  posts:
    - /jekyll/navigation-overview/
    - /jekyll/building-sidebar/
    - /jekyll/smart-related-posts/
    - /jekyll/yaml-based-relationships/

Each group contains a title and a list of post URLs. This gives you full editorial control.

Step 2: Connect the Current Page to Its Group

In your post front matter, add a key to associate with a group in the YAML file:

---
title: "Jekyll Template Performance Tips"
category: jekyll
related_group: template-performance
---

Step 3: Render Related Posts from Data File

In your layout or include file, add the following logic:

{% assign group_key = page.related_group %}
{% assign group_data = site.data.related[group_key] %}
{% if group_data %}
  <div class="related-posts">
    <h3>More in: {{ group_data.title }}</h3>
    <ul>
    {% for url in group_data.posts %}
      {% assign post = site.posts | where: "url", url | first %}
      {% if post and post.url != page.url %}
        <li><a href="{{ post.url }}">{{ post.title }}</a></li>
      {% endif %}
    {% endfor %}
    </ul>
  </div>
{% endif %}

This system checks the group for the current post, loads its title, and loops through all posts in that group, excluding the current one.

Step 4: Add a Fallback if No Group Found

To avoid empty output, consider adding a fallback using category or tag match:

{% unless group_data %}
  <h3>Related Articles in {{ page.category | capitalize }}</h3>
  <ul>
  {% assign related = site.posts | where: "category", page.category | where_exp: "p", "p.url != page.url" | limit: 3 %}
  {% for post in related %}
    <li><a href="{{ post.url }}">{{ post.title }}</a></li>
  {% endfor %}
  </ul>
{% endunless %}

Advantages of the YAML-Based Approach

  • Manual curation without editing post files
  • Custom titles for related sections
  • Cross-category linking is easy
  • Better visibility for thematic or campaign content

Example Use Case: Campaign-Specific Guides

Imagine you're running a content campaign called “Launch Your Jekyll Site in a Week”. Instead of tagging or creating a category, just add a group like:

jekyll-launch-week:
  title: "Launch Your Jekyll Site in a Week"
  posts:
    - /jekyll/setup-github-pages/
    - /jekyll/choose-a-theme/
    - /jekyll/write-first-post/
    - /jekyll/push-live/

Now every article in that series gets meaningful related content, even if they span different categories or tags.

Tips for Maintaining Your Related YAML File

  • Use descriptive keys (e.g. launch-week, performance-tips)
  • Keep URLs updated — use relative URLs for consistency
  • Use a validator or syntax checker for your YAML if working at scale

Bonus: Display Group Navigation

You can also display the current group’s full list as a navigation menu for multi-part articles:

<nav class="related-nav">
  <h4>{{ group_data.title }}</h4>
  <ol>
  {% for url in group_data.posts %}
    {% assign post = site.posts | where: "url", url | first %}
    <li{% if post.url == page.url %} class="active"{% endif %}>
      <a href="{{ post.url }}">{{ post.title }}</a>
    </li>
  {% endfor %}
  </ol>
</nav>

Conclusion

By storing related post relationships in a YAML file, you unlock a structured, scalable, and highly flexible system. You can control related posts globally without modifying dozens of Markdown files, and the result is a much more coherent reading experience for your users.

In the next article, we’ll explore how to make this system multilingual by combining _data/related.yml with _data/i18n and language-aware layouts in Jekyll.

multilingual related posts in jekyll using data and language detection

Why Multilingual Related Posts Matter

For multilingual Jekyll sites, simply reusing the same related post data across all languages leads to a poor UX. Users might click related links that aren’t in their preferred language. This breaks immersion, especially in knowledge bases or documentation.

✅ Goal:

  • Only show related posts that match the current post's language
  • Support shared related post groups across languages
  • Optional: Fallback if translation is missing

Step 1: Add Language to Post Front Matter

Add lang to each post:

---
title: "Optimisasi Template di Jekyll"
lang: id
related_group: template-performance
---

And its English version:

---
title: "Template Optimization in Jekyll"
lang: en
related_group: template-performance
---

Step 2: Update YAML Related Group File

Reuse same group keys for all languages, keeping just URLs:

template-performance:
  title:
    en: "Template Optimization Series"
    id: "Seri Optimisasi Template"
  posts:
    - /jekyll/template-basics/
    - /jekyll/layout-optimizations/
    - /jekyll/template-performance/
    - /jekyll/minification/

Step 3: Multilingual Logic in Layout or Include

Use the following Liquid logic to render related posts that match page.lang:

{% assign group_key = page.related_group %}
{% assign group_data = site.data.related[group_key] %}
{% if group_data %}
  {% assign lang = page.lang | default: "en" %}
  <div class="related-posts">
    <h3>{{ group_data.title[lang] | default: group_data.title["en"] }}</h3>
    <ul>
    {% for url in group_data.posts %}
      {% assign post = site.posts | where: "url", url | where: "lang", lang | first %}
      {% if post and post.url != page.url %}
        <li><a href="{{ post.url }}">{{ post.title }}</a></li>
      {% endif %}
    {% endfor %}
    </ul>
  </div>
{% endif %}

This code:

  • Loads the group from _data/related.yml
  • Uses the correct group title based on the page language
  • Only displays related posts that match the current language

Optional: Language Fallback for Missing Translations

If a translation is missing, you can show a fallback post (e.g., English):

{% assign fallback_post = site.posts | where: "url", url | where: "lang", "en" | first %}
{% assign post = site.posts | where: "url", url | where: "lang", lang | first | default: fallback_post %}

Step 4: Add Translated Titles to Related YAML

To make it fully multilingual, include title translations in your YAML:

jekyll-basics:
  title:
    en: "Jekyll Basics Series"
    id: "Seri Dasar Jekyll"
  posts:
    - /jekyll/setup/
    - /jekyll/structure/
    - /jekyll/markdown-guide/

Use {{ group_data.title[page.lang] }} to render it dynamically.

Optional: Language-Based Navigation Menus

You can even use this data structure to build multilingual section menus per language:

<nav class="group-nav">
  <h4>{{ group_data.title[lang] }}</h4>
  <ul>
  {% for url in group_data.posts %}
    {% assign post = site.posts | where: "url", url | where: "lang", lang | first %}
    {% if post %}
      <li{% if post.url == page.url %} class="active"{% endif %}>
        <a href="{{ post.url }}">{{ post.title }}</a>
      </li>
    {% endif %}
  {% endfor %}
  </ul>
</nav>

Recap: Why This System Works

  • 📌 Keeps logic clean — data-driven instead of per-post
  • 🌍 Language-aware related post experience
  • 💡 Can be expanded into navigation, sidebars, or custom blocks

What’s Next?

We’ve now built:

  1. Basic related post system using front matter
  2. Advanced system using _data/related.yml
  3. Multilingual support based on page.lang

In the next part, we'll explore **automatically generating related post suggestions** using Liquid filters, word overlaps, or tag intersections—ideal when you don’t want to curate related groups manually.