Help Center

Angular i18next

Internationalization (i18n) is essential for building global Angular applications. This guide shows you how to implement i18next in Angular with proper resource loading, initialization, and usage patterns.

Why i18next for Angular?

i18next is one of the most popular i18n frameworks, offering powerful features and flexibility:

  • Framework agnostic - works seamlessly with Angular, React, Vue, and others.
  • Powerful interpolation, pluralization, and context support.
  • Lazy loading and code splitting support.
  • TypeScript support with type safety.
  • Large ecosystem with plugins and extensions.

Installation

Install i18next using npm or yarn:

npm install i18next

That's it! i18next is a standalone library with no framework-specific dependencies. You don't need angular-i18next or any other wrapper - just use i18next directly in your Angular application.

Loading Translation Resources

There are two main approaches to loading translation resources in Angular with i18next:

Dynamic Imports (Recommended)

Using dynamic imports leverages Webpack's code splitting to load translations on-demand. This approach is ideal for applications with many languages or large translation files.

Advantages:

  • Bundle size optimization - only load needed translations.
  • Better performance - faster initial load time.
  • Works perfectly with SSR and prerendering.
  • No additional HTTP requests required.

Example: Loading with Dynamic Imports

private async loadLanguageResources(
  lang: string,
): Promise<Record<string, Record<string, unknown>>> {
  const languageResources: Record<string, Record<string, unknown>> = {};

  for (const namespace of NAMESPACES) {
    const translationModule = await this.loadJson(lang, namespace);
    languageResources[namespace] = translationModule.default;
  }
  return languageResources;
}

private async loadJson(
  language: string,
  namespace: string,
): Promise<{ default: Record<string, unknown> }> {
  // Webpack automatically code-splits this into separate chunks
  return import(`../../i18n/${language}/${namespace}.json`);
}

This approach imports translation files as ES modules, allowing Webpack to automatically code-split them into separate chunks that are loaded on demand.

i18next-http-backend

The HTTP backend plugin loads translations via HTTP requests from a server or CDN. This is useful when you want to update translations without rebuilding your app.

Advantages:

  • Update translations without redeployment.
  • Load translations from external CDN.
  • Useful for runtime translation management.

Disadvantages:

  • Additional HTTP requests slow down initial load.
  • Requires network connectivity.
  • More complex SSR setup.

Example: Loading with HTTP Backend

import HttpBackend from 'i18next-http-backend';

await init({
  lng: initialLanguage,
  fallbackLng: DEFAULT_LANGUAGE,
  backend: {
    loadPath: '/assets/i18n/{{lng}}/{{ns}}.json',
  },
  use: [HttpBackend],
  ns: NAMESPACES,
  defaultNS: DEFAULT_NAMESPACE,
});

This approach fetches translation files from a server at runtime. Note that this requires additional configuration for SSR.

Which Approach to Choose?

Use Dynamic Imports when:

  • You want optimal bundle size and performance.
  • Translations are part of your build process.
  • You need SSR/prerendering support.

Use HTTP Backend when:

  • You need to update translations without redeployment.
  • Translations are managed externally.
  • You have a CDN for serving translation files.

Proper i18next Initialization

Initializing i18next correctly is crucial for avoiding runtime errors and ensuring translations are available when your app renders.

Using provideAppInitializer

Angular's provideAppInitializer function (introduced in Angular 18) ensures i18next is fully initialized before the application starts rendering. The provided function is executed during app bootstrap, and initialization does not complete until the Promise resolves. This prevents translation keys from appearing instead of translated text.

Example: App Initializer Configuration

export const appConfig: ApplicationConfig = {
  providers: [
    // ... other providers
    provideAppInitializer(initializeI18n),
    // ... more providers
  ],
};

Why This Matters:

  • The function runs in the injection context during application startup.
  • Prevents flickering or showing translation keys on initial load.
  • Ensures translations are available in all components from the start.
  • Works correctly with SSR, prerendering, and client-side rendering.

i18next Initialization Function

The initialization function handles loading resources and configuring i18next settings.

Example: I18next Initialization

