npm package: @flusys/ng-localization
Description: Localization module for multi-language support in FLUSYS Angular apps
Latest version: 4.1.1

# @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.

[![npm version](https://img.shields.io/npm/v/@flusys/ng-localization.svg)](https://www.npmjs.com/package/@flusys/ng-localization)
[![Angular](https://img.shields.io/badge/Angular-21-red.svg)](https://angular.io)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)

---

## Table of Contents

- [Overview](#overview)
- [Features](#features)
- [Compatibility](#compatibility)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Module Registration](#module-registration)
- [TranslateService](#translateservice)
- [TranslatePipe](#translatepipe)
- [Variable Interpolation](#variable-interpolation)
- [Language Management](#language-management)
- [Components](#components)
  - [LanguageSelectorComponent](#languageselectorcomponent)
- [Route Resolver](#route-resolver)
- [RTL Support](#rtl-support)
- [TRANSLATE_ADAPTER Token](#translate_adapter-token)
- [API Endpoints](#api-endpoints)
- [Configuration Reference](#configuration-reference)
- [Troubleshooting](#troubleshooting)
- [License](#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)
- ✅ `TranslatePipe` with signal reactivity — re-renders on language switch
- ✅ `{{variableName}}` interpolation with `messageVariables`
- ✅ 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()` — registers `TRANSLATE_ADAPTER` and `LAYOUT_LANGUAGE_SELECTOR`
- ✅ Missing key detection and fallback

---

## Compatibility

| Package | Version |
|---------|---------|
| Angular | 21+ |
| @flusys/ng-core | 4.x |
| @flusys/ng-shared | 4.x |

---

## Installation

```bash
npm install @flusys/ng-localization @flusys/ng-core @flusys/ng-shared
```

---

## Quick Start

### 1. Enable Localization in Config

```typescript
// environments/environment.ts
export const environment = {
  services: {
    localization: { enabled: true },
  },
};
```

### 2. Register Localization Providers

```typescript
// 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

```html
<!-- 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()`

```typescript
import { provideLocalization } from '@flusys/ng-localization';

providers: [
  ...provideLocalization(),
]
```

Registers:
- `TRANSLATE_ADAPTER` — `TranslateService` implementation (used by `TranslatePipe` from `ng-shared`)
- `LAYOUT_LANGUAGE_SELECTOR` — `ILanguageSelectorProvider` implementation (used by `AppTopbar`)
- `APP_INITIALIZER` — loads translations for the default language before first render

---

## TranslateService

The core service managing translations and language state:

```typescript
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:**

```typescript
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.

```html
<!-- 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.element
```

Examples:
```
common.save
common.cancel
common.delete
auth.login.title
auth.login.email.label
dashboard.title
menu.products
pagination.showing
```

---

## Variable 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}} نتيجة"
```

```html
<!-- 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:

```json
{
  "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:

```typescript
// 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:

```typescript
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`.

```html
<!-- 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:

```typescript
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:

```typescript
// 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`.

```typescript
// 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:
1. `provideLocalization()` is missing from `app.config.ts`
2. The key doesn't exist in the database for the current language
3. 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`:

```javascript
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