@skhalidq/ngx-i18n
v1.1.6
Published
A type-safe Angular internationalization library for translations, interpolation, pluralization, and lazy loading.
Maintainers
Readme
@skhalidq/ngx-i18n
A lightweight, type-safe Angular internationalization library for typed translations, interpolation, pluralization, language switching, persistence, fallback handling, and optional lazy loading.
Why use this library?
@skhalidq/ngx-i18n is designed for Angular apps that need typed translation keys, easy language switching, clean fallback behavior, and optional lazy loading. It keeps translation logic simple in both templates and TypeScript, while still supporting advanced cases like interpolation, pluralization, and per-language loading.
License
This package is distributed under a proprietary license. You may use it in your own application or product, but you may not copy, redistribute, modify, resell, or create a derivative library from it.
See the LICENSE file for full terms.
Installation
npm install @skhalidq/ngx-i18nFeatures
- Type-safe translation keys through TypeScript interfaces
- Template translations with the
translatepipe - TypeScript translations with the standalone
translate()helper - Interpolation with
{{ variable }}placeholders - Pluralization with
zero,one, andotherforms - Language switching with reactive updates
- Automatic persistence to
localStorage - Fallback language support
- Optional lazy loading at language, namespace, or string level
- Support for
enGB,en-GB,en_GB, and custom formats
Quick start
1. Define your translation shape
// src/app/i18n/translation.ts
export interface Translation {
common: {
welcome: string;
goodbye: string;
};
navigation: {
home: string;
about: string;
};
}2. Create language objects
// src/app/i18n/enGB.ts
import { Translation } from './translation';
export const enGB: Translation = {
common: {
welcome: 'Welcome',
goodbye: 'Goodbye'
},
navigation: {
home: 'Home',
about: 'About'
}
};// src/app/i18n/esES.ts
import { Translation } from './translation';
export const esES: Translation = {
common: {
welcome: 'Bienvenido',
goodbye: 'Adiós'
},
navigation: {
home: 'Inicio',
about: 'Acerca de'
}
};3. Configure the library
Module-based Angular application
// src/app/app-i18n-module.ts
import { NgModule } from '@angular/core';
import { I18nModule } from '@skhalidq/ngx-i18n';
import type { I18nConfig } from '@skhalidq/ngx-i18n';
import { Translation } from './i18n/translation';
import { enGB } from './i18n/enGB';
import { esES } from './i18n/esES';
const i18nConfig: I18nConfig<Translation> = {
languages: [
{ code: 'enGB', name: 'English (GB)', translations: enGB },
{ code: 'esES', name: 'Español', translations: esES }
],
fallbackLanguage: 'enGB',
initialLanguage: 'enGB'
};
@NgModule({
imports: [I18nModule.forRoot<Translation>(i18nConfig)],
exports: [I18nModule]
})
export class AppI18nModule {}// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { AppI18nModule } from './app-i18n-module';
@NgModule({
imports: [BrowserModule, AppI18nModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}Standalone application
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { importProvidersFrom } from '@angular/core';
import { I18nModule } from '@skhalidq/ngx-i18n';
import { AppComponent } from './app/app.component';
import { Translation } from './app/i18n/translation';
import { enGB } from './app/i18n/enGB';
import { esES } from './app/i18n/esES';
const i18nConfig: I18nConfig<Translation> = {
languages: [
{ code: 'enGB', name: 'English (GB)', translations: enGB },
{ code: 'esES', name: 'Español', translations: esES }
],
fallbackLanguage: 'enGB',
initialLanguage: 'enGB'
};
bootstrapApplication(AppComponent, {
providers: [importProvidersFrom(I18nModule.forRoot<Translation>(i18nConfig))]
});Usage
In templates
<h1>{{ 'common.welcome' | translate }}</h1>
<a>{{ 'navigation.home' | translate }}</a>With interpolation
// enGB.ts
export const enGB = {
greeting: 'Hello, {{name}}!'
};<p>{{ 'greeting' | translate:{ name: 'John' } }}</p>With pluralization
export const enGB = {
items: {
zero: 'No items',
one: '1 item',
other: '{{count}} items'
}
};<p>{{ 'items' | translate:{ count: 5 } }}</p>In TypeScript code
import { translate } from '@skhalidq/ngx-i18n';
const title = translate('common.welcome');
const greeting = translate('greeting', { name: 'John' });
const itemSummary = translate('items', { count: 5 });Switching languages
import { I18nService } from '@skhalidq/ngx-i18n';
class AppComponent {
constructor(private i18n: I18nService<Translation>) {}
switchLanguage(code: string): void {
this.i18n.setLanguage(code);
}
}You can also subscribe to currentLanguage$ if you need to react to language changes anywhere in the app.
Persistence and fallback
The library persists the selected language in localStorage by default.
Configuration options:
const i18nConfig: I18nConfig<Translation> = {
languages: [
{ code: 'enGB', name: 'English (GB)', translations: enGB },
{ code: 'esES', name: 'Español', translations: esES }
],
fallbackLanguage: 'enGB',
initialLanguage: 'enGB',
persistLanguage: true,
storageKey: 'my-app-language'
};Notes:
persistLanguagedefaults totruestorageKeydefaults to@skhalidq/ngx-i18n-language- If a translation is missing in the current language, the library falls back to
fallbackLanguage - If the key is still missing, the original key is returned
Fallback example
const i18nConfig: I18nConfig<Translation> = {
languages: [
{ code: 'enGB', name: 'English (GB)', translations: enGB },
{ code: 'esES', name: 'Español', translations: esES }
],
fallbackLanguage: 'enGB'
};
// If the current language is 'esES' and a key is missing, the library will try 'enGB'.
// If 'enGB' also has no match, it returns the original key.Lazy loading
Lazy loading is enabled through lazyLoading: true and can be configured with LazyLoadingMode.
import { LazyLoadingMode } from '@skhalidq/ngx-i18n';
const i18nConfig: I18nConfig<Translation> = {
languages: [
{
code: 'enGB',
name: 'English (GB)',
loader: () => import('./i18n/enGB').then((m) => m.enGB)
},
{
code: 'esES',
name: 'Español',
loader: () => import('./i18n/esES').then((m) => m.esES)
}
],
fallbackLanguage: 'enGB',
lazyLoading: true,
lazyLoadingMode: LazyLoadingMode.Language
};Supported modes:
LazyLoadingMode.Language: load full language packsLazyLoadingMode.Namespace: load namespaces on demandLazyLoadingMode.String: load individual strings on demandLazyLoadingMode.None: manual load control only
Language code formats
The library supports validating language codes with LanguageCodeFormat.
import { LanguageCodeFormat } from '@skhalidq/ngx-i18n';
const i18nConfig: I18nConfig<Translation> = {
languages: [
{ code: 'enGB', name: 'English (GB)', translations: enGB },
{ code: 'es-ES', name: 'Español', translations: esES }
],
fallbackLanguage: 'enGB',
codeFormat: LanguageCodeFormat.CUSTOM
};Supported formats:
ISO:enGB,esES,jaJPIETF:en-GB,es-ES,ja-JPPOSIX:en_GB,es_ES,ja_JPCUSTOM: any format you prefer
API summary
Main exports
| Export | Purpose |
| --- | --- |
| I18nModule | Angular module used to register the library and configure it via forRoot(). |
| I18nService | Core runtime service for changing languages, translating keys, and loading lazy content. |
| TranslatePipe | Angular pipe for translating keys directly in templates. |
| translate() | Standalone helper for translating keys from TypeScript code. |
| I18nConfig | Configuration interface for the library. |
| LanguageCodeFormat | Enum that defines how language codes are validated. |
| LazyLoadingMode | Enum that defines the lazy-loading strategy. |
| TranslationKey | Utility type for strongly typed translation keys. |
Core service methods
| Method | Parameters | Return type | Description |
| --- | --- | --- | --- |
| setLanguage(code) | code: string — language code such as enGB or esES | Promise<void> | Switches the active language and updates the app. |
| translate(key, params) | key: string, params?: Record<string, unknown> — for interpolation and pluralization, e.g. { name: 'Ada', count: 2 } | string | Resolves a translation key and applies interpolation or plural forms when present. |
| getLanguage() | None | LanguageInfo | Returns the current language code and display name. |
| getAvailableLanguages() | None | string[] | Returns the configured language codes. |
| loadLanguage(code) | code: string — language code to load | Promise<void> | Loads a language lazily when lazyLoading is enabled. |
| isLanguageLoaded(code) | code: string | boolean | Checks whether a language has been loaded. |
| isLanguageLoading(code) | code: string | boolean | Checks whether a language is currently loading. |
| loadNamespace(path) | path: string — namespace path such as common.home | void | Loads a namespace on demand in namespace/string lazy-loading modes. |
| loadNamespaces(paths) | paths: string[] | void | Loads multiple namespaces in one call. |
| isNamespaceLoaded(path) | path: string | boolean | Checks whether a namespace is already loaded. |
| loadString(path) | path: string — translation key such as common.welcome | void | Loads a single translation string on demand. |
| loadStrings(paths) | paths: string[] | void | Loads multiple translation strings in one call. |
| isStringLoaded(path) | path: string | boolean | Checks whether a string is already loaded. |
| getLoadingProgress() | None | Observable<LoadingProgress> | Returns an observable stream of loading progress. |
| getLoadingProgressSnapshot() | None | LoadingProgress | Returns the current loading progress snapshot synchronously. |
I18nConfig<T> properties
| Property | Type | Description |
| --- | --- | --- |
| languages | I18nLanguage<T>[] | Array of configured languages. |
| fallbackLanguage | string | Fallback language used when a key is missing in the current language. |
| initialLanguage | string | Optional initial language on first load. |
| codeFormat | LanguageCodeFormat | Optional language-code validation format. |
| persistLanguage | boolean | Whether to persist the selected language in localStorage. |
| storageKey | string | Optional custom storage key for persistence. |
| lazyLoading | boolean | Enables lazy loading for language modules. |
| lazyLoadingMode | LazyLoadingMode | Controls whether lazy loading is language, namespace, string, or manual. |
I18nLanguage<T> properties
| Property | Type | Description |
| --- | --- | --- |
| code | string | Language code, for example enGB, esES, or jaJP. |
| name | string | Human-readable language label shown in the UI. |
| translations | T | Translation object used for eager loading. |
| loader | () => Promise<T> | Lazy loader function for a language module. |
LanguageCodeFormat enum values
| Enum value | Description |
| --- | --- |
| ISO | Validates language codes in the format enGB, esES, jaJP. |
| IETF | Validates language codes in the format en-GB, es-ES, ja-JP. |
| POSIX | Validates language codes in the format en_GB, es_ES, ja_JP. |
| CUSTOM | Disables validation and allows any format. |
LazyLoadingMode enum values
| Enum value | Description |
| --- | --- |
| Language | Loads full language packs on demand. |
| Namespace | Loads translation namespaces lazily. |
| String | Loads individual translation strings lazily. |
| None | Uses manual control only. |
Notes
- Prefer the
translatepipe in templates for automatic change detection. - Use the standalone
translate()function for component and service logic. - Keep translation keys typed and consistent across languages.
- For larger applications, use namespace or string lazy loading to keep bundles smaller.
Troubleshooting
- If a value is missing, the library returns the original key instead of an empty string.
- If you are using lazy loading, make sure each configured language provides a loader.
- If language codes do not match the configured format, validation will throw early.
- If a translation key does not resolve, check that the key matches the nested structure exactly, including dot notation.
Testing translations
Keep tests small and focused on the behavior you care about.
import { TestBed } from '@angular/core/testing';
import { Component } from '@angular/core';
import { I18nModule, I18nService } from '@skhalidq/ngx-i18n';
@Component({ template: `{{ 'common.welcome' | translate }}` })
class TestComponent {}
it('renders the active language', async () => {
await TestBed.configureTestingModule({
declarations: [TestComponent],
imports: [
I18nModule.forRoot({
languages: [
{ code: 'enGB', name: 'English (GB)', translations: { common: { welcome: 'Welcome' } } },
{ code: 'esES', name: 'Español', translations: { common: { welcome: 'Bienvenido' } } }
],
fallbackLanguage: 'enGB',
initialLanguage: 'enGB'
})
]
}).compileComponents();
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement.textContent.trim()).toBe('Welcome');
});If the goal is to verify the correct translation key is being resolved, check the service result directly:
const service = TestBed.inject(I18nService);
expect(service.translate('common.welcome')).toBe('Welcome');This is useful when the test is specifically about key-to-translation correctness, not just rendered output.
© 2026 SKhalidQ. All rights reserved.
