Skip to content

Multilingual & Localization

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:

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

Locale Variables

locale

Current active locale:

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

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

locale_url_prefix

URL prefix for current locale:

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

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

config.locales

All available locales:

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

Translation Helpers

{% translate %}

Fetch translations from your translation files:

liquid
{% 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:

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

{% localized_path %}

Generate URL for specific locale:

liquid
{% 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:

liquid
{{ 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

liquid
<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>
liquid
<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

liquid
<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

liquid
<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>
liquid
<form action="{{ locale_url_prefix }}/search" method="get">
  <input
    type="search"
    name="q"
    placeholder="{% translate 'search.placeholder' %}"
    value="{{ params.q }}">

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

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

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

Localized Product Catalog

liquid
<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

liquid
<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>

Currency and Locale Selection

liquid
<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 class="currency-selector">
    <label>{% translate 'settings.currency' %}:</label>
    <select id="currency-select">
      <option value="EUR" {% if site.currency == 'EUR' %}selected{% endif %}>EUR (€)</option>
      <option value="USD" {% if site.currency == 'USD' %}selected{% endif %}>USD ($)</option>
      <option value="GBP" {% if site.currency == 'GBP' %}selected{% endif %}>GBP (£)</option>
    </select>
  </div>
</div>

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

SEO: Alternate Language Tags

liquid
<head>
  <!-- Language alternatives for SEO -->
  {% for translation in page.translations %}
    <link
      rel="alternate"
      hreflang="{{ translation.locale }}"
      href="{{ translation.url }}">
  {% endfor %}

  <!-- Default language fallback -->
  <link rel="alternate" hreflang="x-default" href="{{ page.url }}">

  <!-- Current page language -->
  <html lang="{{ locale }}">
</head>

Translation File Structure

Translations are stored in config/translations/:

yaml
# 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"
yaml
# 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

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

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

2. Provide Defaults

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

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

3. Consistent URL Structure

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

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

4. SEO-Friendly Language Tags

liquid
<!-- ✅ Good: Complete alternate tags -->
{% for translation in page.translations %}
  <link rel="alternate" hreflang="{{ translation.locale }}" href="{{ translation.url }}">
{% endfor %}

Next Steps

Build truly global sites with Nimbu's multilingual features!