ngx-translate

Last updated: July 31, 2025Author: Jakub Pomykała

In this article, you will learn how to configure ngx-translate library with hosted translations via Translation Hosting or locally loaded files. Translation hosting allows you to change translations without re-deploying your application as all messages are loaded directly from the cloud. Locally loaded files are useful when you want to keep translations in your repository or load them from a different source.

Installation

Install ngx-translate and its HTTP loader for Angular:

npm install @ngx-translate/core @ngx-translate/http-loader

Configuration

Import ngx-translate modules

Update your app.module.ts to include the ngx-translate modules:

// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

// Factory function for the TranslateHttpLoader
export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      },
      defaultLanguage: 'en'
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Initialize translation service

Configure the translation service in your main component:

// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'ngx-translate-app';

  constructor(private translate: TranslateService) {}

  ngOnInit() {
    // Set default language
    this.translate.setDefaultLang('en');

    // Detect browser language and use it if available
    const browserLang = this.translate.getBrowserLang();
    const availableLanguages = ['en', 'es', 'fr', 'de'];

    if (browserLang && availableLanguages.includes(browserLang)) {
      this.translate.use(browserLang);
    } else {
      this.translate.use('en');
    }
  }

  switchLanguage(language: string) {
    this.translate.use(language);
    localStorage.setItem('preferred-language', language);
  }
}

Load translations

Option 1: Hosted translations

If you want to use hosted translations from SimpleLocalize CDN, create a custom loader:

// src/app/services/simplelocalize-loader.service.ts
import { Injectable } from '@angular/core';
import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class SimpleLocalizeLoader implements TranslateLoader {
  private PROJECT_TOKEN = '<YOUR_PROJECT_TOKEN>';
  private BASE_URL = 'https://cdn.simplelocalize.io';
  private ENVIRONMENT = '_latest'; // or '_production'

  constructor(private http: HttpClient) {}

  getTranslation(lang: string): Observable<any> {
    const url = `${this.BASE_URL}/${this.PROJECT_TOKEN}/${this.ENVIRONMENT}/${lang}`;
    return this.http.get(url);
  }
}

Update your app.module.ts to use the custom loader:

// src/app/app.module.ts
import { SimpleLocalizeLoader } from './services/simplelocalize-loader.service';

// Factory function for SimpleLocalizeLoader
export function SimpleLocalizeLoaderFactory(http: HttpClient) {
  return new SimpleLocalizeLoader(http);
}

@NgModule({
  // ...existing code...
  imports: [
    // ...existing imports...
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: SimpleLocalizeLoaderFactory,
        deps: [HttpClient]
      },
      defaultLanguage: 'en'
    })
  ],
  // ...existing code...
})
export class AppModule { }

Option 2: Locally loaded files

For local translation files, keep the default HTTP loader configuration and create translation files in the assets/i18n/ directory:

src/
  assets/
    i18n/
      en.json
      es.json
      fr.json
      de.json

Example translation files:

// src/assets/i18n/en.json
{
  "nav": {
    "home": "Home",
    "about": "About",
    "services": "Services",
    "contact": "Contact"
  },
  "home": {
    "title": "Welcome to Our Application",
    "subtitle": "This is a multilingual Angular application",
    "description": "Built with ngx-translate and SimpleLocalize"
  },
  "contact": {
    "title": "Contact Us",
    "form": {
      "name": "Full Name",
      "email": "Email Address",
      "message": "Your Message",
      "submit": "Send Message"
    }
  },
  "common": {
    "loading": "Loading...",
    "save": "Save",
    "cancel": "Cancel",
    "delete": "Delete"
  }
}
// src/assets/i18n/es.json
{
  "nav": {
    "home": "Inicio",
    "about": "Acerca de",
    "services": "Servicios",
    "contact": "Contacto"
  },
  "home": {
    "title": "Bienvenido a Nuestra Aplicación",
    "subtitle": "Esta es una aplicación Angular multiidioma",
    "description": "Construida con ngx-translate y SimpleLocalize"
  },
  "contact": {
    "title": "Contáctanos",
    "form": {
      "name": "Nombre Completo",
      "email": "Dirección de Correo",
      "message": "Tu Mensaje",
      "submit": "Enviar Mensaje"
    }
  },
  "common": {
    "loading": "Cargando...",
    "save": "Guardar",
    "cancel": "Cancelar",
    "delete": "Eliminar"
  }
}

Both options are valid, and you can choose the one that fits your needs.

