Nimbu Developer Docs
Other

Multilingual & Localization

Build multilingual sites with locale routing, translations, and localized content

Nimbu provides comprehensive multilingual support with automatic locale routing, translation management, and localized content delivery.

Locale Configuration

Locales are configured in your theme's config/locales.yml:

default: en
available:
  - en
  - nl
  - fr
  - de

Locale Variables

locale

Current active locale:

{{ locale }}
<!-- Output: "en", "nl", "fr", etc. -->

{% if locale == 'nl' %}
  <!-- Dutch-specific content -->
{% endif %}

locale_url_prefix

URL prefix for current locale:

{{ locale_url_prefix }}
<!-- Output: "/nl" for Dutch, "" for default locale -->

<a href="{{ locale_url_prefix }}/about">About</a>

config.locales

All available locales:

{% for locale_pair in config.locales %}
  {{ locale_pair[0] }}: {{ locale_pair[1] }}
{% endfor %}

Translation Helpers

{% translate %}

Fetch translations from your translation files:

{% translate 'menu.home' %}
<!-- Output: "Home" (en), "Accueil" (fr), "Home" (nl) -->

{% translate 'menu.about', default: 'About Us' %}

{% translate 'cart.items_count', count: cart.items.size %}

With Variables:

{% translate 'welcome.message', name: customer.firstname %}
<!-- Translation: "Welcome back, {{name}}!" -->
<!-- Output: "Welcome back, John!" -->

{% localized_path %}

Generate URL for specific locale:

{% localized_path 'nl' %}
<!-- Current page in Dutch -->

{% localized_path 'fr' %}
<!-- Current page in French -->

<a href="{% localized_path 'de' %}">Deutsch</a>

Working with Translations

Page Translations

Pages have built-in translation support:

{{ page.translations }}
<!-- Array of available translations -->

{% for translation in page.translations %}
  <a href="{{ translation.url }}" hreflang="{{ translation.locale }}">
    {{ translation.localized_language }}
  </a>
{% endfor %}

Translation Properties:

  • translation.locale - Locale code (en, nl, fr)
  • translation.url - Full URL to translated page
  • translation.localized_language - Language name in that language

Practical Examples

Language Switcher

<div class="language-switcher">
  {% if page.translations.size > 1 %}
    <!-- Page-specific translations -->
    {% for translation in page.translations %}
      <a
        href="{{ translation.url }}"
        class="lang-link {% if locale == translation.locale %}active{% endif %}"
        hreflang="{{ translation.locale }}">
        {{ translation.locale | upcase }}
      </a>
    {% endfor %}
  {% elsif config.locales.size > 1 %}
    <!-- Site-wide locale switching -->
    {% for locale_option in config.locales %}
      <a
        href="{% localized_path locale_option[1] %}"
        class="lang-link {% if locale == locale_option[1] %}active{% endif %}">
        {{ locale_option[1] | upcase }}
      </a>
    {% endfor %}
  {% endif %}
</div>
<div class="language-dropdown">
  <button class="current-language" aria-haspopup="true">
    {{ locale | upcase }}
    <span class="icon-chevron">▼</span>
  </button>

  <ul class="language-menu" aria-label="{% translate 'language.select' %}">
    {% for translation in page.translations %}
      {% unless locale == translation.locale %}
        <li>
          <a
            href="{{ translation.url }}"
            hreflang="{{ translation.locale }}">
            {{ translation.localized_language }}
          </a>
        </li>
      {% endunless %}
    {% endfor %}
  </ul>
</div>

Multilingual Navigation

<nav class="main-nav" aria-label="{% translate 'nav.main' %}">
  <a href="{{ locale_url_prefix }}/">
    {% translate 'menu.home' %}
  </a>

  <a href="{{ locale_url_prefix }}/about">
    {% translate 'menu.about' %}
  </a>

  <a href="{{ locale_url_prefix }}/products">
    {% translate 'menu.products' %}
  </a>

  <a href="{{ locale_url_prefix }}/contact">
    {% translate 'menu.contact' %}
  </a>
</nav>

Localized Date Formatting

<article>
  <h1>{{ article.title }}</h1>

  <time datetime="{{ article.published_at | date: '%Y-%m-%d' }}">
    {{ article.published_at | localized_date: "long" }}
  </time>
  <!-- English: "October 7, 2025" -->
  <!-- Dutch: "7 oktober 2025" -->
  <!-- French: "7 octobre 2025" -->

  <p class="byline">
    {% translate 'article.published_by', author: article.author.name %}
  </p>

  {{ article.content }}
</article>

A search form for templates/search.liquid that keeps the locale prefix. See Search for the results object.

<form action="{{ locale_url_prefix }}/search" method="get">
  <input
    type="search"
    name="q"
    placeholder="{% translate 'search.placeholder' %}"
    value="{{ params.q | escape }}">

  <button type="submit">
    {% translate 'search.submit' %}
  </button>
</form>

{% if params.q %}
  {% assign safe_query = params.q | escape %}
  <div class="search-results">
    <h2>
      {% translate 'search.results_for', query: safe_query %}
    </h2>

    {% if results.any? %}
      {% for result in results.all %}
        <div class="result">
          <h3><a href="{{ result.url }}">{{ result.title }}</a></h3>
        </div>
      {% endfor %}
    {% else %}
      <p class="no-results">
        {% translate 'search.no_results' %}
      </p>
    {% endif %}
  </div>
{% endif %}