public async initializeI18n(): Promise<void> {
  const initialLanguage = this.getInitialLanguage();
  const resources: Record<string, Record<string, Record<string, unknown>>> = {};

  if (isPlatformBrowser(this.platformId)) {
    // Browser: Only load the specific language needed
    const languageResources = await this.loadLanguageResources(initialLanguage);
    resources[initialLanguage] = languageResources;
  } else {
    // SSR: Load all languages for prerendering
    for (const lang of SUPPORTED_LANGUAGES) {
      const languageResources = await this.loadLanguageResources(lang);
      resources[lang] = languageResources;
    }
  }

  await init({
    lng: initialLanguage,
    fallbackLng: DEFAULT_LANGUAGE,
    resources,
    ns: NAMESPACES,
    defaultNS: DEFAULT_NAMESPACE,
    interpolation: {
      escapeValue: false,
    },
  });
}

Key Points:

  • Load only the current language in browser for optimal performance.
  • Load all languages during SSR for prerendering support.
  • Use fallbackLng to handle missing translations gracefully.
  • Configure namespaces for better organization.

Translation Pipe

The Angular translation pipe provides a clean way to use translations in templates.

Pipe Implementation

Example: Translation Pipe

import { Pipe, PipeTransform } from '@angular/core';
import { t } from 'i18next';

@Pipe({
  name: 't',
  standalone: true,
  pure: false, // Need to update when language changes
})
export class TranslatePipe implements PipeTransform {
  transform(key: string, options?: Record<string, unknown>): string {
    return t(key, options);
  }
}

The pipe is marked as pure: false to ensure it updates when the language changes. This is important because language changes don't modify the translation key itself.

Using the Translation Pipe

In Templates:

<!-- With namespace -->
<p>{{ "common:welcomeMessage" | t }}</p>

<!-- With nested keys -->
<p>{{ "dashboard.title" | t }}</p>

<!-- Simple interpolation -->
<p>{{ "greeting" | t: { name: userName } }}</p>
<!-- Result: "Hello, John!" from greeting: "Hello, {{name}}!" -->

<!-- With Angular pipes -->
<p>{{ "price" | t: { amount: 29.99 | currency } }}</p>
<!-- Result: "Price: $29.99" from: "Price: {{amount}}" -->

<!-- Multiple variables -->
<p>{{ "updated" | t: { date: lastModified | date, user: currentUser } }}</p>
<!-- Result: "Updated on Jan 15, 2024 by Alice" from: "Updated on {{date}} by {{user}}." -->

Interpolation allows you to insert dynamic values into your translations. You can pass variables, use Angular pipes for formatting, and combine multiple values in a single translation string.

In Component Code:

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

@Component({
  selector: 'app-dashboard',
  template: `
    <h1>{{ title }}</h1>
    <p>{{ welcomeMsg }}</p>
  `,
})
export class DashboardComponent {
  title = t('dashboard.title');
  welcomeMsg = t('greeting', { name: 'John' });
}

For template usage, the pipe syntax is clean and readable. For programmatic access, call t() directly. This is useful for showing notifications, setting dynamic titles, or handling translations in component logic.

Best Practices for Angular i18next

Use Meaningful Keys

Use dot notation that reflects your app structure: 'auth:login.title' instead of 'loginTitle' or raw text.

Organize with Namespaces

Split translations into logical namespaces (e.g., common.json, auth.json, dashboard.json) for better maintainability.

Handle Pluralization

Use i18next's built-in pluralization support for handling singular/plural forms correctly in all languages.

Wait for Initialization

Always ensure i18next is initialized before rendering your app using provideAppInitializer() to run initialization during app bootstrap.

Type Safety for Translation Keys

Use TypeScript to extract translation keys directly from your JSON files for complete type safety. Instead of manually maintaining type definitions, leverage TypeScript's typeof and dynamic imports to automatically generate types from your actual translation files.

Define a type that extracts all possible keys from your JSON translation files using a recursive NestedKeys helper type.

import common from './en/common.json'; // defaultNS: DEFAULT_NAMESPACE,
import auth from './en/auth.json'; // namespaces

type NestedKeys<T, Prefix extends string = ''> = T extends object
  ? {
      [K in keyof T & (string | number)]: T[K] extends object
        ?
            | NestedKeys<T[K], `${Prefix}${K & (string | number)}.`>
            | `${Prefix}${K & (string | number)}`
        : `${Prefix}${K & (string | number)}`;
    }[keyof T & (string | number)]
  : never;