Usage

Using the translate pipe

The translate pipe is the most common way to use translations in templates:

<!-- src/app/app.component.html -->
<div class="app-container">
  <nav class="navigation">
    <a routerLink="/">{{ 'nav.home' | translate }}</a>
    <a routerLink="/about">{{ 'nav.about' | translate }}</a>
    <a routerLink="/services">{{ 'nav.services' | translate }}</a>
    <a routerLink="/contact">{{ 'nav.contact' | translate }}</a>
  </nav>

  <main>
    <h1>{{ 'home.title' | translate }}</h1>
    <p>{{ 'home.subtitle' | translate }}</p>
    <p>{{ 'home.description' | translate }}</p>
  </main>

  <app-language-switcher></app-language-switcher>
</div>

Using with parameters

Pass parameters to translations using the translate pipe:

<p>{{ 'welcome.message' | translate: { name: userName, count: itemCount } }}</p>

Translation file:

{
  "welcome": {
    "message": "Hello {{name}}, you have {{count}} items"
  }
}

Using the translate service

For more complex scenarios, use the TranslateService directly in your components:

// src/app/components/user-profile.component.ts
import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-user-profile',
  template: `
    <div class="user-profile">
      <h2>{{ profileTitle }}</h2>
      <p>{{ joinedMessage }}</p>
      <button (click)="deleteAccount()">{{ 'common.delete' | translate }}</button>
    </div>
  `
})
export class UserProfileComponent implements OnInit {
  profileTitle = '';
  joinedMessage = '';

  constructor(private translate: TranslateService) {}

  ngOnInit() {
    this.translate.get('profile.title').subscribe((translation: string) => {
      this.profileTitle = translation;
    });

    const joinDate = new Date('2023-01-15');
    this.translate.get('profile.joined', {
      date: joinDate.toLocaleDateString()
    }).subscribe((translation: string) => {
      this.joinedMessage = translation;
    });
  }

  deleteAccount() {
    this.translate.get(['common.confirm', 'profile.delete.message']).subscribe(translations => {
      const confirmMessage = translations['common.confirm'];
      const deleteMessage = translations['profile.delete.message'];

      if (confirm(`${confirmMessage}: ${deleteMessage}`)) {
        // Handle account deletion
      }
    });
  }
}

Language switching

Create a language switcher component:

// src/app/components/language-switcher.component.ts
import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

interface Language {
  code: string;
  name: string;
  flag: string;
}

@Component({
  selector: 'app-language-switcher',
  template: `
    <div class="language-switcher">
      <label for="language-select">{{ 'common.language' | translate }}</label>
      <select
        id="language-select"
        [value]="currentLanguage"
        (change)="onLanguageChange($event)"
        class="language-select"
      >
        <option
          *ngFor="let lang of availableLanguages"
          [value]="lang.code"
        >
          {{ lang.flag }} {{ lang.name }}
        </option>
      </select>
    </div>
  `,
  styles: [`
    .language-switcher {
      display: flex;
      align-items: center;
      gap: 0.5rem;
      margin: 1rem 0;
    }

    .language-select {
      padding: 0.5rem;
      border: 1px solid #ddd;
      border-radius: 4px;
      background: white;
      font-size: 1rem;
    }
  `]
})
export class LanguageSwitcherComponent implements OnInit {
  currentLanguage: string = 'en';

  availableLanguages: Language[] = [
    { code: 'en', name: 'English', flag: '🇺🇸' },
    { code: 'es', name: 'Español', flag: '🇪🇸' },
    { code: 'fr', name: 'Français', flag: '🇫🇷' },
    { code: 'de', name: 'Deutsch', flag: '🇩🇪' }
  ];

  constructor(private translate: TranslateService) {}

  ngOnInit() {
    this.currentLanguage = this.translate.currentLang || this.translate.defaultLang;

    // Load saved language preference
    const savedLanguage = localStorage.getItem('preferred-language');
    if (savedLanguage && this.availableLanguages.some(lang => lang.code === savedLanguage)) {
      this.translate.use(savedLanguage);
      this.currentLanguage = savedLanguage;
    }
  }

  onLanguageChange(event: Event) {
    const target = event.target as HTMLSelectElement;
    const selectedLanguage = target.value;

    if (selectedLanguage !== this.currentLanguage) {
      this.translate.use(selectedLanguage);
      this.currentLanguage = selectedLanguage;
      localStorage.setItem('preferred-language', selectedLanguage);
    }
  }
}

