Skip to content

Latest commit

 

History

History
349 lines (260 loc) · 8.91 KB

File metadata and controls

349 lines (260 loc) · 8.91 KB

Internationalization (i18n)

Setup

<script>
  NoJS.i18n({
    defaultLocale: 'en',
    fallbackLocale: 'en',
    locales: {
      en: {
        greeting: 'Hello, {name}!',
        items: '{count} item | {count} items',  // Pluralization
        nav: {
          home: 'Home',
          about: 'About'
        }
      },
      'pt-BR': {
        greeting: 'Olá, {name}!',
        items: '{count} item | {count} itens',
        nav: {
          home: 'Início',
          about: 'Sobre'
        }
      }
    }
  });
</script>

External Locale Files

Instead of inlining all translations in JavaScript, load them from external JSON files.

Flat Mode (one file per locale)

/locales/en.json
/locales/es.json
<script>
  NoJS.i18n({
    defaultLocale: 'en',
    loadPath: '/locales/{locale}.json'
  });
</script>

Namespace Mode (split by feature)

/locales/en/common.json
/locales/en/dashboard.json
<script>
  NoJS.i18n({
    defaultLocale: 'en',
    loadPath: '/locales/{locale}/{ns}.json',
    ns: ['common']   // Loaded at init
  });
</script>

Namespace per Route

<template route="/dashboard" src="./pages/dashboard.tpl" i18n-ns="dashboard"></template>

Namespace on Any Element

<div i18n-ns="settings">
  <h2 t="settings.title"></h2>
  <p t="settings.desc"></p>
</div>

Caching

Fetched JSON files are cached in memory by default. Disable in development:

<script>
  NoJS.i18n({ loadPath: '/locales/{locale}.json', cache: false });
</script>

Fallback Behavior

When a translation key is missing from the current locale, No.JS falls back to the fallbackLocale:

<script>
  NoJS.i18n({
    defaultLocale: 'pt-BR',
    fallbackLocale: 'en',
    locales: {
      en: { greeting: 'Hello', farewell: 'Goodbye' },
      'pt-BR': { greeting: 'Olá' }
      // farewell missing → falls back to English "Goodbye"
    }
  });
</script>

If the key is missing from both the current locale and the fallback, the raw key string is displayed.

Browser Locale Detection

Enable automatic locale detection from the browser's navigator.language:

<script>
  NoJS.i18n({
    detectBrowser: true,
    defaultLocale: 'en',
    fallbackLocale: 'en'
  });
</script>

When detectBrowser is true, No.JS checks navigator.language and uses the matching locale if available. Falls back to defaultLocale if no match is found.

Detection with lazy locale files (supportedLocales)

With inline locales, detection matches navigator.language against the loaded bundle keys. But with loadPath (lazy JSON locales), no bundles exist yet when detection runs — so you must declare the locales you ship in supportedLocales. The browser language is adopted on the first visit only when it appears in that list.

<script>
  NoJS.i18n({
    loadPath: '/locales/{locale}/{ns}.json',
    ns: ['common'],
    supportedLocales: ['en', 'es', 'pt', 'it', 'fr'],
    detectBrowser: true,
    persist: true,
    defaultLocale: 'en'
  });
</script>

On the first load, the active locale is resolved in this priority order:

  1. Persisted nojs-locale — a previously chosen locale in localStorage (when persist: true) always wins.
  2. Detected browser languagenavigator.language (or its base, e.g. pt from pt-BR), if it is listed in supportedLocales.
  3. defaultLocale — the fallback when nothing else matches.

This means a returning visitor who picked a language keeps it, while a first-time visitor lands in their browser's language when you support it.


Usage

<!-- Simple translation -->
<h1 t="greeting" t-name="user.name"></h1>
<!-- Output: "Hello, John!" or "Olá, John!" -->

<!-- Nested keys -->
<a route="/" t="nav.home"></a>

<!-- Pluralization -->
<span t="items" t-count="cart.items.length"></span>
<!-- Output: "1 item" or "5 items" -->

<!-- Switch locale -->
<button on:click="$i18n.locale = 'pt-BR'">Português</button>
<button on:click="$i18n.locale = 'en'">English</button>

<!-- Current locale -->
<span bind="$i18n.locale"></span>

HTML Translations (t-html)

Add the t-html attribute to render a translation value as sanitized HTML instead of plain text.

