Start with a language-switch at the app root and bind it to a provider that stores the current language and loads messages. Use a selectbox to present options and connect its change event to update the locale.

In typescript projects, basically define messages as a per-language map, register them with the i18n provider, and expose a translator through binding on your page templates.

Use converters to format numbers and dates and to handle pluralization, so UI strings stay correct across languages. Build a small, local messages catalog and load it on startup to avoid flash of untranslated text.

When the user selects a language in the language-switch, the app updates the locale and reloads messages. If the locale becomes undefined, fall back to English. If you wanted the app to reflect changes immediately, trigger a changed event and rebind affected components.

Keep translations organized: store keys in a single namespace, use a light page layout, and just write tests that exercise register and binding flows. If you want to accelerate onboarding, define a shared dictionary for common phrases and document your supported languages. Update messages and converters as your app grows.

Step 1: Install aurelia-i18n plugin and add a translation.json

Install aurelia-i18n plugin and add a translation.json to your project now to enable i18n from the start. Use jspm to align with a bundled workflow or npm for modern builds: jspm install aurelia-i18n --dev; npm i aurelia-i18n --save.

Create a lightweight wrapper that wires the plugin into your Aurelia life cycle. The wrapper handles loading locale data and exposes a simple translation function. Expect converters to format numbers and dates, and hook them into the binding so a single converter handles common formatting while converters can be extended for special needs.

Place translation.json alongside your entry point and reference it through the loader. A typical path is src/assets/i18n/translation.json, and the file should be bundled with the app to ensure it is available at startup, including on Safari where offline caching can affect loading.

Example structure for translation.json: { "messages": { "greeting": "Hello, {name}!" }, "labels": { "save": "Save" } }

Strategy notes: use indefinite keys for broad, reusable strings and specific keys for UI fragments. Selected keys map to the needed UI parts, so wanted phrases stay readable in code and translators can return accurate results. The project should evolve by adding new keys under messages and labels as features change, without breaking existing translations.

If you use aureliavalidation-i18n, align validation messages in translation.json so the wrapper can deliver user-friendly feedback. Test across browsers, including safari, to confirm that bundled resources load correctly and that locale changes reflect immediately in the UI life should become clear.

Step 2: Create folders and files (src/i18n, locales, app.js, main.js)

Create the folders and files now and wire aurelia-i18n into your Aurelia app, so localization loads on startup via a backend that serves locale data. Place source i18n logic in src/i18n, and organize translations in locales; keep aurelias naming conventions to avoid collisions.

  1. Folder structure and purpose
    • src/i18n: holds configuration, converters, and any custom i18n adapters.
    • locales: stores per-language JSON packs, loaded with a filesglob pattern like locales/**/*.json.
    • app.js: export a configure function that registers the i18n plugin, the backend, and a default locale (selected).
    • main.js: bootstrap Aurelia, register the i18n plugin, and start the app with binding to set locale and language messages.
  2. app.js configuration
    • Exported function: export function configure(aurelia){ ... }
    • Provide a backend loader to fetch locale files from locales path, using filesglob like locales/**/*.json.
    • Register aurelia-i18n and set default locale via selected.
    • Expose a format helper or converters if you need custom date or number formatting.
  3. Main entry and runtime binding
    • main.js boots Aurelia and creates the binding for a language selector element, e.g., a <selectbox> bound to selected.
    • Use label keys from locales to render dynamic text: ${firstname} from the bound data (firstname comes from your view-model).
    • Return the configured app instance so navigation and routing work with localized content.
  4. Messages and runtime binding
    • In your view templates, bind to i18n keys via a binding or helper, e.g., t('messages.greeting', { firstname: firstname }).
    • Provide a few named translations, such as order and selected, to demonstrate switching locale without a full reload.
    • Define placeholders and formatting in messages to support dynamic values like firstname and aurelias naming.

Sample organization in locales

Tip: use filesglob to load all translations automatically, e.g., locales/**/*.json. In your messages, keep keys flat or grouped under label and messages, so a binding like binding="i18n.t('messages.welcome', { firstname: firstname })" remains clean and specific.

Step 3: Use a plugin with TypeScript and Aurelia's loader

Install aurelia-i18nt and register it with Aurelia's loader in your TypeScript bootstrap to enable dynamic translation loading. This setup lets the loader fetch translation bundles on demand, so you don't ship all strings upfront. Use the option backend with a loadPath that points to your server or static files, and set a default fallbackLocale.

Configure the backend so the loader can request per-namespace files, such as /i18n/en/common.json or /i18n/en/home.json. If a key is missing, it may be undefined, so you can show the original key or a friendly fallback. Cover error handling with a try/catch around the request and log it to window.console for debugging.

Example: in a template, bind to translations with the i18n service. Use keys like firstname to render the user's given name and support a namespace greeting. Example: binding with i18n.t('greeting.firstname') or i18n.t('home:firstname') covers different contexts.

TypeScript tip: provide typed access to keys by declaring a small interface and wrapping the t() call. This becomes specific to your project and helps with binding in templates. If you compile with strict mode, undefined checks become visible at compile time.

