Posts

Showing posts with the label collections

creating localized post collections in jekyll

Understanding Jekyll Collections for Multilingual Blogs

Jekyll collections offer a flexible structure for organizing content beyond standard posts and pages. When building a multilingual blog, collections make it easier to separate content by language, maintain consistent structures, and apply language-specific templates.

Why Use Collections Instead of Tags or Folders

While you could use tags or language-specific folders to organize multilingual posts, collections provide:

  • Custom permalinks per language
  • Dedicated layouts for each language
  • Explicit configuration for sorting and output

Planning the Folder Structure

Start by creating language-specific collections under a clear and predictable structure:

_posts/
_data/
_en/
_fr/
_es/

Each of these collections (_en, _fr, etc.) will hold blog posts written in the respective language.

Configuring Collections in _config.yml

Next, define these collections in your configuration file:

collections:
  en:
    output: true
    permalink: /en/:title/
  fr:
    output: true
    permalink: /fr/:title/
  es:
    output: true
    permalink: /es/:title/

This allows Jekyll to treat these folders as collections and output them as standalone pages.

Creating Posts Inside Language Collections

Each collection behaves like posts. For example:

_en/
  2025-01-01-my-first-english-post.md

_fr/
  2025-01-01-mon-premier-article.md

Each file can include language-specific front matter:

---
layout: post
title: "My First English Post"
lang: en
category: tutorials
---

Creating Language-Specific Index Pages

To list all posts for a particular language, create index pages like:

en/index.md
fr/index.md

Inside en/index.md:

---
layout: default
title: "Blog in English"
---

{% raw %}

{% endraw %}

Using Shared Layouts with Conditional Rendering

To avoid code duplication, use a shared layout with conditional logic:

{% raw %}
{% if page.lang == "en" %}
  <h2>Recent Posts in English</h2>
{% elsif page.lang == "fr" %}
  <h2>Articles récents en français</h2>
{% endif %}
{% endraw %}

Case Study: A Language-Learning Blog

A language learning blog needed three separate post collections: English tips, French grammar, and Spanish vocabulary. By using collections instead of folders or tags:

  • Authors could focus on one language without interfering with others
  • Readers accessed a clean URL path for each language
  • SEO was improved through structured permalinks

Setting Up Navigation Between Languages

Use the filename or front matter to create alternate links:

{% raw %}
{% if page.lang == "en" %}
  <a href="/fr/{{ page.slug }}">Lire en français</a>
{% endif %}
{% endraw %}

Or define translation mappings in a data file:

# _data/translations.yml
my-first-post:
  en: /en/my-first-english-post/
  fr: /fr/mon-premier-article/

Optimizing URLs and SEO

Collections give you full control over permalinks, allowing cleaner multilingual slugs. Use slug in front matter and access it for localized URLs.

---
title: "Mon premier article"
slug: mon-premier-article
lang: fr
---

Adding Localized RSS Feeds

Create custom feeds per language with plugins or manual templates:

feed.xml:
{% raw %}
{% for post in site.en %}
  <item>
    <title>{{ post.title }}</title>
    <link>{{ site.url }}{{ post.url }}</link>
  </item>
{% endfor %}
{% endraw %}

Common Pitfalls and Solutions

  • Mixed content types: Avoid mixing collections and standard posts. Keep them separate for clarity.
  • Missing output config: Collections won’t render unless output: true is specified.
  • Duplicate slugs: Always ensure unique slug per post per language to prevent overwrite conflicts.

Advanced Filtering and Sorting

Sort and filter posts within a collection using Liquid:

{% raw %}
{% assign sorted_posts = site.fr | sort: "date" | reverse %}
{% for post in sorted_posts %}
  <a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}
{% endraw %}

Pagination for Each Language

Jekyll-paginate doesn’t support multiple collections out of the box. You can build a workaround using includes and custom logic or use jekyll-paginate-v2 which supports multiple collections and languages.

Conclusion

Using language-specific post collections in Jekyll gives you structured control, scalable organization, and a better editorial experience. It also enhances your site’s SEO and usability. In the next article, we’ll look at building a language switcher component that works seamlessly with these collections and data-driven templates.

centralizing translations with jekyll data files

Why Centralize Translations in Jekyll