Don't forget to declare the component in your module:

// src/app/app.module.ts
import { LanguageSwitcherComponent } from './components/language-switcher.component';

@NgModule({
  declarations: [
    AppComponent,
    LanguageSwitcherComponent
  ],
  // ...existing code...
})
export class AppModule { }

Contact form example

Create a contact form component with ngx-translate:

// src/app/components/contact-form.component.ts
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-contact-form',
  template: `
    <form [formGroup]="contactForm" (ngSubmit)="onSubmit()" class="contact-form">
      <h2>{{ 'contact.title' | translate }}</h2>

      <div class="form-group">
        <label for="name">{{ 'contact.form.name' | translate }}</label>
        <input
          id="name"
          type="text"
          formControlName="name"
          [placeholder]="'contact.form.name' | translate"
          [class.error]="contactForm.get('name')?.invalid && contactForm.get('name')?.touched"
        />
        <div *ngIf="contactForm.get('name')?.invalid && contactForm.get('name')?.touched" class="error-message">
          {{ 'validation.required' | translate }}
        </div>
      </div>

      <div class="form-group">
        <label for="email">{{ 'contact.form.email' | translate }}</label>
        <input
          id="email"
          type="email"
          formControlName="email"
          [placeholder]="'contact.form.email' | translate"
          [class.error]="contactForm.get('email')?.invalid && contactForm.get('email')?.touched"
        />
        <div *ngIf="contactForm.get('email')?.invalid && contactForm.get('email')?.touched" class="error-message">
          {{ getEmailErrorMessage() }}
        </div>
      </div>

      <div class="form-group">
        <label for="message">{{ 'contact.form.message' | translate }}</label>
        <textarea
          id="message"
          formControlName="message"
          rows="5"
          [placeholder]="'contact.form.message' | translate"
          [class.error]="contactForm.get('message')?.invalid && contactForm.get('message')?.touched"
        ></textarea>
        <div *ngIf="contactForm.get('message')?.invalid && contactForm.get('message')?.touched" class="error-message">
          {{ 'validation.required' | translate }}
        </div>
      </div>

      <div class="form-actions">
        <button
          type="submit"
          [disabled]="contactForm.invalid || isSubmitting"
          class="submit-btn"
        >
          {{ isSubmitting ? ('common.loading' | translate) : ('contact.form.submit' | translate) }}
        </button>
      </div>

      <div *ngIf="submitMessage" [class]="'submit-message ' + submitStatus">
        {{ submitMessage }}
      </div>
    </form>
  `,
  styles: [`
    .contact-form {
      max-width: 600px;
      margin: 2rem auto;
      padding: 2rem;
      border: 1px solid #e0e0e0;
      border-radius: 8px;
    }

    .form-group {
      margin-bottom: 1.5rem;
    }

    .form-group label {
      display: block;
      margin-bottom: 0.5rem;
      font-weight: 600;
    }

    .form-group input,
    .form-group textarea {
      width: 100%;
      padding: 0.75rem;
      border: 1px solid #ccc;
      border-radius: 4px;
      font-size: 1rem;
    }

    .form-group input.error,
    .form-group textarea.error {
      border-color: #e74c3c;
    }

    .error-message {
      color: #e74c3c;
      font-size: 0.875rem;
      margin-top: 0.25rem;
    }

    .submit-btn {
      background: #007acc;
      color: white;
      padding: 0.75rem 2rem;
      border: none;
      border-radius: 4px;
      font-size: 1rem;
      cursor: pointer;
      transition: background 0.2s;
    }

    .submit-btn:hover:not(:disabled) {
      background: #005999;
    }

    .submit-btn:disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }

    .submit-message {
      margin-top: 1rem;
      padding: 1rem;
      border-radius: 4px;
      text-align: center;
    }

    .submit-message.success {
      background: #d4edda;
      color: #155724;
      border: 1px solid #c3e6cb;
    }

    .submit-message.error {
      background: #f8d7da;
      color: #721c24;
      border: 1px solid #f5c6cb;
    }
  `]
})
export class ContactFormComponent {
  contactForm: FormGroup;
  isSubmitting = false;
  submitMessage = '';
  submitStatus: 'success' | 'error' = 'success';

  constructor(
    private fb: FormBuilder,
    private translate: TranslateService
  ) {
    this.contactForm = this.fb.group({
      name: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
      message: ['', Validators.required]
    });
  }

