Translations
Elemental provides a reactive translation engine built with Angular Signals, allowing for dynamic updates to your application's text when the language changes or new translations are loaded. This guide covers how to use the translation functions, manage languages, and handle dynamic label replacements.
All translation functionalities are available from @elemental/common
.
Core Concepts
The translation system revolves around a few key functions and concepts:
translate(module, phrase, replacements?, delimiters?)
: This is the primary function. It returns an Angular
that automatically updates with the correct translation whenever the current language changes, new translations are provided for that phrase, or anySignal <string>
-based replacements are updated.Signal addTranslations(language, translations)
: This function is used to load your translation strings into the Elemental translation engine. Translations are organized by language, then by module, then by phrase.currentLanguage()
andsetLanguage(language)
: These allow you to get the current language Signal or set a new language for the entire Elemental environment, respectively.- Label Replacements: The system supports dynamic placeholders in your translation strings (e.g.,
{userName}
) and provides utilities to replace them with actual values.
Setting Up and Loading Translations
To make translations available to your application, you need to load them using addTranslations
.
addTranslations(language: LmnLanguage, translations: LmnTranslations): void
language
: The ISO code of the language for these translations (e.g.,'en'
,'it'
,'fr'
). TheLmnLanguage
type is typically a string like'en'
,'es'
, etc.translations
: An object where keys are module names (strings), and values are objects where keys are the source phrases (in your base language, usually English) and values are their translations.
Example: Loading Translations
Typically, you would load translations for various languages when your application initializes or when a language pack is fetched.
//
// translations.service.ts or app.config.ts
//
import { addTranslations, LmnTranslations, LmnLanguage } from '@elemental/common';
const englishTranslations: LmnTranslations = {
userProfile: {
'Welcome back, {userName}!': 'Welcome back, {userName}!',
'View Profile': 'View Profile',
},
course: {
'Course completed': 'Course completed',
'Start Learning': 'Start Learning',
},
};
const italianTranslations: LmnTranslations = {
userProfile: {
'Welcome back, {userName}!': 'Bentornato, {userName}!',
'View Profile': 'Vedi Profilo',
},
course: {
'Course completed': 'Corso completato',
// 'Start Learning' might not be translated yet for 'it' in this set
},
};
// Call this during your application setup
export function loadAppTranslations() {
addTranslations('en', englishTranslations);
addTranslations('it', italianTranslations);
// Add more languages as needed
}
Important: It's common for an integrating application (like an LMS) to have its own mechanism for fetching and loading a comprehensive set of translations. Elemental's addTranslations
function is designed to integrate with such systems, allowing the application to feed its translations into the Elemental engine.
Using Translations in Your Components
The translate
function is the core of consuming translations.
translate(module: string, phrase: string, replacements?: LmnLabelReplacements, variableDelimiters?: [string, string]):
module
: A string identifying the module or domain of the phrase (e.g.,'userProfile'
,'coursePlayer'
). This helps organize translations.phrase
: The key for the translation, typically the original phrase in the base language (e.g.,'Welcome back, {userName}!'
).replacements
(optional): An object for dynamic values. Keys are placeholder names in thephrase
, and values can be strings, numbers, or Signals of strings/numbers.variableDelimiters
(optional): An array of two strings defining the start and end delimiters for placeholders in thephrase
. Defaults to['{', '}']
.
Basic Translation
To get a translated string, call translate
and then access the value of the returned Signal.
//
// my-component.ts
//
import { Component } from '@angular/core';
import { translate } from '@elemental/common';
@Component ({
selector: 'app-my-component',
template: `
<h2>{{ pageTitle() }}</h2>
<p>{{ welcomeMessage() }}</p>
`,
})
export class MyComponent {
// Assuming 'en' translations for 'common' module are loaded with:
// { common: { 'My Page Title ': 'My Page Title ', 'Hello World': 'Hello World' } }
pageTitle = translate('common', 'My Page Title ');
welcomeMessage = translate('common', 'Hello World');
}
If the current language changes (e.g., via setLanguage('it')
) and Italian translations for 'common.My Page
are available, pageTitle()
will automatically update. If a translation is not found for the current language, the original phrase
string is returned.
Translations with Replacements
Often, translations include dynamic parts.
1. Using translate
with replacements
(Recommended for reactive updates):
If your replacement values are Signals, the translated string will update reactively when these Signals change.
//
// user-greeting.component.ts
//
import { Component , signal } from '@angular/core';
import { translate } from '@elemental/common';
@Component ({
selector: 'app-user-greeting',
template: `<p>{{ greeting() }}</p>`,
})
export class UserGreetingComponent {
userName = signal ('Alice');
// Assumes 'userProfile.Welcome back, {userName}!' is a loaded phrase
greeting = translate('userProfile', 'Welcome back, {userName}!', { userName: this.userName });
changeUser() {
this.userName.set('Bob'); // greeting() will automatically update
}
}
2. Using applyLabelReplacements
for non-Signal or complex cases:
If you have translations with placeholders but the replacement values are not Signals, or if you need to apply replacements to an already translated Signal value, you can use applyLabelReplacements
.
applyLabelReplacements(label: string, replacements: LmnLabelReplacements, replacementDelimiters?: [string, string]): string
//
// course-status.component.ts
//
import { Component , computed , signal } from '@angular/core';
import { translate, applyLabelReplacements } from '@elemental/common';
@Component ({
selector: 'app-course-status',
template: `<p>{{ completionMessage() }}</p>`,
})
export class CourseStatusComponent {
// Phrase: 'Congratulations, you finished {courseName} in {duration} minutes!'
baseCompletionMessage = translate('course', 'Congratulations, you finished {courseName} in {duration} minutes!');
courseName = 'Introduction to Elemental';
durationMinutes = signal (45); // Duration might be reactive
completionMessage = computed (() => {
const translatedBase = this.baseCompletionMessage(); // Get current translation
return applyLabelReplacements(translatedBase, {
courseName: this.courseName, // Static replacement
duration: this.durationMinutes(), // Reactive replacement (value extracted here)
});
});
}
Using Translations in Templates with the lmnReplace
Pipe
For applying replacements directly in the template, especially when working with Signals, the lmnReplace
pipe is very convenient.
LmnLabelReplacementPipe
(lmnReplace
)
<!--
Assuming in your component's TypeScript:
import { translate } from '@elemental/common';
...
progressMessage = translate('course', 'Progress: {percentComplete}%');
percentComplete = signal(0);
-->
<p>{{ progressMessage() | lmnReplace: { percentComplete: percentComplete() } }}</p>
The pipe will handle unwrapping Signal-based replacements.
Managing Language
The translation system reacts to changes in the current language.
currentLanguage():
: A read-only Signal representing the currently active language (e.g.,Signal <LmnLanguage>'en'
).setLanguage(language: LmnLanguage): void
: Function to change the application's current language. Calling this will trigger updates in all Signals created by thetranslate
function.
//
// language-switcher.component.ts
//
import { Component } from '@angular/core';
import { currentLanguage, setLanguage, LmnLanguage } from '@elemental/common';
@Component ({
selector: 'app-language-switcher',
template: `
<p>Current language: {{ currentLang() }}</p>
<button (click)="switchToItalian()">Switch to Italian</button>
<button (click)="switchToEnglish()">Switch to English</button>
`,
})
export class LanguageSwitcherComponent {
currentLang = currentLanguage();
switchToItalian() {
setLanguage('it');
}
switchToEnglish() {
setLanguage('en');
}
}
Global Label Replacements
Sometimes, you might have placeholders that should be globally replaced across many translations (e.g., application name, user's company name).
globalLabelReplacements():
: A read-only Signal of the currently defined global replacements.Signal <LmnLabelReplacements>setGlobalLabelReplacements(replacements: LmnLabelReplacements): void
: Sets or updates the global replacements.GLOBAL_LABEL_REPLACEMENT_DELIMITERS: [string, string]
: The delimiters used for global replacements, which defaults to['', '']
(meaning no delimiters, the key itself is replaced, e.g.,AppName
directly, not{{AppName}}
).
The translate
function automatically applies these global replacements after local ones.
Example:
//
// app.config.ts or similar setup file
//
import { setGlobalLabelReplacements } from '@elemental/common';
setGlobalLabelReplacements({
AppName: 'Elemental Showcase App',
SupportEmail: 'support@elemental.com'
});
//
// translations.ts (example loaded with addTranslations)
//
const englishTranslations: LmnTranslations = {
footer: {
'Contact SupportEmail for help.': 'Contact SupportEmail for help.',
'Welcome to AppName!': 'Welcome to AppName!'
}
};
//
// footer.component.ts
//
import { Component } from '@angular/core';
import { translate } from '@elemental/common';
@Component ({
selector: 'app-footer',
template: `
<p>{{ contactHelp() }}</p>
<p>{{ appWelcome() }}</p>
`,
})
export class FooterComponent {
contactHelp = translate('footer', 'Contact SupportEmail for help.');
appWelcome = translate('footer', 'Welcome to AppName!');
// contactHelp() will become "Contact support@elemental.com for help."
// appWelcome() will become "Welcome to Elemental Showcase App!"
}
Workflow Summary
- Load Translations: During application setup, use
addTranslations
to load all necessary language strings, typically organized by module. This might be done by a central translation management system in larger applications. - Set Initial Language: Use
setLanguage
to establish the default language. - Consume Translations:
- In your components, use
translate('module', 'phrase')
to get a Signal for a static translated string. - For dynamic content, pass a
replacements
object totranslate
, ideally using Signals for reactive updates:translate('module', 'phrase with {param}', { param: mySignal })
. - Alternatively, use the
lmnReplace
pipe in templates:{{ myTranslatedSignal() | lmnReplace: { param: myValue } }}
. - Or use
applyLabelReplacements
in TypeScript if needed.
- In your components, use
- Switch Language: When the user changes language, call
setLanguage
with the new language code. All translation Signals will update automatically.