Complete internationalization system with language switching, translation services, router guards for URL-based i18n, and reactive language management for Angular applications.
Core internationalization service interface for language management and text translation.
/**
* Core internationalization service interface
* Implement this interface to provide custom i18n functionality
*/
interface AlainI18NService {
/** Observable stream of language changes */
readonly change: Observable<string>;
/** Default language code */
defaultLang: string;
/** Current active language code */
currentLang: string;
/** Change active language and optionally provide language data */
use(lang: string, data?: Record<string, unknown>): void;
/** Get list of available languages */
getLangs(): any[];
/** Translate text using key path and optional parameters */
fanyi(path: string, params?: unknown | unknown[]): string;
}
/** Injection token for i18n service */
const ALAIN_I18N_TOKEN: InjectionToken<AlainI18NService>;Usage Examples:
import { Injectable, inject } from "@angular/core";
import { Observable, BehaviorSubject } from "rxjs";
import { AlainI18NService, ALAIN_I18N_TOKEN } from "@delon/theme";
// Custom i18n service implementation
@Injectable()
export class CustomI18nService implements AlainI18NService {
private _change = new BehaviorSubject<string>('en');
private _currentLang = 'en';
private _defaultLang = 'en';
private translations: Record<string, Record<string, string>> = {
en: {
'app.title': 'My Application',
'user.greeting': 'Hello, {{name}}!',
'nav.dashboard': 'Dashboard',
'nav.users': 'Users'
},
zh: {
'app.title': '我的应用程序',
'user.greeting': '你好,{{name}}!',
'nav.dashboard': '仪表板',
'nav.users': '用户'
}
};
get change(): Observable<string> {
return this._change.asObservable();
}
get defaultLang(): string {
return this._defaultLang;
}
set defaultLang(lang: string) {
this._defaultLang = lang;
}
get currentLang(): string {
return this._currentLang;
}
use(lang: string, data?: Record<string, unknown>): void {
if (data) {
this.translations[lang] = { ...this.translations[lang], ...data };
}
this._currentLang = lang;
this._change.next(lang);
}
getLangs(): any[] {
return Object.keys(this.translations);
}
fanyi(path: string, params?: unknown | unknown[]): string {
const langData = this.translations[this._currentLang] || this.translations[this._defaultLang];
let text = langData[path] || path;
// Handle parameter substitution
if (params) {
if (Array.isArray(params)) {
params.forEach((param, index) => {
text = text.replace(`{{${index}}}`, String(param));
});
} else if (typeof params === 'object') {
Object.keys(params).forEach(key => {
text = text.replace(`{{${key}}}`, String(params[key]));
});
}
}
return text;
}
}
// Component using i18n service
@Component({
selector: "app-header",
template: `
<header>
<h1>{{ i18n.fanyi('app.title') }}</h1>
<nav>
<a>{{ i18n.fanyi('nav.dashboard') }}</a>
<a>{{ i18n.fanyi('nav.users') }}</a>
</nav>
<div class="language-switcher">
<select (change)="switchLanguage($event)">
<option *ngFor="let lang of i18n.getLangs()" [value]="lang">
{{ lang }}
</option>
</select>
</div>
<div class="user-greeting">
{{ i18n.fanyi('user.greeting', { name: userName }) }}
</div>
</header>
`
})
export class AppHeaderComponent {
i18n = inject(ALAIN_I18N_TOKEN);
userName = 'John Doe';
constructor() {
// Listen to language changes
this.i18n.change.subscribe(lang => {
console.log('Language changed to:', lang);
// Update page title, direction, etc.
document.title = this.i18n.fanyi('app.title');
});
}
switchLanguage(event: Event) {
const lang = (event.target as HTMLSelectElement).value;
this.i18n.use(lang);
}
}Abstract base implementation providing common i18n functionality.
/**
* Abstract base implementation of AlainI18NService
* Extend this class for custom i18n implementations
*/
abstract class AlainI18nBaseService implements AlainI18NService {
abstract readonly change: Observable<string>;
abstract defaultLang: string;
abstract currentLang: string;
abstract use(lang: string, data?: Record<string, unknown>): void;
abstract getLangs(): any[];
abstract fanyi(path: string, params?: unknown | unknown[]): string;
}Usage Examples:
import { Injectable } from "@angular/core";
import { BehaviorSubject, Observable } from "rxjs";
import { AlainI18nBaseService } from "@delon/theme";
@Injectable()
export class MyI18nService extends AlainI18nBaseService {
private _change = new BehaviorSubject<string>('en');
private _currentLang = 'en';
defaultLang = 'en';
get change(): Observable<string> {
return this._change.asObservable();
}
get currentLang(): string {
return this._currentLang;
}
use(lang: string, data?: Record<string, unknown>): void {
// Custom implementation
this._currentLang = lang;
this._change.next(lang);
}
getLangs(): any[] {
return ['en', 'zh', 'es', 'fr'];
}
fanyi(path: string, params?: unknown | unknown[]): string {
// Custom translation logic
return `[${this._currentLang}] ${path}`;
}
}Simple fake implementation for testing and development.
/**
* Simple fake implementation of AlainI18NService
* Returns translation keys as-is, useful for development
*/
class AlainI18NServiceFake implements AlainI18NService {
readonly change: Observable<string>;
defaultLang: string;
currentLang: string;
use(lang: string, data?: Record<string, unknown>): void;
getLangs(): any[];
fanyi(path: string, params?: unknown | unknown[]): string;
}Usage Examples:
import { AlainI18NServiceFake } from "@delon/theme";
// Use fake service for testing
const fakeI18n = new AlainI18NServiceFake();
fakeI18n.defaultLang = 'en';
fakeI18n.currentLang = 'en';
// Returns the key as-is
console.log(fakeI18n.fanyi('app.title')); // Output: "app.title"
console.log(fakeI18n.fanyi('user.greeting', { name: 'John' })); // Output: "user.greeting"Pipe for translating text in templates using the i18n service.
/**
* I18n pipe for template translation
* Automatically updates when language changes
*/
class I18nPipe implements PipeTransform {
/**
* Transform translation key to localized text
* @param key - Translation key path
* @param params - Optional parameters for interpolation
* @returns Translated text
*/
transform(key: string, params?: unknown | unknown[]): string;
}Usage Examples:
// Template usage
@Component({
template: `
<h1>{{ 'app.title' | i18n }}</h1>
<p>{{ 'user.greeting' | i18n:{ name: userName } }}</p>
<button>{{ 'button.save' | i18n }}</button>
<!-- With array parameters -->
<span>{{ 'message.items' | i18n:[itemCount, totalCount] }}</span>
<!-- Nested in other pipes -->
<div [title]="'tooltip.help' | i18n">
{{ 'content.help' | i18n | uppercase }}
</div>
`
})
export class MyComponent {
userName = 'Alice';
itemCount = 5;
totalCount = 20;
}
// Component usage
import { Pipe, PipeTransform, inject } from "@angular/core";
import { ALAIN_I18N_TOKEN } from "@delon/theme";
@Pipe({ name: 'customI18n', pure: false })
export class CustomI18nPipe implements PipeTransform {
private i18n = inject(ALAIN_I18N_TOKEN);
transform(key: string, params?: any): string {
return this.i18n.fanyi(key, params);
}
}Router guards for URL-based internationalization with automatic language detection and redirection.
/**
* Router guard for URL-based internationalization
* Automatically redirects to language-prefixed routes
*/
function alainI18nCanActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean | UrlTree;
/**
* Router guard for child routes with i18n support
* Handles language switching in nested route structures
*/
function alainI18nCanActivateChild(
childRoute: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean | UrlTree;Usage Examples:
import { Routes } from "@angular/router";
import { alainI18nCanActivate, alainI18nCanActivateChild } from "@delon/theme";
// Routes with i18n guards
const routes: Routes = [
{
path: '',
canActivate: [alainI18nCanActivate],
children: [
{
path: 'dashboard',
component: DashboardComponent
},
{
path: 'users',
canActivateChild: [alainI18nCanActivateChild],
children: [
{ path: 'list', component: UserListComponent },
{ path: 'detail/:id', component: UserDetailComponent }
]
}
]
},
{
path: ':lang',
canActivate: [alainI18nCanActivate],
children: [
{ path: 'dashboard', component: DashboardComponent },
{ path: 'users', component: UsersComponent }
]
}
];
// Language-aware routing service
@Injectable()
export class I18nRoutingService {
constructor(
private router: Router,
private i18n: AlainI18NService
) {}
navigateWithLang(path: string, lang?: string) {
const currentLang = lang || this.i18n.currentLang;
this.router.navigate([`/${currentLang}${path}`]);
}
getCurrentPath(): string {
const url = this.router.url;
// Remove language prefix
return url.replace(/^\/[a-z]{2}(-[A-Z]{2})?/, '');
}
switchLanguage(newLang: string) {
const currentPath = this.getCurrentPath();
this.i18n.use(newLang);
this.navigateWithLang(currentPath, newLang);
}
}Configuration and provider setup for i18n services.
/**
* Provide i18n service in application
* Configure with provideAlain options
*/
interface AlainProvideOptions {
/** Custom i18n service class */
i18nClass?: Type<any>;
/** Default language configuration */
defaultLang?: AlainProvideLang;
}
interface AlainProvideLang {
/** Language abbreviation (e.g., 'en', 'zh') */
abbr: string;
/** Angular locale data */
ng: any;
/** ng-zorro-antd locale data */
zorro: any;
/** Date locale data */
date: any;
/** Delon locale data */
delon: any;
}Usage Examples:
import { bootstrapApplication } from "@angular/platform-browser";
import { provideAlain } from "@delon/theme";
import { CustomI18nService } from "./i18n.service";
import enUS from "@angular/common/locales/en";
import { en_US } from "ng-zorro-antd/i18n";
import { enUS as dateEnUS } from "date-fns/locale";
import { en_US as delonEnUS } from "@delon/theme/locale";
bootstrapApplication(AppComponent, {
providers: [
provideAlain({
i18nClass: CustomI18nService,
defaultLang: {
abbr: 'en',
ng: enUS,
zorro: en_US,
date: dateEnUS,
delon: delonEnUS
}
})
]
});
// Module-based setup
@NgModule({
providers: [
{
provide: ALAIN_I18N_TOKEN,
useClass: CustomI18nService,
multi: false
}
]
})
export class AppModule {}
// Custom provider factory
export function createI18nService(): AlainI18NService {
const service = new CustomI18nService();
service.defaultLang = 'en';
return service;
}
const I18N_PROVIDER: Provider = {
provide: ALAIN_I18N_TOKEN,
useFactory: createI18nService
};