Localized Product Catalog

<div class="product-list">
  {% for product in products %}
    <div class="product-card">
      <a href="{{ locale_url_prefix }}{{ product.url }}">
        <img
          src="{{ product.images.first.url | filter, width: '400px' }}"
          alt="{{ product.name }}">
      </a>

      <h3>
        <a href="{{ locale_url_prefix }}{{ product.url }}">
          {{ product.name }}
        </a>
      </h3>

      <p class="price">
        {{ product.price | money_with_currency }}
      </p>

      <a
        href="{{ product.url }}"
        class="btn">
        {% translate 'product.view_details' %}
      </a>
    </div>
  {% endfor %}
</div>

Multilingual Contact Form

<div class="contact-page">
  <h2>{% translate 'contact.title' %}</h2>
  <p>{% translate 'contact.subtitle' %}</p>

  {% form channels.contact, class: 'contact-form' %}
    {% error_messages_for form_model %}

    <div class="form-group">
      {% input 'name', label: {% translate 'contact.name_label' %}, required: true %}
    </div>

    <div class="form-group">
      {% input 'email', as: 'email', label: {% translate 'contact.email_label' %}, required: true %}
    </div>

    <div class="form-group">
      {% text_area 'message', rows: 6, label: {% translate 'contact.message_label' %}, required: true %}
    </div>

    {% submit_tag {% translate 'contact.submit' %}, class: 'btn btn-primary' %}
  {% endform %}
</div>

Locale Selection

<div class="region-settings">
  <div class="locale-selector">
    <label>{% translate 'settings.language' %}:</label>
    <select id="locale-select">
      {% for translation in page.translations %}
        <option
          value="{{ translation.url }}"
          {% if locale == translation.locale %}selected{% endif %}>
          {{ translation.localized_language }}
        </option>
      {% endfor %}
    </select>
  </div>
</div>

<script>
document.getElementById('locale-select').addEventListener('change', function() {
  window.location.href = this.value;
});
</script>

A site has one shop currency, so there is no currency to switch.

SEO: Language Annotations

You don't write hreflang or canonical tags yourself. {{ content_for_header }} outputs, for the current page:

  • a canonical link to the page in the current locale,
  • a <link rel="alternate" hreflang="..."> for every locale the page is available in,
  • the same annotations as HTTP Link headers.

Hidden locales, and locales where the page is not available, are left out. The themed 404 page gets no canonical or hreflang links.

Two settings in the Search Engines section of Settings → General change the output:

  • Search language overrides: the language tag per locale, for example nl-BE for nl to target Belgium only. Leave blank to use the locale code.
  • Fallback language (x-default): the locale that also gets hreflang="x-default", the version suggested when no language matches. Visitors are not redirected.

Put the language on the <html> element, not in <head>:

<!DOCTYPE html>
<html lang="{{ locale }}">
<head>
  <title>{{ page.title }} - {{ site.name }}</title>
  {{ content_for_header }}
</head>

If a layout does not output {{ content_for_header }}, none of these annotations are added. See SEO, robots.txt & llms.txt for more.

Translation File Structure

Translations are stored in config/translations/:

# config/translations/en.yml
en:
  menu:
    home: "Home"
    about: "About Us"
    products: "Products"
    contact: "Contact"

  cart:
    title: "Shopping Cart"
    items_count:
      one: "1 item"
      other: "{{count}} items"
    empty: "Your cart is empty"

  product:
    view_details: "View Details"
    add_to_cart: "Add to Cart"
    out_of_stock: "Out of Stock"
# config/translations/nl.yml
nl:
  menu:
    home: "Home"
    about: "Over Ons"
    products: "Producten"
    contact: "Contact"

  cart:
    title: "Winkelwagen"
    items_count:
      one: "1 artikel"
      other: "{{count}} artikelen"
    empty: "Je winkelwagen is leeg"

  product:
    view_details: "Bekijk Details"
    add_to_cart: "Toevoegen"
    out_of_stock: "Niet op Voorraad"

Best Practices

1. Use Translation Keys

<!-- ✅ Good: Use translation keys -->
<h1>{% translate 'page.title' %}</h1>

<!-- ❌ Bad: Hardcode text -->
<h1>Welcome</h1>

2. Provide Defaults

<!-- ✅ Good: Fallback for missing translations -->
{% translate 'new.feature', default: 'New Feature' %}

<!-- ⚠️ Without default, shows key if missing -->
{% translate 'new.feature' %}

3. Consistent URL Structure

<!-- ✅ Good: Use locale_url_prefix consistently -->
<a href="{{ locale_url_prefix }}/products">Products</a>

<!-- ❌ Bad: Hardcoded paths -->
<a href="/products">Products</a>

4. Let Nimbu Handle Language Tags

Output {{ content_for_header }} in every layout and set <html lang="{{ locale }}">. Nimbu adds the hreflang alternates and canonical link; configure regional tags and x-default in the site settings instead of in the theme.

Next Steps

Build truly global sites with Nimbu's multilingual features!

On this page