Managing multilingual content in static sites can quickly become unmanageable if every translation is hard-coded. Jekyll offers an elegant solution through _data files, allowing translations to be stored centrally in YAML files. This approach improves consistency, reduces duplication, and simplifies updates.

How Translation Data Files Work

Inside your Jekyll project, the _data directory lets you store reusable data in YAML, JSON, or CSV formats. For translations, YAML is the most readable and maintainable format. You can organize your files like this:

_data/
  i18n/
    en.yml
    fr.yml
    es.yml

Each file should contain a tree of keys and translated values:

# en.yml
greeting:
  welcome: "Welcome to our site"
  intro: "Explore our services"
# fr.yml
greeting:
  welcome: "Bienvenue sur notre site"
  intro: "Découvrez nos services"

Loading Translations in Templates

Use Liquid’s dot notation and site.data to fetch translations dynamically. You can create a helper variable in your layout or template:

{% raw %}{% assign t = site.data.i18n[page.lang] %}

<h2>{{ t.greeting.welcome }}</h2>
<p>{{ t.greeting.intro }}</p>{% endraw %}

This makes your content files much cleaner and easier to manage, especially when content is repeated across many pages in different languages.

Designing Scalable Translation Trees

As your site grows, your translation files might become complex. Use clear and nested structures to make them scalable:

services:
  web:
    title: "Web Development"
    desc: "Custom websites built with care"
  seo:
    title: "SEO Optimization"
    desc: "Increase your visibility organically"

Accessing nested keys is just as easy:

{% raw %}{{ t.services.web.title }}{% endraw %}

Case Study: Multilingual NGO Website

An international NGO needed to present their content in 4 languages across 100+ pages. Initially, all translations were inline in the templates. This caused errors and inconsistencies. After migrating to centralized data files:

  • Translation updates became 3× faster
  • New language additions only required a single YAML file
  • Volunteers could help translate without touching HTML

Integrating Translations in Layouts and Includes

Use includes to wrap common blocks like banners or CTA buttons:

# _includes/cta.html
<div class="cta">
  <h3>{{ t.call_to_action.title }}</h3>
  <p>{{ t.call_to_action.text }}</p>
</div>

Then include it in your layout or page:

{% raw %}{% include cta.html %}{% endraw %}

This promotes DRY (Don't Repeat Yourself) principles in multilingual environments.

Handling Fallbacks and Missing Keys

Sometimes translations might be missing. Liquid doesn’t support try-catch, but you can work around it by adding fallback checks:

{% raw %}{% if t.greeting.welcome %}
  {{ t.greeting.welcome }}
{% else %}
  Welcome!
{% endif %}{% endraw %}

For large sites, consider writing a script to check for missing keys across language files before deployment.

Localizing Navigation Menus

Navigation is a great example of repeated translated elements. You can store them in _data/navigation.yml:

en:
  - title: "Home"
    url: "/en/"
  - title: "About"
    url: "/en/about/"
fr:
  - title: "Accueil"
    url: "/fr/"
  - title: "À propos"
    url: "/fr/a-propos/"

Then load them dynamically based on page.lang:

{% raw %}{% for item in site.data.navigation[page.lang] %}
  <a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}{% endraw %}

Best Practices for Managing Translation Files

  • Keep keys consistent across all languages
  • Use a flat structure for short projects, nested for complex sites
  • Document your key structure in a README for contributors
  • Use meaningful key names (avoid numbered keys)

Automating Translations with Third-Party Tools

While Jekyll doesn't support automatic translation syncing natively, you can use tools like:

  • Poedit or Lokalise for collaborative translation management
  • GitHub Actions to validate keys or YAML syntax
  • Custom Ruby or Python scripts to diff and sync keys

Comparing Approach: Inline vs Data-Based Translation

Approach Pros Cons
Inline Simple to start, no external files Hard to maintain, code duplication
Data files Centralized, clean code, scalable Initial setup takes more planning

Conclusion

Centralizing translations in Jekyll using YAML data files is a scalable and maintainable strategy for any multilingual site. It keeps your content clean, reusable, and easy to manage for both developers and non-technical contributors. In the next article, we’ll explore how to extend this approach by creating localized blog post collections with Jekyll.

seo best practices for multilingual jekyll sites

Why Use Collections for Multilingual Content?

