@ngx-tralgee-translator/ngx-translator
v1.3.0
Published
Angular SDK for Tralgee Translation Management — fetch, cache, ICU format, TranslatePipe, TranslateDirective.
Downloads
651
Readme
@ngx-tralgee-translator/ngx-translator
Angular SDK for Tralgee — fetch, cache, ICU format, TranslatePipe, TranslateDirective.
Supports Angular 12 through 21+ via three entry points, each optimised for the features available in that Angular era.
Quick-start: which entry point do I need?
| My Angular version | Entry point to import from | Integration style |
|--------------------|---------------------------|-------------------|
| 12 – 13 | …/legacy | NgModule + constructor DI + BehaviorSubject |
| 14 – 16 | …/compat | Standalone or NgModule + inject() + Observable |
| 17 – 21+ | (main package) | Standalone or NgModule + Signals ✨ |
npm install @ngx-tralgee-translator/ngx-translatorVariant A — Angular 12-13 (/legacy)
Entry point
import { ... } from '@ngx-tralgee-translator/ngx-translator/legacy';Setup — app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {
NgxTranslatorLegacyModule,
cookieLanguageStorage,
navigatorLanguageDetector,
} from '@ngx-tralgee-translator/ngx-translator/legacy';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
NgxTranslatorLegacyModule.forRoot({
apiUrl: 'https://your-tralgee-instance.com/api',
projectId: 'clxxxxxxxxxxxxxxxxx',
defaultLocale: 'en',
preloadLocales: ['fr'], // optional
languageStorage: cookieLanguageStorage('lang'), // optional
languageDetectors: [navigatorLanguageDetector()],// optional
}),
],
bootstrap: [AppComponent],
})
export class AppModule {}Setup — feature module
Import without forRoot() to get pipe + directive in a feature module:
import { NgModule } from '@angular/core';
import { NgxTranslatorLegacyModule } from '@ngx-tralgee-translator/ngx-translator/legacy';
@NgModule({
imports: [NgxTranslatorLegacyModule],
declarations: [ProductListComponent],
})
export class ProductsModule {}Component usage
No extra imports needed — the pipe and directive are exported by the module.
import { Component } from '@angular/core';
@Component({
selector: 'app-home',
template: `
<h1>{{ 'homepage.title' | translate }}</h1>
<p>{{ 'greeting' | translate: { name: 'Alice' } }}</p>
<span [translate]="'items.count'" [translateParams]="{ count: total }"></span>
`,
})
export class HomeComponent {
total = 42;
}Service injection
import { Component, OnInit } from '@angular/core';
import { TranslatorService } from '@ngx-tralgee-translator/ngx-translator/legacy';
@Component({ ... })
export class MyComponent implements OnInit {
constructor(private readonly translator: TranslatorService) {}
ngOnInit() {
// Synchronous
const title = this.translator.instant('homepage.title');
// Observable — re-emits on locale change
this.translator.translate('homepage.title').subscribe(t => (this.title = t));
// Switch language
this.translator.changeLanguage('fr');
}
}Note: This variant works with both RxJS 6 and RxJS 7 (the
use()method avoidslastValueFrom).
Variant B — Angular 14-16 (/compat)
Entry point
import { ... } from '@ngx-tralgee-translator/ngx-translator/compat';Two setup paths exist: standalone (recommended for Angular 14+) and NgModule.
Path B1 — Standalone app (app.config.ts)
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import {
provideTranslator,
cookieLanguageStorage,
navigatorLanguageDetector,
} from '@ngx-tralgee-translator/ngx-translator/compat';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(),
provideRouter(routes),
provideTranslator({
apiUrl: 'https://your-tralgee-instance.com/api',
projectId: 'clxxxxxxxxxxxxxxxxx',
defaultLocale: 'en',
preloadLocales: ['fr'],
languageStorage: cookieLanguageStorage('lang'),
languageDetectors: [navigatorLanguageDetector()],
}),
],
};main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig).catch(console.error);Standalone component
import { Component, inject } from '@angular/core';
import {
TranslatePipe,
TranslateDirective,
TranslatorService,
} from '@ngx-tralgee-translator/ngx-translator/compat';
@Component({
standalone: true,
imports: [TranslatePipe, TranslateDirective],
template: `
<h1>{{ 'homepage.title' | translate }}</h1>
<p>{{ 'greeting' | translate: { name: user.name } }}</p>
<span [translate]="'items.count'" [translateParams]="{ count: total }"></span>
<button (click)="switchLang('fr')">Français</button>
`,
})
export class HomeComponent {
private readonly translator = inject(TranslatorService);
user = { name: 'Alice' };
total = 42;
switchLang(lang: string) {
this.translator.changeLanguage(lang);
}
}Path B2 — NgModule app (app.module.ts)
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { NgxTranslatorCompatModule } from '@ngx-tralgee-translator/ngx-translator/compat';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule,
NgxTranslatorCompatModule.forRoot({
apiUrl: 'https://your-tralgee-instance.com/api',
projectId: 'clxxxxxxxxxxxxxxxxx',
defaultLocale: 'en',
}),
],
bootstrap: [AppComponent],
})
export class AppModule {}Variant C — Angular 17-21+ (main entry, recommended)
Entry point
import { ... } from '@ngx-tralgee-translator/ngx-translator';Uses Angular Signals (signal(), effect()) for zero-overhead reactivity.
Three sub-paths exist depending on your app architecture.
Path C1 — Standalone app (Angular 17+, recommended)
app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import {
provideTranslator,
cookieLanguageStorage,
navigatorLanguageDetector,
} from '@ngx-tralgee-translator/ngx-translator';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(),
provideTranslator({
apiUrl: 'https://your-tralgee-instance.com/api',
projectId: 'clxxxxxxxxxxxxxxxxx',
defaultLocale: 'en',
preloadLocales: ['fr', 'de'],
languageStorage: cookieLanguageStorage('preferred_lang'),
languageDetectors: [navigatorLanguageDetector()],
}),
],
};Standalone component
import { Component, inject, computed } from '@angular/core';
import {
TranslatePipe,
TranslateDirective,
TranslatorService,
} from '@ngx-tralgee-translator/ngx-translator';
@Component({
standalone: true,
imports: [TranslatePipe, TranslateDirective],
template: `
<h1>{{ 'homepage.title' | translate }}</h1>
<p>{{ 'greeting' | translate: { name: 'Alice' } }}</p>
<!-- Signal-driven computed property -->
<title>{{ pageTitle() }}</title>
<!-- Show loading state -->
@if (translator.loading()) {
<p>Loading translations…</p>
}
`,
})
export class HomeComponent {
readonly translator = inject(TranslatorService);
// Recomputes automatically when locale or translations change
readonly pageTitle = computed(() => this.translator.instant('page.title'));
}Path C2 — NgModule app (Angular 17+)
app.module.ts
import { APP_INITIALIZER, inject, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import {
NgxTranslatorModule,
TranslatorService,
TRANSLATOR_CONFIG,
} from '@ngx-tralgee-translator/ngx-translator';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule, NgxTranslatorModule],
providers: [
{
provide: TRANSLATOR_CONFIG,
useValue: {
apiUrl: 'https://your-tralgee-instance.com/api',
projectId: 'clxxxxxxxxxxxxxxxxx',
defaultLocale: 'en',
},
},
{
provide: APP_INITIALIZER,
useFactory: () => {
const service = inject(TranslatorService);
return () => service.use(service.language);
},
multi: true,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}Path C3 — Service-only (libraries, SSR, programmatic)
For use inside a library, a resolver, or server-side rendering where you don't need the pipe/directive.
import { Component, inject } from '@angular/core';
import { TranslatorService } from '@ngx-tralgee-translator/ngx-translator';
@Component({ ... })
export class MyComponent {
private readonly translator = inject(TranslatorService);
// Synchronous
getTitle() {
return this.translator.instant('page.title');
}
// Observable stream
getTitle$() {
return this.translator.translate('page.title');
}
// Signal (Angular 17+)
getLocale() {
return this.translator.locale(); // Signal<string>
}
}Configuration reference
All three variants share the same TranslatorConfig interface:
interface TranslatorConfig {
/** Base URL of the Tralgee API — no trailing slash */
apiUrl: string;
/** Project ID from the Tralgee dashboard */
projectId: string;
/** Fallback locale when all detectors return undefined */
defaultLocale: string;
/** Extra locales to fetch in background after init */
preloadLocales?: string[];
/**
* Ordered chain of locale detectors.
* Defaults to [navigatorLanguageDetector()].
*/
languageDetectors?: LanguageDetectorMiddleware[];
/**
* Locale persistence layer.
* Defaults to cookieLanguageStorage('preferred_language').
* Set to null to disable persistence.
*/
languageStorage?: LanguageStorageMiddleware | null;
}Locale resolution order
languageStorage.getLanguage()— stored cookie / localStoragelanguageDetectors[0..n]— first detector returning a value winsconfig.defaultLocale— hard-coded fallback
TranslatorService API
All three variants expose the same public API.
Properties
| Property | Modern (17+) | Compat (14-16) | Legacy (12-13) | Description |
|----------|-------------|---------------|---------------|-------------|
| language | string | string | string | Active locale getter |
| languageAsync | Observable<string> | Observable<string> | Observable<string> | Stream of active locale |
| locale | Signal<string> | (N/A) | (N/A) | Signal — modern only |
| loading | Signal<boolean> | (N/A) | (N/A) | Signal — modern only |
| ready | Signal<boolean> | (N/A) | (N/A) | Signal — modern only |
| snapshot | ReadonlySignal<…> | (N/A) | (N/A) | Translations signal — modern only |
Methods (all variants)
// Synchronous translation
instant(key: string): string
instant(key: string, defaultValue: string): string
instant(key: string, params: TranslateParams): string
instant(key: string, defaultValue: string, params: TranslateParams): string
t(key, params?) // shorthand alias for instant()
// Observable translation — re-emits on locale change
translate(key: string): Observable<string>
translate(key: string, defaultValue: string): Observable<string>
translate(key: string, params: TranslateParams): Observable<string>
// Language switch
use(locale: string): Promise<void>
changeLanguage(locale: string): Promise<void> // alias
// Available languages
getLanguages(): Observable<Language[]>
getLanguagesAsync(): Promise<Language[]>
// Events
on(event: TralgeeEvent): Observable<EventType[event]>
subscribe(event, handler): { unsubscribe(): void }Events
| Event | Payload | When |
|-------|---------|------|
| language | { type: 'language', value: string } | Locale changed |
| loading | boolean | Initial locale fetch start/end |
| fetching | boolean | Any locale fetch start/end |
| initialLoad | void | First locale loaded |
| error | Error | Fetch failed |
| update | void | Translations table updated |
Templates
| translate pipe
{{ 'homepage.title' | translate }}
{{ 'greeting' | translate: { name: 'Alice' } }}
{{ 'items.count' | translate: { count: total } }}[translate] directive
<span translate="homepage.title"></span>
<button [translate]="btnKey" [translateParams]="{ count: total }"></button>ICU Message Format
Built-in parser — no external dependency needed.
Simple variable
"Hello, {name}!"Plural
"{count, plural, =0 {No items} one {# item} other {# items}}"Supported cases: =0, =1, =N, zero, one, two, few, many, other.
Select
"{gender, select, male {his profile} female {her profile} other {their profile}}"Nested ICU
"{count, plural, =0 {No {type} found} one {One {type}} other {# {type}s}}"Language Detection & Storage
Built-in
import {
cookieLanguageStorage,
navigatorLanguageDetector,
} from '@ngx-tralgee-translator/ngx-translator'; // or /legacy or /compatCustom detector (URL param example)
const urlParamDetector: LanguageDetectorMiddleware = {
detect() {
return new URLSearchParams(window.location.search).get('lang') ?? undefined;
},
};Custom storage (localStorage example)
const localStorageMiddleware: LanguageStorageMiddleware = {
getLanguage: () => localStorage.getItem('locale') ?? undefined,
setLanguage: (lang) => localStorage.setItem('locale', lang),
};Disable persistence
languageStorage: nullLanguage Switcher
import { Component, inject } from '@angular/core';
import { TranslatorService, Language } from '@ngx-tralgee-translator/ngx-translator'; // or /compat
@Component({
standalone: true,
template: `
<select (change)="switch($event)">
@for (lang of languages; track lang.code) {
<option [value]="lang.code" [selected]="lang.code === translator.language">
{{ lang.flagEmoji }} {{ lang.name }}
</option>
}
</select>
`,
})
export class LanguageSwitcherComponent {
readonly translator = inject(TranslatorService);
languages: Language[] = [];
ngOnInit() {
this.translator.getLanguages().subscribe(l => (this.languages = l));
}
switch(event: Event) {
this.translator.changeLanguage((event.target as HTMLSelectElement).value);
}
}SSR / Angular Universal
Disable browser-specific middleware on the server:
// server.config.ts
export const serverConfig = mergeApplicationConfig(appConfig, {
providers: [
provideTranslator({
apiUrl: 'https://api.example.com/api',
projectId: 'clxxxxxxxxxxxxxxxxx',
defaultLocale: 'en',
languageStorage: null, // no document.cookie on server
languageDetectors: [], // no navigator on server
}),
],
});Migration from Tolgee (@tolgee/ngx)
| Tolgee | Tralgee | Notes |
|--------|---------|-------|
| NgxTolgeeModule | NgxTranslatorLegacyModule / NgxTranslatorCompatModule / NgxTranslatorModule | Same role |
| TranslateService | TranslatorService | Also re-exported as TranslateService alias |
| t(), instant(), changeLanguage() | Identical | No changes needed |
| lang.tag | lang.code | tag kept as deprecated alias |
| lang.base | lang.isBase | base kept as deprecated alias |
| provideTolgee({ defaultLanguage }) | provideTranslator({ defaultLocale }) | Renamed key |
| provideTolgee({ staticData }) | provideTranslator({ projectId }) | Replaced by server-side fetch |
Troubleshooting
"NullInjectorError: No provider for TRANSLATOR_CONFIG"
You forgot to call forRoot(config) on the module import, or forgot provideTranslator(config) in standalone setup.
Translations not loading / 404 on API call
- Check
apiUrlhas no trailing slash. - The SDK calls
GET {apiUrl}/public/{projectId}/translations/{locale}. - Make sure
provideHttpClient()(standalone) orHttpClientModule(NgModule) is in your providers.
Pipe not re-rendering after changeLanguage()
- Modern variant (17+):
TranslatePipereads thesnapshot()signal — re-renders automatically. Check thatTranslatePipeis inimports. - Compat/Legacy variants: The pipe subscribes to the
'update'event and callsmarkForCheck(). Make sure you are usingChangeDetectionStrategy.OnPushcorrectly — the pipe handles it internally.
Angular 12 with RxJS 6
The /legacy entry point uses an internal lastValue() helper that works with both RxJS 6 and RxJS 7. No changes needed in your project.
