Skip to content
πŸš€ 😍 Angular Internationalization DoneΒ Right
TypeScript JavaScript HTML CSS
Branch: master
Clone or download
itayod docs: ✏️ add docs to programmatically scope translations (#76)
* docs: ✏️ add docs to programmatically scope translations
Latest commit 4ee9165 Sep 14, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
cypress test: πŸ’ fix cypress specs Aug 26, 2019
hooks plugin: πŸ’₯ translators comments Sep 11, 2019
projects/ngneat docs: ✏️ add docs to programmatically scope translations (#76) Sep 14, 2019
schematics fix: πŸ› update migration script Sep 11, 2019
src plugin: πŸ’₯ translators comments Sep 11, 2019
vscode-snippets chore: πŸ€– release vscode version Aug 22, 2019
.all-contributorsrc docs: add @DerSizeS as a contributor Sep 7, 2019
.editorconfig initial commit Jul 12, 2019
.gitignore chore: πŸ€– plugins Aug 27, 2019
.prettierignore add first spec and prettier Jul 23, 2019
.travis.yml chore: πŸ€– update travis config Aug 26, 2019
CHANGELOG.md chore(release): 1.7.8 Sep 11, 2019
CODE_OF_CONDUCT.md add missing files Aug 11, 2019
CONTRIBUTING.md add missing files Aug 11, 2019
CREATE_LIBS.md chore: πŸ€– plugins Aug 27, 2019
ISSUE_TEMPLATE.md docs: ✏️ add stackblitz example to the issue-template (#10) Aug 16, 2019
LICENSE add missing files Aug 11, 2019
PULL_REQUEST_TEMPLATE.md add missing files Aug 11, 2019
README.md docs: ✏️ add docs to programmatically scope translations (#76) Sep 14, 2019
_redirects remove page Aug 4, 2019
angular.json chore: πŸ€– plugins Aug 29, 2019
changelog.config.js chore: πŸ€– add plugin option Sep 1, 2019
commitlint.config.js * plugin: πŸ’₯ added configuration to Message Format Sep 3, 2019
cypress.json fix tests Aug 1, 2019
karma.conf.js feat(specs): add specs coverage Jul 26, 2019
logo.png docs: ✏️ logo Aug 15, 2019
logo.svg update logo Aug 15, 2019
package-lock.json docs: ✏️ add docs to programmatically scope translations (#76) Sep 14, 2019
package.json plugin: πŸ’₯ translators comments Sep 11, 2019
prettier.config.js add first spec and prettier Jul 23, 2019
transloco.gif docs: ✏️ replace gif Sep 6, 2019
tsconfig.json chore: πŸ€– plugins Aug 29, 2019
tslint.json downgrade to v7 Aug 9, 2019

README.md


Translation can drive you crazy, here's the cure!

The internationalization (i18n) library for Angular

Build Status All Contributors commitizen PRs coc-badge semantic-release styled with prettier spectator Join the chat at https://gitter.im/ngneat-transloco

Features

πŸ›€ Clean and DRY templates
😴 Support for Lazy Load
😍 Support for Multiple Languagues
πŸ”₯ Support for Multiple Fallbacks
πŸ€“ Support for Testing
🦊 Hackable

Table of Contents

Installation

Install the library using Angular CLI:

ng add @ngneat/transloco

Demo

As part of the installation process you'll be presented with questions; Once you answer them, everything you need will automatically be created for you. Let's take a closer look at the generated files:

First, Transloco creates boilerplate files for the requested translations:

// assets/i18n/en.json
{
  "hello": "transloco en",
  "dynamic": "transloco {{value}}"
}
// assets/i18n/es.json
{
  "hello": "transloco es",
  "dynamic": "transloco {{value}}"
}

Next, it injects the TranslocoModule into the AppModule, and sets some default options for you:

// app.module
import { TRANSLOCO_CONFIG, TranslocoModule } from '@ngneat/transloco';
import { HttpClientModule } from '@angular/common/http';
import { httpLoader } from './loaders/http.loader';
import { environment } from '../environments/environment';

@NgModule({
  imports: [TranslocoModule, HttpClientModule],
  providers: [
    httpLoader,
    {
      provide: TRANSLOCO_CONFIG,
      useValue: {
        prodMode: environment.production,
        listenToLangChange: true,
        defaultLang: 'en'
      }
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Config Options

Let's explain each one of the config options:

  • listenToLangChange: Subrscribes to the language change event, and allows you to change the active language. This is not needed in applications that don't allow the user to change the language in runtime (i.e., from a dropdown), so by setting it to false in these cases, you can save on memory by rendering the view once, and unsubscribing from the language changes event (defaults to false).
  • defaultLang: Sets the default language
  • fallbackLang: Sets the default language/s to use as a fallback. See the TranslocoFallbackStrategy section if you need to customize it. failedRetries: How many time should Transloco retry to load translation files, in case of a load failure (defaults to 2)
  • prodMode: Whether the application runs in production mode (defaults to false).

It also injects the httpLoader into the AppModule providers:

import { HttpClient } from '@angular/common/http';
import { Translation, TRANSLOCO_LOADER, TranslocoLoader } from '@ngneat/transloco';
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class HttpLoader implements TranslocoLoader {
  constructor(private http: HttpClient) {}

  getTranslation(langPath: string) {
    return this.http.get<Translation>(`/assets/i18n/${langPath}.json`);
  }
}

export const httpLoader = { provide: TRANSLOCO_LOADER, useClass: HttpLoader };

The HttpLoader is a class that implements the TranslocoLoader interface. It's responsible for instructing transloco how to load the translation files. It uses Angular HTTP client to fetch the files, based on the given path (We'll see why it called path on the lazy load section).

Translation in the Template

Transloco provides three ways to translate your templates:

Using the Structural Directive

This is the recommended approach. It's DRY and efficient, as it creates one subscription per template:

<ng-container *transloco="let t">
  <ul>
    <li>{{ t.home }}</li>
    <li>{{ t.alert | translocoParams: { value: dynamic } }}</li>
  </ul>
</ng-container>

<ng-template transloco let-t>
  {{ t.home }}
</ng-template>

Using the read input

You can use the read input in your structural directive to get translations of a particular nested (including deeply nested) property.

Let's say you need to use the dashboard scope all over the template. Given this translation object:

{
  common: {
    foo: 'Foo',
    bar: 'Bar'
  },
  dashboard: {
    title: 'Title',
    desc: 'Desc'
  }
}

you can do:

<ng-container *transloco="let t; read: 'dashboard'">
  <h1>{{ t.title }}</h1>
  <p>{{ t.desc }}</p>
</ng-container>

without having to repeat the dashboard key in each translation.

Using the Attribute Directive

<ul>
  <li><span transloco="home"></span></li>
  <li>
    <span transloco="alert" [translocoParams]="{ value: dynamic }"></span>
  </li>
  <li><span [transloco]="key"></span></li>
</ul>

Using the Pipe

<span>{{ 'home' | transloco }}</span> <span>{{ 'alert' | transloco: { value: dynamic } }}</span>

<span [attr.alt]="'hello' | transloco">Attribute</span>
<span [title]="'hello' | transloco">Property</span>

Programmatical Translation

Sometimes you may need to translate a key in a component or a service. To do so, you can inject the TranslocoService and use its translate method:

export class AppComponent {
  constructor(private service: TranslocoService) {}

  ngOnInit() {
    // Please read the accompanying note before using it
    this.service.translate('hello');
    this.service.translate('hello', { value: 'world' });
    this.service.translate(['hello', 'key']);
    this.service.translate('hello', params, 'en');
    this.service.translate<T>(translate => translate.someKey);
  }
}

Note that in order to safely use this method, you are responsible for ensuring that the translation files have been successfully loaded by the time it's called. If you aren't sure, you can use the selectTranslate() method instead:

this.service.selectTranslate('hello').subscribe(value => ...);
this.service.selectTranslate('hello', params, lang).subscribe(value => ...);

// When quering an object that should be transpiled
// For example: { a: { b: 'Hello {{ value }}', c: 'Hey {{ dynamic }}' }}
this.service.selectTranslate('a', {
  'b': { value: '' },
  'c': { dynamic: '' }
}).subscribe(obj => ...);

// Returns the active language translation
this.service.selectTranslation().subscribe(translation => ...);

// Load and returns the provided language
this.service.selectTranslation('es').subscribe(translation => ...);

Service API

  • getDefaultLang - Returns the default language
  • setDefaultLang - Sets the default language
  • getActiveLang - Gets the current active language
  • setActiveLang - Sets the current active language
service.setActiveLang(lang);
  • getTranslation(lang?: string) - Returns the selected language translation or, if a language isn't passed, all of them:
service.getTranslation();
service.getTranslation('en');
  • setTranslation() : Manually sets a translations object to be used for a given language, set merge to true if you want to append the translations instead of replacing them.
service.setTranslation({ ... }); // defaults to active language
service.setTranslation({ ... }, 'es');
service.setTranslation({ ... }, 'en', { merge: false } );
  • setTranslationKey - Sets the translated value of a key. If a language isn't specified in the third parameter, it sets the key value for the current active language:
service.setTranslationKey('key', 'value');
service.setTranslationKey('key.nested', 'value');
service.setTranslationKey('key.nested', 'value', 'en');
  • langChanges$ - Listens to the language change event:
service.langChanges$.subscribe(lang => lang);
  • events$ - Listens to the translation loading events:
service.events$.pipe(filter(e => e.type === 'translationLoadSuccess')).subscribe(payload => payload.lang);

service.events$.pipe(filter(e => e.type === 'translationLoadFailure')).subscribe(payload => payload.lang);
  • load(lang) - Load the given language, and add it to the service
service.load('en').subscribe();

Lazy Load Translation Files

Scope Configuration

Let's say we have a todos page and we want to create separate translation files for this page, and load them only when the user navigates there. First, we need to create a todos folder (or whatever name you choose); In it, we create a translation file for each language we want to support:

β”œβ”€ i18n/
   β”œβ”€ en.json
   β”œβ”€ es.json
   β”œβ”€ todos/
      β”œβ”€ en.json
      β”œβ”€ es.json

There are 3 levels of setting the translation scope:

  1. We can set it inside the lazy module module providers :
const routes: Routes = [
  {
    path: '',
    component: TodosComponent
  }
];

@NgModule({
  declarations: [TodosComponent],
  providers: [{ provide: TRANSLOCO_SCOPE, useValue: 'todos' }],
  imports: [CommonModule, RouterModule.forChild(routes), TranslocoModule]
})
export class TodosModule {}
  1. We can set it in a component's providers:
@Component({
  selector: 'my-comp',
  templateUrl: './my-comp.component.html',
  providers: [
    {
      provide: TRANSLOCO_SCOPE,
      useValue: 'todos'
    }
  ]
})
export class MyComponent {}
  1. We can set the scope input in the transloco structural directive:
<ng-container *transloco="let t; scope: 'todos';">
  <h1>{{ t.todos.keyFromTodo }}</h1>
</ng-container>

Each one of these options tells Transloco to load the corresponding scope based on the active language and merge it under the scope namespace into the active language translation object.

For example, if the active language is en, it will load the todos/en.json file, and will set the response to be the following:

{
  header: '',
  login: '',
  todos: {
    submit: '',
    title: ''
  }
}

Now we can access each one of the todos keys by using the todos namespace:

{{ 'todos.title' | transloco }}

<span transloco="toods.submit"></span>

By default, the namespace will be the scope name (camel cased), but we can override it by using the config.scopeMapping config:

{
  provide: TRANSLOCO_CONFIG,
   useValue: {
    defaultLang: 'en',
    scopeMapping: {
      todos: 'customName'
    }
  }
}

Now we can access it through customName instead of the original scope name (todos in our case):

{{ 'customName.title' | transloco }}

<span transloco="customName.submit"></span>

Note that to use it in the current version (1.x.x), we need to set config.scopeStrategy to shared. In the next major release, it will be the default.

Programmatically Translations for Scopes

Since TranslocoService is a singleton each time we need to programmatically translate a scope key, we have to specify it in translate method.

Translating scope key of active language:

this.service.translate('title', {}, 'my-scope|scoped');

You could also get translation scope of a specific language:

this.service.translate('title', {}, 'my-scope/en');

Using Multiple Languages Simultaneously

There are times you may need to use a different language in a specific part of the template, or in a particular component or module. This can be achieved in a similar way to the previous example, except here set the TRANSLOCO_LANG provider either in lazy module providers list, the component providers or in the template.

Here's an example of setting it in a component's providers:

@Component({
  selector: 'my-comp',
  templateUrl: './my-comp.component.html',
  providers: [
    {
      provide: TRANSLOCO_LANG,
      useValue: 'es'
    }
  ]
})
export class MyComponent {}

Using Angular's DI rules, this will ensure that the language in this component's template and all of its children's templates is es.

Alternatively, here is how to use it directly in the template:

<ng-container *transloco="let t; lang: 'en'">
  <p>Inline (en) wins: {{ t.home }}</p>
</ng-container>

Note that it will be used as the initial language. If you need it to be static, you can use the static pipe: en|static.

Custom Loading Template

Transloco provides you with a way to define a loading template, that will be used while the translation file is loading.

Similarly to the previous examples, set the TRANSLOCO_LOADING_TEMPLATE provider either in lazy module providers, component providers, in the template, or even in the app.module itself (affecting the entire app). For example:

@Component({
  selector: 'my-comp',
  templateUrl: './my-comp.component.html',
  providers: [
    {
      provide: TRANSLOCO_LOADING_TEMPLATE,
      useValue: '<p>loading...</p>'
    }
  ]
})
export class MyComponent {}

It can take a raw HTML value, or a custom Angular component.

Alternatively, here is how to use it directly in the template:

<ng-container *transloco="let t; loadingTpl: loading">
  <h1>{{ t.title }}</h1>
</ng-container>

<ng-template #loading>
  <h1>Loading...</h1>
</ng-template>

Hack the Library

Transloco provides you with an option to customize each one of its buliding blocks. Here's a list of the things you can customize:

Transloco Loader

The loader provides you with the ability to override the default handling of translation file loading.

export class CustomLoader implements TranslocoLoader {
  getTranslation(lang: string): Observable<Translation> | Promise<Translation> {
    if(langInLocalStorage) {
      return of(langFromStorage);
    }

    return ...
  }
}

export const custom = {
  provide: TRANSLOCO_LOADER,
  useClass: CustomLoader
}

Transloco Interceptor

The interceptor provides you with the ability to manipulate the translation object before it is saved by the service.

export class CustomInterceptor implements TranslocoInterceptor {
  preSaveTranslation(translation: Translation, lang: string): Translation {
    return translation;
  }

  preSaveTranslationKey(key: string, value: string, lang: string): string {
    return value;
  }
}

export const custom = {
  provide: TRANSLOCO_INTERCEPTOR,
  useClass: CustomInterceptor
};

The preSaveTranslation method is called before the translation is saved by the service, and the preSaveTranslationKey is called before a new key-value pair is saved by the service.setTranslationKey() method.

Transloco Transpiler

The transpiler is responsible for resolving the given value. For example, the default transpiler transpiles Hello {{ key }} and replaces the dynamic variable key based on the given params, or the translation object.

export class CustomTranspiler implements TranslocoTranspiler {
  transpile(value: any, params, translation: Translation): any {
    return ...;
  }
}

export const custom = {
  provide: TRANSLOCO_TRANSPILER,
  useClass: CustomTranspiler
}

Transloco Missing Handler

This handler is responsible for handling missing keys. The default handler calls console.warn() with the key when config.isProdMode is set to false, and returns an empty string to use as a replacement for the missing key's value.

export class CustomHandler implements TranslocoMissingHandler {
  handle(key: string, params: HashMap, config: TranslocoConfig) {
    return '...';
  }
}

export const custom = {
  provide: TRANSLOCO_MISSING_HANDLER,
  useClass: CustomHandler
};

Note: The missing handler is not supported when using structural directive.

Transloco Fallback Strategy

The fallback strategy is responsible for loading the fallback translation file, when the selected active language has failed to load. The default behavior is to load the language set in the config.fallbackLang, and set it as the new active language.

When you need more control over this functionality, you can define your own strategy:

export class CustomFallbackStrategy implements TranslocoFallbackStrategy {
  getNextLangs(failedLang: string) {
    return ['langOne', 'langTwo', 'langThree'];
  }
}

export const custom = {
  provide: TRANSLOCO_FALLBACK_STRATEGY,
  useClass: CustomFallbackStrategy
};

The getNextLangs method is called with the failed language, and should return an array containing the next languages to load, in order of preference.

SSR Support

Create a new CLI project and add SSR support:

ng add @nguniversal/express-engine --clientProject <PROJECT-NAME>

When employing Angular SSR, we need to change our loader base path to be absolute instead of relative, in order for it to work. Run ng add @ngneat/transloco and choose the SSR option. This will make sure to update the loader to use an absolute path.

Moreover, Transloco will add a baseUrl key to the environment object. Make sure to update it based on your environments.

export const environment = {
  production: false,
  baseUrl: 'http://localhost:4200' <====
};

Prefetch the User Language

We recommend pre-emptively fetching the user’s data from the server, including internationalization settings, and making it available to the components, before we allow the user to interact with them.

We want to ensure the data is available, because we don’t want to incur a bad user experience, such as jumpy content or flickering CSS.

Here's how you can achieve this using the APP_INITIALIZER token:

import { APP_INITIALIZER } from '@angular/core';
import { UserService } from './user.service';
import { TranslocoService } from '@ngneat/transloco';

export function preloadUser(userService: UserService, transloco: TranslocoService) {
  return function() {
    return userService.getUser().then(({ lang }) => {
      transloco.setActiveLang(lang);
      return transloco.load(lang).toPromise();
    }
  };
}

export const preLoad = {
  provide: APP_INITIALIZER,
  multi: true,
  useFactory: preloadUser,
  deps: [UserService, TranslocoService]
};

This will make sure the application doesn't bootstrap before Transloco loads the translation file based on the current user's language.

You can read more about it in this article.

Unit Testing

When running specs, we want to have the languages available immediately, in a synchronous fashion. Transloco provides you with a TranslocoTestingModule, where you can pass the languages you need in your specs. For example:

import { TranslocoTestingModule } from '@ngneat/transloco';
import en from '../../assets/i18n/en.json';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        TranslocoTestingModule.withLangs({
          en
        }, translocoConfig?)
      ],
      declarations: [AppComponent]
    }).compileComponents();
  }));

  it('should work', function() {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    expect(fixture.debugElement.query(By.css('h1')).nativeElement.innerText).toBe('hello');
  });
});

Note that in order to import JSON files, you need to configure the TypeScript compiler by adding the following properties in tsconfig.json:

{
  "resolveJsonModule": true,
  "esModuleInterop": true
}

Additional Functionality

  • You can point to specific keys in other keys from the same translation file. For example:
{
  "alert": "alert {{value}} english",
  "home": "home english",
  "fromList": "from {{home}}"
}

So the result of service.translate('fromList') will be: "from home english".

  • You don't have to inject the service each time you need to translate a key. Transloco has an exported translate() function:
import { translate } from '@ngneat/transloco';

translate('someKey');
  • getBrowserLang() - Returns the language code name from the browser, e.g. "en"
  • getBrowserCultureLang() - Returns the culture language code name from the browser, e.g. "en-US"
import { getBrowserLang, getBrowserCultureLang } from '@ngneat/transloco';

Migration from ngx-translate

Transloco provides a schematics command that will help you with the migration process.

Comparison to other libraries

Feature @ngneat/transloco @ngx-translate/core Angular i18n
Actively Maintained βœ… ❌ See here βœ…
Runtime Lang Change βœ… βœ… ❌
listenToLangChange βœ… ❌ ❌
Schematics βœ… ❌ ❌
Custom Loading Template βœ… ❌ ❌
Multiple Languages Simultaneously βœ… βœ…* ❌
Lazy Load Translations βœ… βœ…* βœ…
Multiple Fallbacks βœ… ❌ ❌
Hackable βœ… βœ… ❌
Testing βœ… βœ… External library ❌
Structural Directive βœ… ❌ ❌
Attribute Directive βœ… βœ… βœ…
Pipe βœ… βœ… ❌
Ivy support βœ… ❌ See here βœ…
Additional Functionality βœ… See here ❌ ❌
Pluralization βœ… Official Plugin βœ… External library βœ…

(*) Works only by creating a new service instance and mark it as isolated, and it's not supported at the directive level.

Plugins

Support

For any questions or deliberations join our Gitter channel

Core Team

Netanel Basal
Netanel Basal

Shahar Kazaz
Shahar Kazaz

Itay Oded
Itay Oded

Contributors ✨

Thanks goes to these wonderful people (emoji key):

Rustam
Rustam

πŸ“–
Colum Ferry
Colum Ferry

πŸ’» πŸ“– πŸ€” ⚠️ πŸ“
Levent Arman Γ–zak
Levent Arman Γ–zak

πŸ’»
Inbal Sinai
Inbal Sinai

πŸ“–
Lars Kniep
Lars Kniep

πŸ’» πŸ€”
AleΕ‘
AleΕ‘

πŸ’» πŸ€”
Koala
Koala

πŸ“–
Oleg Teterin
Oleg Teterin

πŸ’»

This project follows the all-contributors specification. Contributions of any kind welcome!

You can’t perform that action at this time.