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

@unisense.io/ngx-translator

v1.4.1

Published

Angular SDK for Unisense Translation Management — fetch, cache, ICU format, TranslatePipe, TranslateDirective.

Readme

@unisense.io/ngx-translator

Angular SDK for Unisense — the open-source Translation Management platform.

Manage all your i18n translations in one place, then consume them in your Angular app with a single pipe or directive.
Create your free workspace at unisense.io.

npm Angular License: MIT


Supports Angular 12 through 21+ via three entry points, each optimised for the features available in that Angular era.


Quick-start: which entry point do I need?

| My Angular version | Entry point to import from | Integration style | |--------------------|---------------------------|-------------------| | 12 – 13 | …/legacy | NgModule + constructor DI + BehaviorSubject | | 14 – 16 | …/compat | Standalone or NgModule + inject() + Observable | | 17 | …/compat or (main) | Compat works; main entry (Signals) is preferred | | 18 – 21+ | (main package) | Standalone or NgModule + Signals ✨ |

npm install @unisense.io/ngx-translator

Variant A — Angular 12-13 (/legacy)

Entry point

import { ... } from '@unisense.io/ngx-translator/legacy';

Setup — app.module.ts

import { NgModule }            from '@angular/core';
import { BrowserModule }       from '@angular/platform-browser';
import {
  NgxTranslatorLegacyModule,
  cookieLanguageStorage,
  navigatorLanguageDetector,
} from '@unisense.io/ngx-translator/legacy';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    NgxTranslatorLegacyModule.forRoot({
      apiUrl:        'https://your-unisense-instance.com/api',
      projectId:     'clxxxxxxxxxxxxxxxxx',
      apiKey:        'unisense_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // Required
      defaultLocale: 'en',
      preloadLocales: ['fr'],                          // optional
      languageStorage: cookieLanguageStorage('lang'),  // optional
      languageDetectors: [navigatorLanguageDetector()],// optional
    }),
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Setup — feature module

Import without forRoot() to get pipe + directive in a feature module:

import { NgModule }                  from '@angular/core';
import { NgxTranslatorLegacyModule } from '@unisense.io/ngx-translator/legacy';

@NgModule({
  imports:      [NgxTranslatorLegacyModule],
  declarations: [ProductListComponent],
})
export class ProductsModule {}

Component usage

No extra imports needed — the pipe and directive are exported by the module.

import { Component } from '@angular/core';

@Component({
  selector: 'app-home',
  template: `
    <h1>{{ 'homepage.title' | translate }}</h1>
    <p>{{ 'greeting' | translate: { name: 'Alice' } }}</p>
    <span [translate]="'items.count'" [translateParams]="{ count: total }"></span>
  `,
})
export class HomeComponent {
  total = 42;
}

Service injection

import { Component, OnInit } from '@angular/core';
import { TranslatorService } from '@unisense.io/ngx-translator/legacy';

@Component({ ... })
export class MyComponent implements OnInit {
  constructor(private readonly translator: TranslatorService) {}

  ngOnInit() {
    // Synchronous
    const title = this.translator.instant('homepage.title');

    // Observable — re-emits on locale change
    this.translator.translate('homepage.title').subscribe(t => (this.title = t));

    // Switch language
    this.translator.changeLanguage('fr');
  }
}

Note: This variant works with both RxJS 6 and RxJS 7 (the use() method avoids lastValueFrom).


Variant B — Angular 14-17 (/compat)

Entry point

import { ... } from '@unisense.io/ngx-translator/compat';

Two setup paths exist: standalone (recommended for Angular 14+) and NgModule.

Path B1 — Standalone app (app.config.ts)

import { ApplicationConfig }  from '@angular/core';
import { provideHttpClient }  from '@angular/common/http';
import { provideRouter }      from '@angular/router';
import {
  provideTranslator,
  cookieLanguageStorage,
  navigatorLanguageDetector,
} from '@unisense.io/ngx-translator/compat';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
    provideRouter(routes),
    provideTranslator({
      apiUrl:          'https://your-unisense-instance.com/api',
      projectId:       'clxxxxxxxxxxxxxxxxx',
      apiKey:          'unisense_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
      defaultLocale:   'en',
      preloadLocales:  ['fr'],
      languageStorage: cookieLanguageStorage('lang'),
      languageDetectors: [navigatorLanguageDetector()],
    }),
  ],
};