Jekyll’s collections feature allows you to group content together in a flexible, scalable way. When working with multilingual websites, collections can help organize and manage different language versions of your content, such as blog posts, tutorials, and even static pages. Collections provide a clean separation between different types of content, allowing for easier management and navigation.

Creating a Multilingual Collection Structure

Start by creating a separate collection for each language. For example, you might have collections for English and French content. In your Jekyll site directory, structure the collections as follows:

collections:
  en:
    output: true
  fr:
    output: true

Defining Collections in _config.yml

In your _config.yml file, define the collections for each language as shown above. Each language collection should have a corresponding directory under _posts or wherever your content resides. Each collection can then contain its own posts, pages, or other types of content that need translation.

Structure of Multilingual Collections

Here’s how you can structure your content directory for multilingual collections:

/_posts
  /en
    2025-05-01-welcome-to-our-blog.md
    2025-05-02-first-post.md
  /fr
    2025-05-01-bienvenue-sur-notre-blog.md
    2025-05-02-premier-article.md

Why Use Language-Specific Directories?

Using language-specific directories (e.g., /en/ for English and /fr/ for French) provides clear separation of content. It also ensures that each language collection can be individually built and outputted to the correct location.

Accessing Collection Data in Liquid

Once collections are set up, you can access them in your Jekyll templates using Liquid syntax. Let’s look at how to list posts from a specific language collection:

{% raw %}{% for post in site.en.posts %}
  <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
  <p>{{ post.excerpt }}</p>
{% endfor %}{% endraw %}

For multilingual websites, you can easily adjust this by using dynamic language selection. Here’s an example of how to loop through content for the current language:

{% raw %}{% assign current_lang = page.lang | default: 'en' %}
{% for post in site[current_lang].posts %}
  <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
  <p>{{ post.excerpt }}</p>
{% endfor %}{% endraw %}

Creating a Multilingual Index Page

Now that you’ve set up collections, you can build an index page that dynamically lists content in the correct language. For example, on your homepage, you might want to display the latest posts in both English and French:

{% raw %}{% assign current_lang = page.lang | default: 'en' %}
{% assign posts = site[current_lang].posts %}
{% for post in posts %}
  <article>
    <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
    <p>{{ post.excerpt }}</p>
  </article>
{% endfor %}{% endraw %}

Adding Language Selector to the Index

On the index page, you may want to add a language switcher that allows visitors to toggle between available languages. Combine the logic we covered in earlier articles to make this switcher dynamic.

{% raw %}{% assign current_lang = page.lang | default: 'en' %}
<div class="language-switcher">
  {% for lang in site.data.languages.languages %}
    {% if lang.code != current_lang %}
      <a href="{{ lang.baseurl }}"/>{{ lang.name }}</a>
    {% endif %}
  {% endfor %}
</div>{% endraw %}

Case Study: A Business Blog with English and Spanish

One of our clients, a small business specializing in local products, wanted to expand its reach by offering content in both English and Spanish. Using the multilingual collection setup, they easily managed both language versions of their blog. The directory structure allowed content editors to write posts in either language, and the language switcher ensured a seamless experience for visitors.

Managing Large-Scale Multilingual Content

As your website grows, you may have a large number of posts in each language. Organizing content with collections allows you to manage these large sets of posts efficiently. You can use Jekyll's built-in features like pagination and tags to further structure and navigate content.

Pagination for Multilingual Collections

Pagination is essential for large blogs or documentation sites. With collections, you can paginate posts in each language separately. For example, in the English collection:

{% raw %}{% assign current_lang = 'en' %}
{% for post in site[current_lang].posts %}
  <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
{% endfor %}{% endraw %}

Final Thoughts

Using collections for multilingual content in Jekyll offers a simple and powerful solution to managing and displaying translated content. With the ability to organize content, generate dynamic lists, and add features like pagination and search, you can create a robust multilingual site that scales with your needs.

Next Steps

In the next article, we’ll explore how to use custom page layouts to better control the structure of your multilingual content and improve SEO even further. We’ll also look into integrating search functionality for multilingual sites.

language switcher implementation with liquid logic

Why You Need a Language Switcher

When creating a multilingual website, one essential element of user experience is the ability to switch languages easily. Without a clear and intuitive switcher, visitors may miss translated content, which could reduce engagement and SEO effectiveness. In this article, we’ll walk through how to implement a dynamic language switcher using Liquid templating in Jekyll.

Designing the Language Switcher Concept

