About
The localisation module allows you to translate content on client side. This is especially useful if the used (CMS) system is not able to translate the content server side or if no hard reload should happen if the language was changed.
Install
npm install --save @ribajs/i18n
Register
Import i18nModule and a LocalesService implementation (for example LocalesStaticService), then register the module:
import { coreModule, Riba } from "@ribajs/core";
import { ready } from "@ribajs/utils/src/dom.js";
import { i18nModule, LocalesStaticService } from "@ribajs/i18n";
const locales = {
en: {
examples: {
newsletter: { title: "Hello" },
},
},
};
const riba = new Riba();
const localesService = new LocalesStaticService(locales);
const model = {};
riba.module.register(coreModule.init());
riba.module.register(i18nModule.init({ localesService }));
ready(() => {
riba.bind(document.body, model);
});
The root keys of the locales object must be language codes (en, de, …). See LocalesStaticService.
A default export exists only in the browser bundle (browser.ts); from the main package entry use named imports: import { i18nModule } from '@ribajs/i18n'.
Templates
The module reads the default language from the lang attribute on <html>. Set it to one of your locale keys (e.g. en or de):
<!doctype html>
<html lang="en">
<head>
...
</head>
<body>
...
</body>
</html>
Events
Events are emitted on the LocalesService instance you pass to i18nModule.init({ localesService }), via localesService.event (an EventDispatcher with namespace 'i18n'). You can subscribe with the same namespace:
import { EventDispatcher } from "@ribajs/events";
const event = new EventDispatcher("i18n");
event.on("changed", (langcode: string, initial: boolean) => {
console.debug("The language was changed", langcode, initial);
});
event.on("ready", (currentLangcode: string, translationNeeded: boolean) => {
console.debug(
"Locales are initialized; module is ready",
currentLangcode,
translationNeeded,
);
});
Alternatively, use localesService.event.on(...) on your registered service (same dispatcher instance as new EventDispatcher('i18n')).
| Name | Arguments | Description |
|---|---|---|
| changed | langcode, initial |
The active language was changed. |
| ready | currentLangcode, translationNeeded |
Locales finished loading and initialization; safe to translate. |
Services
LocalesService
The abstract class LocalesService (packages/i18n/src/types/locales-service.ts) is the base for all locale backends. Implementations included in @ribajs/i18n:
| Class | Purpose |
|---|---|
LocalesStaticService |
In-memory object (de / en / … as keys) |
LocalesRestService |
Load JSON from a URL via HttpService |
To implement your own backend, extend LocalesService and implement protected async getAll(): Promise<any> so it returns the same shape as the static service (top-level keys = language codes). Call super(doNotRetranslateDefaultLanguage, showMissingTranslation, autoDetectLangcode) with the three boolean flags expected by the base constructor, then invoke the initialization flow (see LocalesStaticService / LocalesRestService in the source tree for full patterns).
The former name ALocalesService is outdated; the exported abstract type is LocalesService.
LocalesStaticService
The LocalesStaticService can be used to integrate and define the translations directly in the source code:
The root object must use language codes (de, en, …) as keys. Do not wrap translations in an extra property such as locales; otherwise lookups like examples.newsletter.title resolve to en.locales.examples… and fail.
import { LocalesStaticService } from '@ribajs/i18n';
// Your static locales — top-level keys are language codes
const locales = {
de: {
examples: {
newsletter: {
description_html: 'Abonnieren Sie unseren Newsletter und erhalten Sie <strong>10% Rabatt</strong> auf Ihren nächsten Einkauf.',
input_value: 'Unbekannt',
placeholder_last_name: 'Nachname',
title: 'Melde dich für den Newsletter an',
},
},
},
en: {
examples: {
newsletter: {
description_html: 'Subscribe to our newsletter and get <strong>10% off</strong> your next purchase.',
input_value: 'Unknown',
placeholder_last_name: 'Surname',
title: 'Sign up for the newsletter',
},
},
},
};
const localesService = new LocalesStaticService(locales);
import { coreModule, Riba } from "@ribajs/core";
import { ready } from "@ribajs/utils/src/dom.js";
import { i18nModule, LocalesStaticService } from "@ribajs/i18n";
import I18nStaticModule from "./i18n-static.module.js";
const locales = {
de: {
examples: {
i18n: {
switch_language: "Klicke auf eine Sprache um sie zu ändern",
},
newsletter: {
description_html:
"Abonnieren Sie unseren Newsletter und erhalten Sie <strong>10% Rabatt</strong> auf Ihren nächsten Einkauf.",
input_value: "Unbekannt",
placeholder_last_name: "Nachname",
title: "Melde dich für den Newsletter an",
},
},
},
en: {
examples: {
i18n: {
switch_language: "Click on a language to change it",
},
newsletter: {
description_html:
"Subscribe to our newsletter and get <strong>10% off</strong> your next purchase.",
input_value: "Unknown",
placeholder_last_name: "Surname",
title: "Sign up for the newsletter",
},
},
},
};
const riba = new Riba();
const localesService = new LocalesStaticService(locales);
const model = {};
riba.module.register(coreModule.init());
riba.module.register(i18nModule.init({ localesService }));
riba.module.register(I18nStaticModule.init());
ready(() => {
riba.bind(document.body, model);
});
<i18n-switcher>
<p><strong rv-i18n-text="'examples.i18n.switch_language'"></strong></p>
<div class="btn-group" role="group">
<button
type="button"
class="btn btn-secondary text-uppercase"
rv-each-langcode="langcodes"
rv-text="langcode.code"
rv-on-click="switch | args langcode"
rv-class-active="langcode.active"
>Left</button>
</div>
</i18n-switcher>
<div class="form-row my-5">
<div class="col-12">
<h1 rv-i18n-text="'examples.newsletter.title'"></h1>
<p rv-i18n-html="'examples.newsletter.description_html'"></p>
</div>
<div class="col">
<input type="text" class="form-control" rv-i18n-value="'examples.newsletter.input_value'" />
</div>
<div class="col">
<input type="text" class="form-control" rv-i18n-placeholder="'examples.newsletter.placeholder_last_name'" />
</div>
</div>
import { Component } from "@ribajs/core";
export class I18nStaticExampleComponent extends Component {
public static tagName = "rv-i18n-static-example";
protected autobind = true;
static get observedAttributes(): string[] {
return [];
}
public scope = {};
constructor() {
super();
}
protected connectedCallback() {
super.connectedCallback();
this.init(I18nStaticExampleComponent.observedAttributes);
}
protected async init(observedAttributes: string[]) {
return super.init(observedAttributes).then((view) => {
return view;
});
}
protected requiredAttributes(): string[] {
return [];
}
protected async template() {
const { default: template } =
await import("./i18n-static-example.component.html?raw");
return template;
}
}
LocalesRestService
The LocalesRestService loads the translation tree from a URL (JSON). The response should use language codes as top-level keys, same as LocalesStaticService.
import { LocalesRestService } from '@ribajs/i18n';
const url = 'https://example.com/api/locales.json';
const localesService = new LocalesRestService(url);
Constructor signature:
new LocalesRestService(
url: string,
doNotRetranslateDefaultLanguage?: boolean,
showMissingTranslation?: boolean,
autoDetectLangcode?: boolean,
)
If the page runs in a Shopify theme context, the service may append ?shop=… to the request (see implementation).
Binders
i18n-[type]
Gets the (current selected) translation of a keypath string from your locales object (over the TranslationService).
The translation is set as html, text or value depending on the passed type, available types are html, text and value.
If the type is not known, the translation is instead set as an attribute with the given name, a useful example for this could be the placeholder attribute.
i18n-text
i18n-html
i18n-value
i18n-[attributeName]
Formatters
t
This formatter resolves a translation key path and now re-renders when the active language changes. For element content/attributes, the i18n-* binders (rv-i18n-text, rv-i18n-html, …) are still recommended because they are more explicit and support rich translation templates directly.
In mustache text nodes, Riba’s default delimiters are a single { and } (see configuration: templateDelimiters). That differs from Liquid/Shopify, which use {{ and }} — do not copy that syntax here.
{ 'examples.newsletter.title' | t }
{ 'examples.newsletter.title' | t }
Components
i18n-switcher
Use this component to switch to another available language. It does not ship with a template: add your own markup as children (e.g. buttons bound to langcodes).
Types
export interface Langcode {
code: string;
active: boolean;
}
Template methods
| Name | Arguments | Description |
|---|---|---|
| switch | langcode: Langcode | Activates the given language (Langcode.code) if it is not already active |
| toggle | Switches to the other language (only useful when exactly two languages exist) |
Template properties
| Name | Type | Description |
|---|---|---|
| langcodes | Langcode[] |
Language codes from your locales object (with active set for the current one) |
| ready | boolean |
Is true if the locales are initialized |