  getEmailErrorMessage(): string {
    const emailControl = this.contactForm.get('email');
    if (emailControl?.errors?.['required']) {
      return this.translate.instant('validation.required');
    }
    if (emailControl?.errors?.['email']) {
      return this.translate.instant('validation.email');
    }
    return '';
  }

  async onSubmit() {
    if (this.contactForm.valid) {
      this.isSubmitting = true;

      try {
        // Simulate API call
        await new Promise(resolve => setTimeout(resolve, 2000));

        this.submitMessage = this.translate.instant('contact.form.success');
        this.submitStatus = 'success';
        this.contactForm.reset();
      } catch (error) {
        this.submitMessage = this.translate.instant('contact.form.error');
        this.submitStatus = 'error';
      } finally {
        this.isSubmitting = false;

        // Clear message after 5 seconds
        setTimeout(() => {
          this.submitMessage = '';
        }, 5000);
      }
    }
  }
}

Managing translations with SimpleLocalize

Install SimpleLocalize CLI

# macOS / Linux / Windows (WSL) curl -s https://get.simplelocalize.io/2.9/install | bash # Windows (PowerShell) . { iwr -useb https://get.simplelocalize.io/2.9/install-windows } | iex; # npm npm install @simplelocalize/cli
# macOS / Linux / Windows (WSL)
curl -s https://get.simplelocalize.io/2.9/install | bash

# Windows (PowerShell)
. { iwr -useb https://get.simplelocalize.io/2.9/install-windows } | iex;

# npm
npm install @simplelocalize/cli

Configure SimpleLocalize

Create a simplelocalize.yml file in the root of your project:

apiKey: YOUR_PROJECT_API_KEY

# Upload configuration
uploadFormat: single-language-json
uploadPath: ./src/assets/i18n/en.json
uploadLanguageKey: en
uploadOptions:
  - REPLACE_TRANSLATION_IF_FOUND

# Download configuration
downloadPath: ./src/assets/i18n/{lang}.json
downloadLanguageKeys: ['en', 'es', 'fr', 'de']
downloadFormat: single-language-json
downloadOptions:
  - CREATE_DIRECTORIES

Upload source translations

Upload your source (English) translations to SimpleLocalize:

simplelocalize upload

This will upload the en.json file to SimpleLocalize as your source translations.

Manage translations

Now you can manage and translate your messages in the SimpleLocalize translation editor:

  1. Add new languages in the Languages tab
  2. Use auto-translation to quickly translate into multiple languages
  3. Manually refine translations for better quality
  4. Add context and descriptions to help translators

Download translations

Download completed translations to your project:

simplelocalize download

This will create/update translation files in ./src/assets/i18n/ directory:

  • en.json
  • es.json
  • fr.json
  • de.json

Build workflow

Add these scripts to your package.json for a complete workflow:

{
  "scripts": {
    "i18n:upload": "simplelocalize upload",
    "i18n:download": "simplelocalize download",
    "i18n:sync": "npm run i18n:upload && npm run i18n:download",
    "build": "npm run i18n:download && ng build"
  }
}

Advanced features

Pluralization

ngx-translate supports ICU pluralization format:

{
  "items": {
    "count": "{count, plural, =0 {no items} =1 {one item} other {# items}}"
  }
}

Usage:

<p>{{ 'items.count' | translate: { count: itemCount } }}</p>

Lazy loading translations

For large applications, you can lazy load translations for specific modules:

// src/app/modules/admin/admin.module.ts
import { NgModule } from '@angular/core';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { HttpClient } from '@angular/common/http';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';

export function createTranslateLoader(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/admin/', '.json');
}

@NgModule({
  imports: [
    TranslateModule.forChild({
      loader: {
        provide: TranslateLoader,
        useFactory: createTranslateLoader,
        deps: [HttpClient]
      },
      isolate: true
    })
  ]
})
export class AdminModule { }

Custom interpolation

Configure custom interpolation for different placeholder styles:

// src/app/app.module.ts
@NgModule({
  imports: [
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      },
      defaultLanguage: 'en',
      interpolation: {
        format: (value: any, format: string, lng: string): string => {
          if (format === 'uppercase') return value.toUpperCase();
          if (format === 'lowercase') return value.toLowerCase();
          return value;
        }
      }
    })
  ]
})
export class AppModule { }

Usage:

{
  "greeting": "Hello {{name, uppercase}}!"
}

Resources

Was this helpful?