<!-- Translation: "Read our <a href='/terms'>terms</a>" -->
<div t="legal.notice" t-html></div>

The output is passed through _sanitizeHtml() to prevent XSS.


Number & Date Formatting

<!-- Currency -->
<span bind="price | currency"></span>           <!-- $1,234.56 -->
<span bind="price | currency:'BRL'"></span>     <!-- R$ 1.234,56 -->

<!-- Date -->
<span bind="createdAt | date"></span>            <!-- 02/25/2026 -->
<span bind="createdAt | date:'long'"></span>     <!-- February 25, 2026 -->
<span bind="createdAt | datetime"></span>        <!-- 02/25/2026 3:45 PM -->
<span bind="createdAt | relative"></span>        <!-- 2 hours ago -->

<!-- Number -->
<span bind="value | number"></span>              <!-- 1,234.56 -->
<span bind="value | number:0"></span>            <!-- 1,235 -->
<span bind="value | percent"></span>             <!-- 45% -->

Accessing Translations in Expressions ($i18n.*)

The $i18n reactive proxy lets you access translation data as dot-notation properties directly in any expression context. This is the preferred approach for simple lookups — no directive or function call needed.

Syntax

$i18n.[namespace].[key].[subkey]

The proxy resolves dot-path properties against the loaded translation data for the current locale. For example, given this locale file:

// /locales/en/common.json
{
  "buttons": { "save": "Save", "cancel": "Cancel" },
  "status": { "online": "Online", "offline": "Offline" }
}

You can access any value with $i18n.common.buttons.save.

Usage in state and store

<!-- Initialize state with translated labels -->
<div state="{ label: $i18n.common.buttons.save }">
  <button bind="label"></button>
</div>

<!-- Use in store definitions -->
<div store="ui" value="{
  title: $i18n.shell.sidebar.introduction,
  placeholder: $i18n.common.search.placeholder
}"></div>

Usage in bind

<span bind="$i18n.common.buttons.save"></span>
<h1 bind="$i18n.home.hero.title"></h1>

<!-- Combine with other expressions -->
<span bind="$i18n.common.status.online + ' (' + users.length + ')'"></span>

Usage in computed

<div state="{ role: 'admin' }">
  <span computed="roleLabel"
        expr="role === 'admin' ? $i18n.roles.admin : $i18n.roles.user"></span>
  <p bind="roleLabel"></p>
</div>

Usage in conditionals

<span if="$i18n.features.beta" bind="$i18n.features.beta"></span>

Usage in foreach

<!-- Translated headers for a table -->
<th foreach="col in ['name', 'email', 'role']"
    bind="$i18n.table.headers[col]"></th>

Usage in attribute binding

<input bind-placeholder="$i18n.common.search.placeholder">
<img bind-alt="$i18n.common.logo.alt" src="/logo.png">
<button bind-title="$i18n.common.buttons.save" bind-aria-label="$i18n.common.buttons.save">

$i18n.* vs $i18n.t() — When to Use Each

$i18n.key.path $i18n.t('key.path', opts)
Use for Simple string lookups Interpolation, pluralization
Syntax Dot-notation property access Function call with options
Interpolation No Yes ({ name: user.name })
Pluralization No Yes ({ count: n })
<!-- Simple lookup — use $i18n.* -->
<span bind="$i18n.common.buttons.save"></span>

<!-- Interpolation — use $i18n.t() -->
<span bind="$i18n.t('greeting', { name: user.name })"></span>

<!-- Pluralization — use $i18n.t() -->
<span bind="$i18n.t('items', { count: cart.length })"></span>

Reserved Properties

The following properties on $i18n always resolve to methods or configuration values, not translation keys:

Property Type Description
locale string Current locale (get/set)
locales object All loaded locale data
t function Translation function for interpolation/pluralization
setLocale function Change the active locale

Reactivity

All $i18n.* bindings are fully reactive. When the locale changes (via $i18n.locale = 'pt' or $i18n.setLocale('pt')), every element that references $i18n.* in any directive automatically re-evaluates with the new locale's data.

Missing Keys

When a key is not found in the current locale, the proxy returns undefined. Combined with the default filter, you can provide fallback text:

<span bind="$i18n.missing.key | default:'Fallback text'"></span>


See Also

Previous: Routing ← | Next: Drag & Drop →