@flusys/ng-localization
v4.1.1
Published
Localization module for multi-language support in FLUSYS Angular apps
Readme
@flusys/ng-localization
Database-driven internationalization (i18n) library for the FLUSYS Angular platform — multi-language support, RTL/LTR, TranslatePipe, language selector, and Angular APP_INITIALIZER bootstrap.
Table of Contents
- Overview
- Features
- Compatibility
- Installation
- Quick Start
- Module Registration
- TranslateService
- TranslatePipe
- Variable Interpolation
- Language Management
- Components
- Route Resolver
- RTL Support
- TRANSLATE_ADAPTER Token
- API Endpoints
- Configuration Reference
- Troubleshooting
- License
Overview
@flusys/ng-localization provides database-driven i18n for FLUSYS applications. Translations are stored in the backend (nestjs-localization) and loaded at app startup via APP_INITIALIZER. The TranslatePipe integrates with TranslateService through the TRANSLATE_ADAPTER injection token — keeping all other packages independent of ng-localization.
Unlike file-based i18n (ngx-translate), FLUSYS translations are managed in the database and can be updated at runtime without rebuilding.
Features
- ✅ Database-driven translations (updated without rebuild)
- ✅
TranslatePipewith signal reactivity — re-renders on language switch - ✅
{{variableName}}interpolation withmessageVariables - ✅ RTL/LTR document direction auto-switch
- ✅ APP_INITIALIZER bootstrap — translations loaded before app renders
- ✅ Route resolver for per-route language loading
- ✅ Language selector component for topbar
- ✅
provideLocalization()— registersTRANSLATE_ADAPTERandLAYOUT_LANGUAGE_SELECTOR - ✅ Missing key detection and fallback
Compatibility
| Package | Version | |---------|---------| | Angular | 21+ | | @flusys/ng-core | 4.x | | @flusys/ng-shared | 4.x |
Installation
npm install @flusys/ng-localization @flusys/ng-core @flusys/ng-sharedQuick Start
1. Enable Localization in Config
// environments/environment.ts
export const environment = {
services: {
localization: { enabled: true },
},
};2. Register Localization Providers
// app.config.ts
import { provideLocalization } from '@flusys/ng-localization';
export const appConfig: ApplicationConfig = {
providers: [
...provideLocalization(),
// Provides:
// - TRANSLATE_ADAPTER (used by TranslatePipe, ng-shared)
// - LAYOUT_LANGUAGE_SELECTOR (used by AppTopbar from ng-layout)
// - APP_INITIALIZER (loads translations before app renders)
],
};3. Use TranslatePipe in Templates
<!-- Basic translation -->
<span>{{ 'common.save' | translate }}</span>
<button>{{ 'common.cancel' | translate }}</button>
<h1>{{ 'dashboard.title' | translate }}</h1>
<!-- With parameters -->
<p>{{ 'pagination.showing' | translate: { from: 1, to: 10, total: 100 } }}</p>Module Registration
provideLocalization()
import { provideLocalization } from '@flusys/ng-localization';
providers: [
...provideLocalization(),
]Registers:
TRANSLATE_ADAPTER—TranslateServiceimplementation (used byTranslatePipefromng-shared)LAYOUT_LANGUAGE_SELECTOR—ILanguageSelectorProviderimplementation (used byAppTopbar)APP_INITIALIZER— loads translations for the default language before first render
TranslateService
The core service managing translations and language state:
import { TranslateService } from '@flusys/ng-localization';
@Component({ ... })
export class MyComponent {
private translate = inject(TranslateService);
// Signals
currentLanguage = this.translate.currentLanguage; // Signal<ILanguage>
availableLanguages = this.translate.languages; // Signal<ILanguage[]>
isLoading = this.translate.isLoading; // Signal<boolean>
// Methods
translate(key: string, params?: Record<string, string | number>): string {
return this.translate.get(key, params);
}
switchLanguage(languageCode: string): void {
this.translate.setLanguage(languageCode);
}
}TranslateService API:
| Member | Type | Description |
|--------|------|-------------|
| currentLanguage | Signal<ILanguage> | Active language object |
| languages | Signal<ILanguage[]> | All available languages |
| isLoading | Signal<boolean> | True when switching languages |
| get(key, params?) | string | Translate a key synchronously |
| setLanguage(code) | Observable<void> | Switch language (loads translations) |
| reloadTranslations() | Observable<void> | Force reload from backend |
ILanguage Model:
interface ILanguage {
id: string;
name: string; // "English"
nativeName: string; // "English" / "العربية"
code: string; // "en" / "ar"
isDefault: boolean;
isRtl: boolean;
isActive: boolean;
flagIcon?: string; // Country flag identifier
}TranslatePipe
The TranslatePipe from @flusys/ng-shared is powered by TRANSLATE_ADAPTER. It is signal-reactive — when the language changes, all pipes re-render automatically.
<!-- Basic key -->
{{ 'menu.dashboard' | translate }}
<!-- With interpolation parameters -->
{{ 'user.greeting' | translate: { name: userName() } }}
<!-- In attribute binding -->
<input [placeholder]="'form.search.placeholder' | translate" />
<!-- In event handler label -->
<button [title]="'action.delete.tooltip' | translate">
<i class="pi pi-trash"></i>
</button>Translation Key Convention:
module.component.elementExamples:
common.save
common.cancel
common.delete
auth.login.title
auth.login.email.label
dashboard.title
menu.products
pagination.showingVariable Interpolation
Use {{variableName}} in translation values stored in the database:
# In database:
Key: "pagination.showing"
Value: "Showing {{from}}-{{to}} of {{total}} results"
# In Arabic:
Key: "pagination.showing"
Value: "عرض {{from}}-{{to}} من {{total}} نتيجة"<!-- Angular template -->
{{ 'pagination.showing' | translate: { from: page * pageSize + 1, to: endItem, total: total() } }}Backend messageVariables field:
When the backend sends translation responses, it includes a messageVariables field with the variable names the translation expects:
{
"key": "pagination.showing",
"value": "Showing {{from}}-{{to}} of {{total}} results",
"messageVariables": ["from", "to", "total"]
}Language Management
Languages are managed via the ng-localization admin pages:
// app.routes.ts
import { LOCALIZATION_ROUTES } from '@flusys/ng-localization';
export const routes: Routes = [
{
path: 'localization',
loadChildren: () => LOCALIZATION_ROUTES,
},
];| Route | Description |
|-------|-------------|
| /localization/languages | Manage languages (add, enable, set RTL) |
| /localization/translations | Manage translation keys and values |
| /localization/translations/:module | Filter by module |
TranslateApiService for programmatic access:
import { TranslateApiService } from '@flusys/ng-localization';
@Injectable({ ... })
export class MyService {
private translateApi = inject(TranslateApiService);
getLanguages(): Observable<IListResponse<ILanguage>> {
return this.translateApi.getLanguages();
}
getTranslations(languageCode: string): Observable<Record<string, string>> {
return this.translateApi.getTranslations(languageCode);
}
}Components
LanguageSelectorComponent
Language switcher dropdown. Registered via LAYOUT_LANGUAGE_SELECTOR and rendered automatically in AppTopbar.
<!-- Rendered automatically in AppTopbar when provideLocalization() is called -->
<!-- Manual embedding: -->
<flusys-language-selector />Features:
- Shows current language flag + name
- Dropdown lists all active languages
- Switches language and reloads translations on selection
Route Resolver
Use the route resolver to ensure translations are loaded before a route renders:
import { localizationResolver } from '@flusys/ng-localization';
// app.routes.ts
export const routes: Routes = [
{
path: '',
resolve: { translations: localizationResolver },
children: [
// Routes that need translations pre-loaded
],
},
];The resolver calls TranslateService.reloadTranslations() if translations are stale or not yet loaded for the current language.
RTL Support
When a user switches to an RTL language (e.g., Arabic, Hebrew), TranslateService automatically updates the document direction:
// Internal behavior (automatic)
document.documentElement.dir = language.isRtl ? 'rtl' : 'ltr';
document.documentElement.lang = language.code;Tailwind CSS RTL classes (rtl:, ltr:) work automatically once the dir attribute is set.
PrimeNG also supports RTL via the dir attribute on the document root — no extra configuration needed.
TRANSLATE_ADAPTER Token
TRANSLATE_ADAPTER is defined in @flusys/ng-core. All packages that use TranslatePipe or need translation access inject this token — not TranslateService directly. This keeps them independent of ng-localization.
// Defined in ng-core
import { TRANSLATE_ADAPTER, ITranslateAdapter } from '@flusys/ng-core';
// ITranslateAdapter interface
interface ITranslateAdapter {
translate(key: string, params?: Record<string, string | number>): string;
currentLanguage: Signal<ILanguage>;
setLanguage(code: string): Observable<void>;
}
// ng-localization provides the implementation:
// provideLocalization() → { provide: TRANSLATE_ADAPTER, useClass: TranslateService }API Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | /localization/language/get-all | List all languages |
| POST | /localization/language/get/:id | Get language by ID |
| POST | /localization/language/insert | Add language |
| POST | /localization/language/update | Update language |
| POST | /localization/language/delete | Delete language |
| POST | /localization/translation/get-all | List translations |
| POST | /localization/translation/get-by-language | Get all keys for a language |
| POST | /localization/translation/insert | Add translation key |
| POST | /localization/translation/update | Update translation value |
| POST | /localization/translation/delete | Delete translation key |
| POST | /localization/translation/bulk-update | Atomic bulk update |
Configuration Reference
| Config Key | Type | Default | Description |
|------------|------|---------|-------------|
| services.localization.enabled | boolean | false | Enable localization module |
Default language is configured server-side in nestjs-localization via ILanguage.isDefault.
Troubleshooting
TranslatePipe shows raw key (e.g., common.save)
Possible causes:
provideLocalization()is missing fromapp.config.ts- The key doesn't exist in the database for the current language
- Translations failed to load — check network tab for
POST /localization/translation/get-by-language
APP_INITIALIZER delays app startup
Translations are loaded before the first render. For large translation sets, this adds startup time. Consider splitting translations by module and using the route resolver for lazy loading.
Language switch doesn't update all components
TranslatePipe is signal-reactive. If a component uses raw translate.get() (synchronous call, not pipe), it won't auto-update. Use {{ key | translate }} in templates instead.
RTL layout not applying
Ensure Tailwind CSS is configured for RTL classes. Add to tailwind.config.js:
module.exports = {
// Tailwind reads dir from document root automatically
// No extra config needed for rtl: classes
};No provider for TRANSLATE_ADAPTER
Add provideLocalization() to app.config.ts providers.
Missing translations after adding new keys
Call TranslateService.reloadTranslations() or navigate away and back (triggers the route resolver). In-memory translations are cached until the next load.
License
MIT © FLUSYS
