Coder Social home page Coder Social logo

gep-johngouda-zz / transloco Goto Github PK

View Code? Open in Web Editor NEW

This project forked from jsverse/transloco

0.0 0.0 0.0 2.82 MB

πŸš€ 😍 Angular Internationalization DoneΒ Right

Home Page: https://transloco.netlify.com/

License: MIT License

JavaScript 3.98% TypeScript 88.24% HTML 7.22% CSS 0.56%

transloco's Introduction


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 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>

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() {
    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').subscribe(value => {}, 'es');

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 current 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

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 current language and merge it under the scope namespace into the active language translation object.

For example, if the current 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 (capitalized), 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.

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>

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: string, params, translation: Translation): string {
    return ...;
  }
}

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

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.

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.

MessageFormat Support

The library comes with support for messageformat.
Messageformat is a mechanism for handling both pluralization and gender in your app.

You can see its format guide here.

Then add the following to the providers array in your app.module.ts:

import { MessageFormatTranspiler } from '@ngneat/transloco';

...

@NgModule({
  providers: [
    ...,
    { provide: TRANSLOCO_TRANSPILER, useClass: MessageFormatTranspiler }
  ]
})

The MessageFormatTranspiler is compatible with the DefaultTranspiler and therefore you can switch without worry that it will break your current translations.

It then enables support for the following within your i18n translation files:

{
  "mySelectRule": "{myVar, select, val1 {Value 1} val2 {Value 2} other {Other Value}}",
  "myPluralRule": "{myCount, plural, =0 {no results} one {1 result} other {# results}}"
}

Unit Testing

When running specs, we want the 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
        })
      ],
      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');
  });
});

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 βœ… βœ…* ❌
Lazy Load Translations βœ… βœ…* βœ…
Multiple Fallbacks βœ… ❌ ❌
Hackable βœ… βœ… ❌
Testing βœ… βœ… External library ❌
Structural Directive βœ… ❌ ❌
Attribute Directive βœ… βœ… βœ…
Pipe βœ… βœ… ❌
Ivy support βœ… ❌ See here βœ…
Additional Functionality βœ… See here ❌ ❌
Pluralization βœ… βœ… External library βœ…
Plugins WIP βœ… See here ❌

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

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

πŸ’» πŸ€”

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

transloco's People

Contributors

armanozak avatar coly010 avatar irustm avatar itayod avatar netanelbasal avatar shaharkazaz avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.