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
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:
- Add new languages in the Languages tab
- Use auto-translation to quickly translate into multiple languages
- Manually refine translations for better quality
- 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
- ngx-translate Documentation
- Angular Internationalization Guide
- SimpleLocalize CLI Documentation
- Translation Hosting
- ngx-translate HTTP Loader