Before jumping into code, it's important to define how the switcher will behave. A good language switcher should:

  • Preserve page context when switching (e.g., /en/about goes to /fr/about)
  • Use clean URLs and avoid query parameters
  • Be accessible and SEO-friendly
  • Be flexible enough to add more languages later

Key Considerations

The switcher depends on:

  • Consistent page slugs across languages
  • A known list of supported languages
  • Directory structure reflecting language (e.g., /en/, /fr/)

Setting Up the Language Data File

Create a central data file to list supported languages. For example, add a new file: _data/languages.yml

languages:
  - code: en
    name: English
    baseurl: /en/
  - code: fr
    name: Français
    baseurl: /fr/
  - code: es
    name: Español
    baseurl: /es/

Tracking the Current Language

Each page should define its language in front matter using a lang attribute:

lang: en
permalink: /en/about/

In layout files, you can access the language like this:

{% raw %}{% assign current_lang = page.lang | default: 'en' %}{% endraw %}

Creating a Language Switcher Include

Create a reusable include named language-switcher.html inside your _includes directory. This snippet loops through all supported languages and builds the switcher UI.

Basic Implementation

{% raw %}{% assign current_lang = page.lang | default: 'en' %}
{% assign path = page.url | split: '/' %}
{% assign page_slug = path[2] %}
<ul class="language-switcher">
  {% for lang in site.data.languages.languages %}
    {% if lang.code != current_lang %}
      <li>
        <a href="{{ lang.baseurl }}{{ page_slug }}/">{{ lang.name }}</a>
      </li>
    {% endif %}
  {% endfor %}
</ul>{% endraw %}

Improving with Fallback Handling

Sometimes, a page slug may not exist in another language. To handle this, define a map of slugs in your data folder:

_data/slugs.yml
about:
  en: "about"
  fr: "a-propos"
  es: "sobre-nosotros"
contact:
  en: "contact"
  fr: "contact"
  es: "contacto"

Refined Switcher Using Slug Map

{% raw %}{% assign current_slug = page.url | split: '/' | last %}
{% assign current_lang = page.lang | default: 'en' %}
{% assign slug_map = site.data.slugs[current_slug] %}

<ul class="language-switcher">
  {% for lang in site.data.languages.languages %}
    {% assign target_slug = slug_map[lang.code] %}
    {% if target_slug %}
      <li>
        <a href="{{ lang.baseurl }}{{ target_slug }}/">{{ lang.name }}</a>
      </li>
    {% endif %}
  {% endfor %}
</ul>{% endraw %}

Styling the Language Switcher

Add basic styles to make the switcher user-friendly:

.language-switcher {
  list-style: none;
  padding: 0;
  display: flex;
  gap: 1rem;
}
.language-switcher li a {
  text-decoration: none;
  font-weight: bold;
}

Accessibility and SEO Best Practices

  • Use hreflang meta tags in the HTML head
  • Ensure aria-label attributes are used for screen readers
  • Mark current language using aria-current="true" or CSS class

Example with Accessibility

{% raw %}<ul class="language-switcher">
  {% for lang in site.data.languages.languages %}
    {% assign target_slug = site.data.slugs[page.slug][lang.code] %}
    <li>
      <a href="{{ lang.baseurl }}{{ target_slug }}/"
         {% if lang.code == page.lang %}aria-current="true"{% endif %}>
         {{ lang.name }}
      </a>
    </li>
  {% endfor %}
</ul>{% endraw %}

Case Study: News Website with 4 Languages

A multilingual news platform needed a way for readers to switch articles between English, French, German, and Spanish. Using slug maps, language data files, and a shared layout, they implemented a dynamic switcher in less than a day. The bounce rate dropped by 12% and page views per session increased as readers found familiar language content more easily.

Troubleshooting Common Issues

  • Broken links: Always test with multiple slugs per language
  • Undefined slugs: Add fallback logic or remove those links
  • Wrong language shown: Ensure each page has correct lang front matter

Final Notes

A dynamic language switcher powered by Liquid and data logic keeps your Jekyll site user-friendly and scalable. It encourages multilingual SEO, improves UX, and allows international expansion without overhauling your codebase.

Next Steps

In the next article, we’ll explore how to organize content using multilingual collections—a powerful way to handle large-scale translated content like blog posts, tutorials, or documentation.