Event handling: listen for locale changes via the plugin event, then call a refresh method to reload the namespace bundles. This change flows through your binding updates, so components reflect the new language without manual DOM manipulation.

Backend options: host translations on a dedicated backend, or serve them as static assets. In a simple setup, you call a request to fetch /locales/{lng}/{ns}.json and store results in memory for quick access. The loader would provide a fall back when the requested resource is not found. This would become powerful when you connect to a real backend, and you can search keys across loaded namespaces to cover cases that users named in the UI expect.

Performance tips: preload common namespaces during app startup, cache results, and avoid repeated network calls. Use around the startup to fetch essential bundles, then cover others on demand. When you test, verify that the key firstname resolves correctly across components, and that an unknown key shows the fallback value or the key itself.

Step 4: Structure the Translation JSON File and keys

Adopt a single translation file for the English locale and place it in a locales folder. Load additional fragments with a glob-like pattern so updates stay centralized without manual edits. Keep the tree shallow and logical to support quick lookups during UI rendering.

Organize by feature areas: header, content, and actions. Inside each area, assign short labels for UI strings. Example:

{

"header": { "title": "Welcome", "subtitle": "App Guide" },

"content": { "welcome": "Hello" },

"actions": { "submit": "Submit" }

}

Create a TypeScript type that mirrors the JSON shape. This type clarifies structure and editors offer autocomplete. A compact approach keeps both runtime and compile-time checks aligned.

Handle date-like strings with simple placeholders and a separate formatting utility. Example: "today": "Today is {date}". The runtime applies a date formatter or a dedicated function, keeping strings readable and plain.

As new screens appear, extend the existing tree to keep consistency. Use a central mapping file as the source of truth, and keep a small set of root sections.

Loading notes: use a loader to fetch the JSON and apply strings by path such as header.title or content.welcome.

Example snippet:

{

"header": { "title": "Welcome", "subtitle": "App Guide" },

"content": { "welcome": "Hello" }

}

Step 5: Set Locale and implement a locale select box

Bind a language-switch control to the active locale and trigger a change on the i18n provider when the user selects a new language.

Configure i18n with i18next-xhr-backend and a filesglob so translations load smoothly across the project. This approach covers loading JSON files for each locale, keeps keys organized, and supports dates and locale-specific messages. Use the provider to persist the chosen language, so users see the correct content on subsequent requests.

Implementation details

// View-model import { I18N } from 'aurelia-i18n'; export class App { static inject = [I18N]; constructor(i18n) { this.i18n = i18n; this.locale = localStorage.getItem('locale') || 'en'; } changeLocale(locale) { this.locale = locale; this.i18n.setLocale(locale); localStorage.setItem('locale', locale); } }

<select value.bind="locale" change.delegate="changeLocale(locale)"> <option value="en">English</option> <option value="ru">Русский</option> <option value="es">Español</option> </select>

In the backend setup, use i18next-xhr-backend with a loadPath and filesglob to cover all locale files. Example: backend: { loadPath: '/locales/{{lng}}/{{ns}}.json', filesGlob: '**/*.json' }. This configuration supports specific language bundles and avoids missing keys during requests. For validation messages and dates, hook aureliavalidation-i18n into your forms so feedback adapts to the current locale, and ensure the provider remains the single source of truth for language data.

Step 6: View, Unit Tests, and saved searches to filter results

Bind all UI labels to locale keys and expose a saved searches panel to filter results. there is a clear path using jspm to load aurelia-i18n and windowintl polyfills, then extend the bundled loader to fetch locale bundles from a backend or static files. With this setup, the interface updates naturally when locale changes, there’s a smooth return to previous filters, and the life of the UI stays consistent. Use an options object to share language, region, and date formats across the view so the experience feels cohesive.

To keep the view fast, implement a valueconverter for dates and numbers and wire it to the current locale. Example: a DateValueConverter formats dates via windowintl when available; otherwise it falls back to ISO. typescript sources in the aurelia-framework handle the converter logic, and you can register the converter globally. Label keys drive all strings, and the saved searches panel uses locale-aware labels as well. This approach would be straightforward to adapt in another module and would scale with more locales.

View bindings and value converters

In the aurelia-framework, bind text to i18n keys using the t binding or i18n.bind, and use valueconverter to format dates with formats chosen by the locale. The loader can load resources from bundled assets or a backend, and you can use jspm to manage the runtime dependencies. Ensure dates and currencies render consistently across Safari by including necessary polyfills and testing with windowintl. The example locale files (en.json, fr.json) keep label values in simple strings so translations stay maintainable.

Unit tests and saved searches

Write unit tests with aurelia-testing to verify that the view renders localized strings and that the saved searches filter results correctly. Extend tests to cover the DateValueConverter across locales, and validate that saved searches persist in localStorage and restore on reload. Use a mock backend in tests if needed and verify the loader returns bundles for each locale. After done, run the tests in your preferred runner; this would reduce regression risk and help you extend i18n coverage there and back.