translate-angular
v0.1.0
Published
Atomic, modular translation library for Angular standalone applications and components.
Maintainers
Readme
translate-angular
An atomic, modular, and reactive translation library for Angular standalone applications and components.
Features
- ⚡ Atomic & Modular Scoping: Define micro-dictionaries right next to your components and routes instead of maintaining one massive global JSON file.
- 🧩 Hierarchical Auto-Merging: Child and component-level translations automatically merge with parent and root scopes up the injector tree without overwriting sibling keys.
- 💉 Functional Injection (
injectTranslate): Easily translate keys programmatically in component TypeScript code with automatic access to locally provided translations. - 🌍 Angular Formatting Pipes Integration (
idLocale): Dynamically pass active locale codes to Angular's built-in pipes (date,currency,number,percent) so formatted numbers and dates update immediately on language switch. - 🌐 Intl-Powered Language Labels: Pass simple language codes (
['en', 'da', 'es']) and let the browser'sIntl.DisplayNamesautomatically generate localized labels (e.g. "English", "Dansk", "Español"). - 🔄 Reactive Forms & RxJS: Built-in
userLanguageControl(FormControl<string>),userLanguagegetter,setLanguage()helper, anduserLanguage$stream for seamless reactivity. - 🛠 Translate Pipe: Simple, performant template interpolation with dynamic variable replacement (
{{ varName }}). - 📦 Pre-built UI Component: Includes
<translate-language-picker-select>for instant drop-in language switching. - 🚀 100% Standalone: Pure standalone architecture with zero
NgModuleboilerplate.
Installation
npm install translate-angular1. Root Configuration (provideTranslateInitializer)
Configure your application defaults, available languages, and initial translations at bootstrap in app.config.ts.
When fetchTranslations is specified, provideTranslateInitializer automatically wires an APP_INITIALIZER to hold application bootstrap until the translation file has loaded, preventing raw translation keys and layout shifts from flickering on the initial render:
import { ApplicationConfig } from '@angular/core';
import { provideTranslateInitializer } from 'translate-angular';
import { globalTranslations } from './global.translations';
export const appConfig: ApplicationConfig = {
providers: [
provideTranslateInitializer({
defaultLanguage: 'en',
// Pass simple language codes — labels are automatically generated using browser Intl APIs!
languages: ['en', 'da', 'de', 'es'],
// Or provide custom labels where needed:
// languages: [
// { value: 'en', label: 'English' },
// { value: 'da', label: 'Dansk' },
// ],
// Optional: global base translations
translations: globalTranslations,
// Optional: automatically awaited before app renders via APP_INITIALIZER
fetchTranslations: 'assets/i18n/app.json',
}),
],
};[!NOTE]
provideTranslateRootis also exported as an alias for backwards compatibility.
---
## 2. Defining Translations (`TranslationMap`)
Translations are defined as key-value objects mapped by language code:
```typescript
// sign-in.translations.ts
import { TranslationMap } from 'translate-angular';
export const signInTranslations: TranslationMap = {
title: {
en: 'Welcome back!',
da: 'Velkommen tilbage!',
},
submitButton: {
en: 'Sign In',
da: 'Log ind',
},
welcomeUser: {
en: 'Hello, {{ name }}!',
da: 'Hej, {{ name }}!',
},
errorMessage: {
en: 'Invalid credentials provided.',
da: 'Ugyldige loginoplysninger.',
},
};3. Scoped Component Translation
Attach translations directly to a component using addTranslations(...). You can translate keys both in your HTML template using TranslatePipe and in your TypeScript logic using injectTranslate():
import { Component } from '@angular/core';
import { addTranslations, injectTranslate, TranslatePipe } from 'translate-angular';
import { signInTranslations } from './sign-in.translations';
@Component({
selector: 'app-sign-in',
imports: [TranslatePipe],
providers: [addTranslations(signInTranslations)],
template: `
<h2>{{ 'title' | translate }}</h2>
<!-- Interpolation with dynamic variables: {{ name }} -->
<p>{{ 'welcomeUser' | translate: { name: userName } }}</p>
<button (click)="onSubmit()">{{ 'submitButton' | translate }}</button>
`,
})
export class SignInComponent {
// injectTranslate() automatically captures the component's injector & provided translations!
private translate = injectTranslate();
userName = 'Thomas';
onSubmit() {
// In TypeScript code — no need to re-pass signInTranslations:
const error = this.translate('errorMessage');
const greeting = this.translate('welcomeUser', { name: this.userName });
console.log(greeting, error);
}
}[!TIP] Hierarchical Merging: Translations provided at the component level do not overwrite global or parent translations. They are merged into the scope tree, allowing local overrides and additions.
4. Synchronizing Angular Formatting Pipes (idLocale Pipe)
Angular provides powerful built-in formatting pipes such as DatePipe, CurrencyPipe, DecimalPipe, and PercentPipe. All of them accept a locale?: string parameter, but Angular does not natively re-evaluate them when the user changes language at runtime.
The idLocale pipe solves this problem. It listens to the active language in TranslateService and dynamically converts language codes into standard BCP 47 locale identifiers (e.g. 'da' -> 'da-DK', 'en' -> 'en-US').
By piping a dummy value through ('' | idLocale) into the locale parameter of Angular's pipes, your formatted dates, numbers, and currencies update automatically whenever the user switches language!
Template Examples:
import { Component } from '@angular/core';
import { CurrencyPipe, DatePipe, DecimalPipe, PercentPipe } from '@angular/common';
import { IdLocalePipe } from 'translate-angular';
@Component({
selector: 'app-checkout-summary',
imports: [DatePipe, CurrencyPipe, DecimalPipe, PercentPipe, IdLocalePipe],
template: `
<!-- DatePipe: formatted according to the active language -->
<p>Order Date: {{ orderDate | date: 'fullDate' : '' : ('' | idLocale) }}</p>
<!-- CurrencyPipe: dynamic currency formatting and symbols -->
<p>Total: {{ totalAmount | currency: 'EUR' : 'symbol' : '1.2-2' : ('' | idLocale) }}</p>
<!-- DecimalPipe: comma/dot separators automatically adapt -->
<p>Weight: {{ weightKg | number: '1.2-2' : ('' | idLocale) }} kg</p>
<!-- PercentPipe: percentage formatting according to active locale -->
<p>Discount: {{ discountRate | percent: '1.1-2' : ('' | idLocale) }}</p>
`,
})
export class CheckoutSummaryComponent {
orderDate = new Date();
totalAmount = 1499.5;
weightKg = 1250.75;
discountRate = 0.15;
}How idLocale Works:
- When the active language is
'da',('' | idLocale)emits'da-DK'. - When the active language switches to
'en',('' | idLocale)emits'en-US'. - Because
IdLocalePipeis an impure pipe listening touserLanguage$, it automatically marks the view for check whenever the language changes, re-rendering Angular's formatting pipes instantly.
5. Reactive Language Control (TranslateService)
TranslateService provides full programmatic and reactive control over application languages:
import { Component, inject } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { TranslateService } from 'translate-angular';
@Component({
selector: 'app-custom-language-switcher',
imports: [ReactiveFormsModule],
template: `
<!-- Direct two-way binding using the built-in FormControl -->
<select [formControl]="translateService.userLanguageControl">
@for (option of translateService.options; track option.value) {
<option [value]="option.value">
{{ option.label }}
</option>
}
</select>
<p>Current language: {{ translateService.userLanguage }}</p>
`,
})
export class CustomLanguageSwitcherComponent {
protected readonly translateService = inject(TranslateService);
setDanish() {
// Switch language programmatically with setLanguage()
this.translateService.setLanguage('da');
// Equivalent to: this.translateService.userLanguageControl.setValue('da');
}
checkCurrentLanguage() {
// Synchronously read active language code
console.log('Active language:', this.translateService.userLanguage);
}
observeLanguage() {
// React to language changes via RxJS Observable
this.translateService.userLanguage$.subscribe((lang) => {
console.log('Language changed to:', lang);
});
}
}Key Service Members:
userLanguage: Synchronous getter returning the active language code (string), e.g.'en'.setLanguage(lang: string): Helper method to change the active language programmatically.userLanguageControl: Non-nullableFormControl<string>initialized withdefaultLanguage.userLanguage$: ReplayedObservable<string>emitting the current language and all subsequent changes.languageOptions$: ReplayedObservable<LanguageOption[]>emitting the resolved language choices for dropdowns.languageOptions: Synchronous getter returning the current array ofLanguageOption[].options: Synchronous getter forreadonly LanguageOption[].setOptions(options: LanguageOption[]): Dynamically updates the available language options.translate(key, translation, vars?, language?): Synchronous translation method.translate$(key, injector?, vars?, language?): Observable translation lookup supporting reactive subscriptions.
6. Pre-built Drop-in Component (LanguagePickerSelect)
If you want a ready-made language dropdown out of the box, import LanguagePickerSelect:
import { Component } from '@angular/core';
import { LanguagePickerSelect } from 'translate-angular';
@Component({
selector: 'app-header',
imports: [LanguagePickerSelect],
template: `
<header>
<h1>My App</h1>
<translate-language-picker-select />
</header>
`,
})
export class HeaderComponent {}(Note: <atomic-language-picker-select /> is also supported as an alias).
7. Loading External / Remote Translations (fetchTranslations)
To load remote translation JSON files dynamically, use fetchTranslations(url).
Automatic Promise Caching
All fetched translations are automatically cached by URL (accepting string, URL, or standard Request objects). In-flight promises and completed responses are shared across components, routes, and subscriptions. If the user navigates away and returns to a lazy route, the library reuses the cached translations instantly with zero duplicate network requests:
import { Component } from '@angular/core';
import { fetchTranslations, TranslatePipe } from 'translate-angular';
@Component({
selector: 'app-lazy-feature',
imports: [TranslatePipe],
providers: [
// Automatically cached by URL — route re-visits will not re-fetch from network
fetchTranslations('https://api.example.com/translations/feature.json'),
],
template: ` <p>{{ 'featureTitle' | translate }}</p> `,
})
export class LazyFeatureComponent {}API Summary
| Export | Type | Description |
| ------------------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------- |
| provideTranslateInitializer(config?) | Function | Configures root settings and initializes translations via APP_INITIALIZER before bootstrap. |
| provideTranslateRoot(config?) | Function | Alias for provideTranslateInitializer. |
| addTranslations(map) | Function | Scoped provider that merges micro-dictionaries into the current injection tree. |
| fetchTranslations(url) | Function | Scoped provider that fetches translations from a URL with automatic in-memory Promise caching. |
| injectTranslate(injector?) | Function | Functional injection helper returning a translate function bound to the component's injector. |
| TranslatePipe (translate) | Pipe | Translates keys in templates with variable substitution ({{ varName }}). |
| IdLocalePipe (idLocale) | Pipe | Emits active BCP 47 locale string to synchronize Angular pipes (date, currency, number). |
| TranslateService | Service | Provides userLanguage, setLanguage(), userLanguageControl, languageOptions$, translate(), etc. |
| LanguagePickerSelect | Component | Pre-styled language dropdown selector (<translate-language-picker-select>). |
| TranslationMap | Interface | Type definition for translation dictionary structures. |
| TranslateConfig | Interface | Configuration options passed to provideTranslateInitializer. |
| LanguageOption | Interface | Represents an option in the language dropdown ({ value, label }). |
License
MIT