main.ts

import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent }         from './app/app.component';
import { appConfig }            from './app/app.config';

bootstrapApplication(AppComponent, appConfig).catch(console.error);

Standalone component

import { Component, inject } from '@angular/core';
import {
  TranslatePipe,
  TranslateDirective,
  TranslatorService,
} from '@unisense.io/ngx-translator/compat';

@Component({
  standalone: true,
  imports: [TranslatePipe, TranslateDirective],
  template: `
    <h1>{{ 'homepage.title' | translate }}</h1>
    <p>{{ 'greeting' | translate: { name: user.name } }}</p>
    <span [translate]="'items.count'" [translateParams]="{ count: total }"></span>
    <button (click)="switchLang('fr')">Français</button>
  `,
})
export class HomeComponent {
  private readonly translator = inject(TranslatorService);
  user = { name: 'Alice' };
  total = 42;

  switchLang(lang: string) {
    this.translator.changeLanguage(lang);
  }
}

Path B2 — NgModule app (app.module.ts)

import { NgModule }            from '@angular/core';
import { BrowserModule }       from '@angular/platform-browser';
import { HttpClientModule }    from '@angular/common/http';
import { NgxTranslatorCompatModule } from '@unisense.io/ngx-translator/compat';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    HttpClientModule,
    NgxTranslatorCompatModule.forRoot({
      apiUrl:        'https://your-unisense-instance.com/api',
      projectId:     'clxxxxxxxxxxxxxxxxx',
      apiKey:        'unisense_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
      defaultLocale: 'en',
    }),
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Variant C — Angular 17-21+ (main entry, recommended)

Entry point

import { ... } from '@unisense.io/ngx-translator';

Uses Angular Signals (signal(), effect()) for zero-overhead reactivity.


Path C1 — Standalone app (Angular 17+, recommended)

app.config.ts

import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideHttpClient }  from '@angular/common/http';
import { provideRouter }      from '@angular/router';
import {
  provideTranslator,
  cookieLanguageStorage,
  navigatorLanguageDetector,
} from '@unisense.io/ngx-translator';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideHttpClient(),
    provideTranslator({
      apiUrl:          'https://your-unisense-instance.com/api',
      projectId:       'clxxxxxxxxxxxxxxxxx',
      apiKey:          'unisense_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
      defaultLocale:   'en',
      preloadLocales:  ['fr', 'de'],
      languageStorage: cookieLanguageStorage('preferred_lang'),
      languageDetectors: [navigatorLanguageDetector()],
    }),
  ],
};

Standalone component

import { Component, inject, computed } from '@angular/core';
import {
  TranslatePipe,
  TranslateDirective,
  TranslatorService,
} from '@unisense.io/ngx-translator';

@Component({
  standalone: true,
  imports: [TranslatePipe, TranslateDirective],
  template: `
    <h1>{{ 'homepage.title' | translate }}</h1>
    <p>{{ 'greeting' | translate: { name: 'Alice' } }}</p>

    <!-- Signal-driven computed property -->
    <title>{{ pageTitle() }}</title>

    <!-- Show loading state -->
    @if (translator.loading()) {
      <p>Loading translations…</p>
    }
  `,
})
export class HomeComponent {
  readonly translator = inject(TranslatorService);

  // Recomputes automatically when locale or translations change
  readonly pageTitle = computed(() => this.translator.instant('page.title'));
}

Path C2 — NgModule app (Angular 17+)

app.module.ts