export type TranslationKey =
  | `common:${NestedKeys<typeof common>}`
  | `auth:${NestedKeys<typeof auth>}`;

Change the pipe's transform method to accept TranslationKey instead of string. This provides type safety in templates and ensures only valid translation keys can be used.

import { Pipe, PipeTransform } from '@angular/core';
import { t } from 'i18next';

import { TranslationKey } from '../../i18n/translation-key';

@Pipe({
  name: 't',
  standalone: true,
  pure: false, // Need to update when language changes
})
export class TranslatePipe implements PipeTransform {
  transform(key: TranslationKey, options?: Record<string, unknown>): string {
    return t(key, options);
  }
}

Now you get full IntelliSense autocomplete and compile-time errors for invalid keys in both TypeScript code and templates.

t('common:greeting');       // ✅ Autocomplete
t('auth:login.title');      // ✅ Valid
t('invalid.key');           // ❌ IDE shows error

{{ "common:greeting" | t }} // ✅ Valid in templates
{{ "invalid.key" | t }}     // ❌ IDE shows error

Benefits of Type-Safe Translation Keys:

  • Autocomplete in your IDE for all available translation keys across all namespaces.
  • Compile-time errors if you use a non-existent key, catching typos before runtime.
  • Refactoring support - rename keys safely across your entire codebase.
  • Zero runtime overhead - types are completely removed during compilation.
  • Auto-generated from your actual JSON files - no manual type maintenance required.

Language Detection and Switching

Implement intelligent language detection and allow users to switch languages at runtime with automatic resource loading.

Determine the user's language to use when your app starts by checking multiple sources in priority order.

private getInitialLanguage(): string {
  if (isPlatformBrowser(this.platformId)) {
    // Try to get language from localStorage (user preference)
    const savedLang = localStorage.getItem('language');
    if (savedLang) {
      return savedLang;
    }

    // Fall back to browser language
    return navigator.language ?? DEFAULT_LANGUAGE;
  }

  // Default language for SSR
  return DEFAULT_LANGUAGE;
}

This approach checks localStorage first (user preference), then falls back to the browser's language (navigator.language), and finally defaults to the app's default language.

Switch languages at runtime while automatically loading translation resources on-demand.

public async switchLanguage(
  language: string
): Promise<void> {
  // In browser, check if we need to load the translation first
  if (isPlatformBrowser(this.platformId)) {
    const i18next = await import('i18next');

    // Check if any namespace is missing for this language
    for (const namespace of NAMESPACES) {
      const currentResources = i18next.default.getResourceBundle(lang, namespace);

      if (!currentResources) {
        // Translation not loaded yet, load it dynamically
        const translationModule = await this.loadJson(lang, namespace);
        i18next.default.addResourceBundle(
          lang,
          namespace,
          translationModule.default
        );
      }
    }
  }

  // Switch to the new language
  await changeLanguage(lang);

  // Save preference to localStorage
  if (isPlatformBrowser(this.platformId)) {
    localStorage.setItem('language', lang);
  }
}

When using dynamic imports, translations are loaded on-demand. The switchLanguage() method checks if resources are already loaded using getResourceBundle(). If missing, it dynamically imports and adds them using addResourceBundle(). This prevents redundant network requests while ensuring all needed translations are available.

Key Implementation Details:

  • Check localStorage first to restore user's preferred language.
  • Use navigator.language as fallback for browser's language preference.
  • Load missing resources dynamically with addResourceBundle().

Automate Translation with AI

Managing translations manually across multiple languages is time-consuming and error-prone. That's where AI-powered localization comes in.

Why l10n.dev?

l10n.dev is an AI-powered translation service specifically designed for i18next JSON files:

  • Preserves JSON structure, keys, and nested objects.
  • Maintains placeholders like {{name}}, {{count}} correctly.
  • Automatically generates plural forms for all languages.
  • Handles context and interpolation intelligently.
  • Supports all i18next features including namespaces.

Simple Workflow

  1. Export your base language JSON file (e.g., en/common.json)
  2. Upload to l10n.dev and select target languages.
  3. AI translates with context awareness and proper formatting.
  4. Download translated files ready to use in your Angular app.

Save hours of manual translation work and avoid common errors like broken placeholders or incorrect plural forms.

Ready to streamline your Angular i18n workflow?