npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@skhalidq/ngx-i18n

v1.1.6

Published

A type-safe Angular internationalization library for translations, interpolation, pluralization, and lazy loading.

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-i18n

Features

  • Type-safe translation keys through TypeScript interfaces
  • Template translations with the translate pipe
  • TypeScript translations with the standalone translate() helper
  • Interpolation with {{ variable }} placeholders
  • Pluralization with zero, one, and other forms
  • 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:

  • persistLanguage defaults to true
  • storageKey defaults 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 packs
  • LazyLoadingMode.Namespace: load namespaces on demand
  • LazyLoadingMode.String: load individual strings on demand
  • LazyLoadingMode.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, jaJP
  • IETF: en-GB, es-ES, ja-JP
  • POSIX: en_GB, es_ES, ja_JP
  • CUSTOM: 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 translate pipe 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.