import { NgModule }         from '@angular/core';
import { BrowserModule }    from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import {
  NgxTranslatorModule,
  provideTranslator,
} from '@unisense.io/ngx-translator';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule, NgxTranslatorModule],
  providers: [
    provideTranslator({
      apiUrl:        'https://your-unisense-instance.com/api',
      projectId:     'clxxxxxxxxxxxxxxxxx',
      apiKey:        'unisense_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
      defaultLocale: 'en',
    }),
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Path C3 — Service-only (libraries, SSR, programmatic)

For use inside a library, a resolver, or server-side rendering where you don't need the pipe/directive.

import { Component, inject } from '@angular/core';
import { TranslatorService } from '@unisense.io/ngx-translator';

@Component({ ... })
export class MyComponent {
  private readonly translator = inject(TranslatorService);

  // Synchronous
  getTitle() {
    return this.translator.instant('page.title');
  }

  // Observable stream
  getTitle$() {
    return this.translator.translate('page.title');
  }

  // Signal (Angular 17+)
  getLocale() {
    return this.translator.locale(); // Signal<string>
  }
}

Configuration reference

All three variants share the same TranslatorConfig interface:

interface TranslatorConfig {
  /** Base URL of the Unisense API — no trailing slash */
  apiUrl: string;

  /** Project ID from the Unisense dashboard */
  projectId: string;

  /** API Key for the project — required for public translation fetching */
  apiKey: string;

  /** Fallback locale when all detectors return undefined */
  defaultLocale: string;

  /** Extra locales to fetch in background after init */
  preloadLocales?: string[];

  /**
   * Ordered chain of locale detectors.
   * Defaults to [navigatorLanguageDetector()].
   */
  languageDetectors?: LanguageDetectorMiddleware[];

  /**
   * Locale persistence layer.
   * Defaults to cookieLanguageStorage('preferred_language').
   * Set to null to disable persistence.
   */
  languageStorage?: LanguageStorageMiddleware | null;

  /**
   * Where to cache downloaded translation bundles between page reloads.
   * Defaults to 'session'.
   * See the "Bundle Cache" section for details.
   */
  bundleCache?: 'session' | 'local' | 'none';
}

Locale resolution order

  1. languageStorage.getLanguage() — stored cookie / localStorage
  2. languageDetectors[0..n] — first detector returning a value wins
  3. config.defaultLocale — hard-coded fallback

TranslatorService API

All three variants expose the same public API.

Properties

| Property | Modern (17+) | Compat (14-17) | Legacy (12-13) | Description | |----------|-------------|---------------|---------------|-------------| | language | string | string | string | Active locale getter | | languageAsync | Observable<string> | Observable<string> | Observable<string> | Stream of active locale | | locale | Signal<string> | (N/A) | (N/A) | Signal — modern only | | loading | Signal<boolean> | (N/A) | (N/A) | Signal — modern only | | ready | Signal<boolean> | (N/A) | (N/A) | Signal — modern only | | snapshot | ReadonlySignal<…> | (N/A) | (N/A) | Translations signal — modern only |

Methods (all variants)

// Synchronous translation
instant(key: string): string
instant(key: string, defaultValue: string): string
instant(key: string, params: TranslateParams): string
instant(key: string, defaultValue: string, params: TranslateParams): string
t(key, params?)           // shorthand alias for instant()

// Observable translation — re-emits on locale change
translate(key: string): Observable<string>
translate(key: string, defaultValue: string): Observable<string>
translate(key: string, params: TranslateParams): Observable<string>

// Language switch
use(locale: string): Promise<void>
changeLanguage(locale: string): Promise<void>   // alias

// Available languages
getLanguages(): Observable<Language[]>
getLanguagesAsync(): Promise<Language[]>

// Events
on(event: UnisenseEvent): Observable<EventType[event]>
subscribe(event, handler): { unsubscribe(): void }

Events

| Event | Payload | When | |-------|---------|------| | language | { type: 'language', value: string } | Locale changed | | loading | boolean | Initial locale fetch start/end | | fetching | boolean | Any locale fetch start/end | | initialLoad | void | First locale loaded | | error | Error | Fetch failed | | update | void | Translations table updated |


Templates

| translate pipe

{{ 'homepage.title' | translate }}
{{ 'greeting' | translate: { name: 'Alice' } }}
{{ 'items.count' | translate: { count: total } }}

[translate] directive

<span translate="homepage.title"></span>
<button [translate]="btnKey" [translateParams]="{ count: total }"></button>

ICU Message Format

Built-in parser — no external dependency needed.

Simple variable

"Hello, {name}!"

Plural

"{count, plural, =0 {No items} one {# item} other {# items}}"

Supported cases: =0, =1, =N, zero, one, two, few, many, other.

Select

"{gender, select, male {his profile} female {her profile} other {their profile}}"

Nested ICU

"{count, plural, =0 {No {type} found} one {One {type}} other {# {type}s}}"

Language Detection & Storage

Built-in

import {
  cookieLanguageStorage,
  navigatorLanguageDetector,
} from '@unisense.io/ngx-translator';        // or /legacy or /compat

Custom detector (URL param example)

const urlParamDetector: LanguageDetectorMiddleware = {
  detect() {
    return new URLSearchParams(window.location.search).get('lang') ?? undefined;
  },
};

Custom storage (localStorage example)

const localStorageMiddleware: LanguageStorageMiddleware = {
  getLanguage: () => localStorage.getItem('locale') ?? undefined,
  setLanguage: (lang) => localStorage.setItem('locale', lang),
};

Disable persistence

languageStorage: null

Bundle Cache

By default (v1.4.0+) the SDK caches every downloaded translation bundle in sessionStorage so that language switches never trigger a network request within the same browser session — not even after a page refresh (F5).

How it works

translator.changeLanguage('fr')
  │
  ├─ 1. In-memory Map  → hit → instant (< 1 µs), no HTTP
  ├─ 2. sessionStorage → hit → JSON.parse (< 1 ms), no HTTP
  └─ 3. HTTP fetch     → stores result in both layers

The first time a locale is requested it goes to the network. Every subsequent request — including after F5 — is served from storage without touching the server.

Configuration

provideTranslator({
  apiUrl:      'https://your-unisense-instance.com/api',
  projectId:   'clxxxxxxxxxxxxxxxxx',
  defaultLocale: 'en',

  // 'session' (default) — sessionStorage: cleared when the tab is closed.
  //   Language switches and F5 reloads are instant, no HTTP.
  bundleCache: 'session',

  // 'local' — localStorage: persists across browser sessions.
  //   The bundle is invalidated automatically when the server's
  //   translation version changes (translationsVersion field).
  // bundleCache: 'local',

  // 'none' — in-memory only, reverts to pre-v1.4 behaviour.
  // bundleCache: 'none',
})

Strategy comparison

| Strategy | Survives F5 | Survives tab close | Shared across tabs | Evicted when | |---|---|---|---|---| | 'session' (default) | ✅ | ❌ | ❌ | Tab closed | | 'local' | ✅ | ✅ | ✅ | Browser cache cleared | | 'none' | ❌ | ❌ | ❌ | Never stored |

SSR / Angular Universal

On the server there is no window — the SDK detects this automatically and falls back to 'none' regardless of the configured strategy. No extra configuration needed.

Storage keys

Bundles are stored under the key unisense:{projectId}:{locale}.
Example: unisense:clxxx:fr, unisense:clxxx:en.

You can inspect or clear them manually in DevTools → Application → Storage.


Language Switcher

import { Component, inject } from '@angular/core';
import { TranslatorService, Language } from '@unisense.io/ngx-translator'; // or /compat

@Component({
  standalone: true,
  template: `
    <select (change)="switch($event)">
      @for (lang of languages; track lang.code) {
        <option [value]="lang.code" [selected]="lang.code === translator.language">
          {{ lang.flagEmoji }} {{ lang.name }}
        </option>
      }
    </select>
  `,
})
export class LanguageSwitcherComponent {
  readonly translator = inject(TranslatorService);
  languages: Language[] = [];

  ngOnInit() {
    this.translator.getLanguages().subscribe(l => (this.languages = l));
  }

  switch(event: Event) {
    this.translator.changeLanguage((event.target as HTMLSelectElement).value);
  }
}

SSR / Angular Universal

Disable browser-specific middleware on the server:

// server.config.ts
export const serverConfig = mergeApplicationConfig(appConfig, {
  providers: [
    provideTranslator({
      apiUrl:           'https://api.example.com/api',
      projectId:        'clxxxxxxxxxxxxxxxxx',
      apiKey:           'unisense_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
      defaultLocale:    'en',
      languageStorage:  null,   // no document.cookie on server
      languageDetectors: [],    // no navigator on server
    }),
  ],
});

Migration from Tolgee (@tolgee/ngx)

| Tolgee | Unisense | Notes | |--------|---------|-------| | NgxTolgeeModule | NgxTranslatorLegacyModule / NgxTranslatorCompatModule / NgxTranslatorModule | Same role | | TranslateService | TranslatorService | Also re-exported as TranslateService alias | | t(), instant(), changeLanguage() | Identical | No changes needed | | lang.tag | lang.code | tag kept as deprecated alias | | lang.base | lang.isBase | base kept as deprecated alias | | provideTolgee({ defaultLanguage }) | provideTranslator({ defaultLocale }) | Renamed key | | provideTolgee({ staticData }) | provideTranslator({ projectId }) | Replaced by server-side fetch |


Troubleshooting

"NullInjectorError: No provider for TRANSLATOR_CONFIG"

You forgot to call forRoot(config) on the module import, or forgot provideTranslator(config) in standalone setup.

Translations not loading / 404 or 401 on API call

  • Check apiUrl has no trailing slash.
  • The SDK calls GET {apiUrl}/public/{projectId}/translations/{locale}.
  • Ensure you have provided a valid apiKey in the config.
  • Make sure provideHttpClient() (standalone) or HttpClientModule (NgModule) is in your providers.

"Cannot find module '@unisense.io/ngx-translator/compat'" (TS2307)

Your TypeScript config uses "moduleResolution": "node" which does not understand the exports field in package.json.
Two options:

Option A — upgrade tsconfig.json (recommended for Angular 16+):

{ "compilerOptions": { "moduleResolution": "bundler" } }

Option B — the package ships typesVersions as a fallback (since v1.3.1), which resolves the subpaths automatically without any tsconfig change.

"No matching export for 'provideAppInitializer'" (Angular 17)

provideAppInitializer was introduced in Angular 18. If you are on Angular 17, use:

  • Main entry v1.3.1+provideTranslator() now uses APP_INITIALIZER internally, compatible with Angular 17–21+.
  • /compat entry — always uses APP_INITIALIZER, compatible with Angular 14–17.

Pipe not re-rendering after changeLanguage()

  • Modern variant (17+): TranslatePipe reads the snapshot() signal — re-renders automatically. Check that TranslatePipe is in imports.
  • Compat/Legacy variants: The pipe subscribes to the 'update' event and calls markForCheck(). Make sure ChangeDetectionStrategy.OnPush is applied correctly — the pipe handles it internally.

Angular 12 with RxJS 6

The /legacy entry point uses an internal lastValue() helper that works with both RxJS 6 and RxJS 7. No changes needed in your project.


Changelog

1.4.0

  • Feature: Two-level bundle cache — sessionStorage (default), localStorage, or 'none'.
    Language switches and F5 reloads no longer trigger a network request. Configure via bundleCache in TranslatorConfig.
  • Feature: bundleCache: 'local' uses localStorage for cross-session persistence.
    Bundles include a v (version) field that matches the server's translationsVersion — ready for future version-based eviction.
  • Fix: preload() now checks the bundle cache before making an HTTP request, so preloadLocales is also zero-cost on repeated page loads.
  • API: ApiResponse now includes an optional version field (returned by Unisense API ≥ 1.4).
  • SSR: resolveStorage() guards against window being undefined on the server — no change needed in server.config.ts.

1.3.1

  • Fix: Main entry now uses APP_INITIALIZER instead of provideAppInitializer, restoring compatibility with Angular 17 (provideAppInitializer is Angular 18+ only).
  • Fix: Added typesVersions to package.json — TypeScript projects using "moduleResolution": "node" can now resolve /compat and /legacy subpaths without any tsconfig change.
  • Docs: Variant B compat range extended to Angular 14–17; quick-start table updated.

1.3.0

  • Initial public release with Signals API, Observable compat layer, and NgModule legacy support.

Related packages

| Package | Framework | Link | |---|---|---| | @unisense.io/ngx-translator | Angular 12 – 21+ | this package | | @unisense/react-translator | React 16.8+ | npm |


License

MIT © Unisense