GithubHelp home page GithubHelp logo

ngstack / translate Goto Github PK

View Code? Open in Web Editor NEW
22.0 7.0 2.0 4.3 MB

Translation library for Angular and Ionic applications.

License: MIT License

JavaScript 4.56% TypeScript 92.19% CSS 0.19% HTML 3.05%
angular i18n translation angular-component translation-library typescript internationalization

translate's Introduction

translate

Lightweight (±3KB) translation library for Angular applications.

Live Demo

Buy Me A Coffee

Table of Contents

Installing

npm install @ngstack/translate

Using with the application

Create en.json file in the src/app/assets/i18n folder of your application.

{
  "TITLE": "Hello from NgStack/translate!"
}

Import TranslateModule into you main application module, configure TranslateService to load during application startup.

You will also need HttpClientModule module dependency.

import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { TranslateModule } from '@ngstack/translate';

// needed to load translation before application starts
export function setupTranslateService(service: TranslateService) {
  return () => service.load();
}

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule,
    TranslateModule.forRoot({
      // options
    })
  ],
  providers: [
    // needed to load translation before application starts
    {
      provide: APP_INITIALIZER,
      useFactory: setupTranslateService,
      deps: [TranslateService],
      multi: true
    }
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

In the main application template, use the following snippet:

<h2>
  {{ 'TITLE' | translate }}
</h2>

Features

Translate Pipe

  • <element>{{ 'KEY' | translate }}</element>
  • <element [attribute]="property | translate"></element>
  • <element attribute="{{ property | translate }}"></element>
  • <element [innerHTML]="'KEY' | translate"></element>
  • <element>{{ 'PROPERTY.PATH' | translate }}</element>
  • <element>{{ 'FORMAT' | translate:params }}</element>
  • (2.3.0) <element [translate]="'KEY'"></element>
  • (2.3.0) <element [translate]="'FORMAT'" [translateParams]="{ msg: hello }"></element>
  • (2.3.0) <element translate="KEY"></element>

Title Service

  • Sets page title value with automatic translation
  • Watches for language changes and updates the title accordingly

Translating application title

Update the localization files for your application and add APP.TITLE resource key:

{
  "APP": {
    "TITLE": "My Application"
  }
}

Update the title from the code, the main application component is a perfect place for that:

import { TitleService } from '@ngstack/translate';

@Component({...})
export class AppComponent implements OnInit {
  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.setTitle('APP.TITLE');
  }
}

Now every time the language is changed, the page title is going to get changed automatically.

Translate Service

  • Load translations on language change
  • Translation from code
  • Defining translation data from code
  • Merging multiple translations
  • Loading translations from multiple locations
  • Automatic fallback for missing translations
  • Defining supported languages
  • Configurable cache busting
  • Lazy loading support
  • Visual debugging mode to simplify development and testing

Using from code

You can import and use translate service in the code:

@Component({...})
export class MyComponent {

  text: string;

  constructor(translate: TranslateService) {

    this.text = translate.get('SOME.PROPERTY.PATH');

  }

}

Custom language without external files

An example for providing translation data from within the application, without loading external files.

@NgModule({...})
export class AppModule {
  constructor(translate: TranslateService) {
    translate.use('en', {
      'TITLE': 'Hello from @ngstack/translate!'
    });
  }
}

Formatted translations

You can use runtime string substitution when translating text

{
  "FORMATTED": {
    "HELLO_MESSAGE": "Hello, {username}!"
  }
}

Then in the HTML:

<div>{{ 'FORMATTED.HELLO_MESSAGE' | translate:{ 'username': 'world' } }}</div>

Or in the Code:

@Component({...})
export class MyComponent {

  text: string;

  constructor(translate: TranslateService) {

    this.text = translate.get(
      'FORMATTED.HELLO_MESSAGE',
      { username: 'world' }
    );

  }

}

Should produce the following result at runtime:

Hello, world!

You can use multiple values in the format string. Note, however, that TranslateService checks only the top-level properties of the parameter object.

Advanced topics

You can provide custom parameters for the forRoot method of the TranslateModule

interface TranslateSettings {
  debugMode?: boolean;
  disableCache?: boolean;
  supportedLangs?: string[];
  translationRoot?: string;
  translatePaths?: string[];
  activeLang?: string;
}

For example:

TranslateModule.forRoot({
  debugMode: true,
  activeLang: 'fr'
});

Testing components

When testing localisation with a single translation file it is sometimes hard to tell if a component text switches to a different language. You can simplify testing of the end-applications and components by enabling the debug mode.

While in the debug mode, the service automatically prepends active language id to very translated result. That allows to verify that your components support i18n correctly and do not contain hard-coded text.

TranslateModule.forRoot({
  debugMode: true
});

Now, if using en as the active language, all strings should start with the [en] prefix.

Watching for language change

You can watch for language change event utilising the activeLangChanged event:

@Component({...})
export class MyComponent {

  constructor(translate: TranslateService) {

    translate.activeLangChanged.subscribe(
      (event: { previousValue: string; currentValue: string }) => {
        console.log(event.previousValue);
        console.log(event.currentValue);
      }
    );

  }
}

Custom translation path

By default TranslateService loads files stored at assets/i18n folder. You can change the TranslateService.translationRoot property to point to a custom location if needed.

TranslateModule.forRoot({
  translationRoot: '/some/path'
});

Loading from multiple locations

To provide multiple locations use the TranslateService.translatePaths collection property.

TranslateModule.forRoot({
  translatePaths: ['assets/lib1/i18n', 'assets/lib2/i18n']
});

The files are getting fetched and merged in the order of declarations, and applied on the top of the default data loaded from TranslateService.translationRoot path.

Cache busting

You can disable browser caching and force application always load translation files by using TranslateService.disableCache property.

TranslateModule.forRoot({
  disableCache: true
});

Define active language

The service takes browser language as an active language at startup. You can use activeLang property to define a custom value and override browser settings.

TranslateModule.forRoot({
  activeLang: 'fr'
});

Restricting supported languages

It is possible to restrict supported languages to a certain set of values. You can avoid unnecessary HTTP calls by providing TranslateService.supportedLangs values.

TranslateModule.forRoot({
  supportedLangs: ['fr', 'de']
});

The service will try to load resource files only for given set of languages, and will use fallback language for all unspecified values.

By default this property is empty and service will probe all language files. The service always takes into account the Active and Fallback languages, even if you do not specify them in the list.

Using with your own pipes

It is possible to use TranslateService with your own implementations.

You can see the basic pipe implementation below:

import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService, TranslateParams } from '@ngstack/translate';

@Pipe({
  name: 'myTranslate',
  pure: false
})
export class CustomTranslatePipe implements PipeTransform {
  constructor(private translate: TranslateService) {}

  transform(key: string, params?: TranslateParams): string {
    return this.translate.get(key, params);
  }
}

Then in the HTML templates you can use your pipe like following:

<p>
  Custom Pipe: {{ 'TITLE' | myTranslate }}
</p>

Lazy Loading

To enable Lazy Loading use TranslateModule.forRoot() in the main application, and TranslateModule.forChild() in all lazy-loaded feature modules.

For more details please refer to Lazy Loading Feature Modules

See also

List of alternative libraries you can check out:

translate's People

Contributors

denysvuika avatar dependabot-preview[bot] avatar dependabot[bot] avatar mergify[bot] avatar osahner avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

osahner whebz

translate's Issues

Problem with using parameters

This is my translation file's content:

"AccessDenied": {
  "Title": "Access Denied",
  "Message": "Access is denied to user {name}!"
}

This works:

<div class="page">
    <span class="logo"></span>
    <h1>{{'AccessDenied.Title' | translate}}</h1>
    <p>{{'AccessDenied.Message' | translate:{ 'name': account.name } }}</p>
</div>

This doesn't (no space after param closing curly bracket):

<div class="page">
    <span class="logo"></span>
    <h1>{{'AccessDenied.Title' | translate}}</h1>
    <p>{{'AccessDenied.Message' | translate:{ 'name': account.name }}}</p>
</div>

Error thrown by Angular compiler:

Parser Error: Missing expected } at the end of the expression [{{'AccessDenied.Message' | translate:{ 'name': account.name }}}]

This looks like more like an Angular limitation or a bug.

Also, this should be working to, but currently, it's not (added spaces around param for readability):

"AccessDenied": {
  "Title": "Access Denied",
  "Message": "Access is denied to user { name }!"
}

Result:

Param name is not translated.

I presume that some trimming should be executed when parsing params.

Also, when passing param object, single quoting the param name is not necessary, as we are passing an object.

So, this:

<p>{{'AccessDenied.Message' | translate:{ 'name': account.name } }}</p>

can be written like this (no quotes):

<p>{{'AccessDenied.Message' | translate:{ name: account.name } }}</p>

HttpInterceptor not triggering with lazy loaded modules

Custom HttpInterceptor not triggering with lazy loaded modules, when using TranslateModule as stated in docs.

It's a complex project, so I cannot show the example code.

The problem with lazy loaded modules and HttpInterceptor is that HttpClientModule can only be imported once in the project inside app.module.ts. Looking into this library's code, I found that HttpClientModule is imported internally, which, I presume, could cause this problem.

The files to look for these imports are:

projects/translate/src/lib/translate.module.ts
projects/translate/src/lib/translate.pipe.spec.ts
projects/translate/src/lib/title.service.spec.ts
projects/translate/src/lib/translate.service.spec.ts

Take browser culture into account

Provide better fallback for browser cultures. For example, when browser is set to zh-CN and translation file is missing, the probing flow should be:

  1. zh-CN.json
  2. zh.json
  3. en.json (or fallback language of choice)

Consider support for explicit support matrix in configuration/code to avoid useless HTTP calls for non-supported cultures.

Angular 8 support?

Angular 8 is out there. Is Angular 8 support planned any time soon, or should I create a fork?

Improvement suggestions

I've decided to drop my @ngx-translate implementation and turn it into @ngstack/translate implementation. Along the way, I've hit some obstacles, with @ngstack/translate missing some basic features @ngx-translate has. So, I suggest the following improvements:

  1. Translate params should accept numbers, along with strings.
export interface TranslateParams {
    [key: string]: string;
}

should be transformed into:

export interface TranslateParams {
    [key: string]: string | number;
}
  1. get method should translate array of strings, along with string:
get(key: string, params?: TranslateParams, lang?: string): string;

should be transformed into:

get(key: string | string[], params?: TranslateParams, lang?: string): string | string[];

Keep up the good work! :D

Build for production failed

When build an app for production with ng build --prod, the following error is thrown:

ERROR in Error during template compile of 'AppModule' Function calls are not supported in decorators but 'TranslateModule' was called.

Reason is the forRoot() call in the declaration of the main module.

translate pipe transforming not working

//app.module.ts
import { TranslateModule, TranslateService } from '@ngstack/translate';

export function setupTranslateFactory(service: TranslateService): Function {
    return () => service.load();
}

imports: [
    TranslateModule.forRoot({
        debugMode: true,
        activeLang: 'en'
    })
]

providers: [
{ provide: APP_INITIALIZER, useFactory: setupTranslateFactory, deps: [ TranslateService ], multi: true }
]

// app.component.ts
import { TitleService } from '@ngstack/translate';
export class AppComponent implements OnInit {
    constructor(private titleService: TitleService) {}
    ngOnInit() {
        this.titleService.setTitle('TITLE');
    }
}
//##Document Title is working fine but not in templae LOGIN


{{ 'LOGIN' | translate }}

// package.json
"@angular/core": "^8.2.1"
"@ngstack/translate": "^2.1.0"

TitleService is working, but pipe transforming is not. Just wondering am I missing anything.

Support for routes

Hello,
Do you have any plans on supporting routes? I'm looking for an alternative for localize-router since it seems abandoned and has a bunch of bugs. Just wondering if I should keep an eye open for changes to this project.

Compile failed because of unknown pipe

I'm using ngstack/translate in an Angular/Ionic project. Although the application works, I do get errors in the IDE (IntelliJ), telling me that the pipe "translate" in unknown.

Now I tried the new Ivy renderer and do get the same error messages:

ERROR in : Template parse errors: The pipe 'translate' could not be found ("<ion-header> <ion-toolbar> <ion-title>{{[ERROR ->]'Edit' | translate}}</ion-title> </ion-toolbar> </ion-header>

I can ignore the IDE warnings, although they are annoying. But it would be very nice to be able to use the upcoming Angular compiler.

Thanks in advance, Heiner

Translations default to default language until language file is loaded

I use three languages: en, sr-Latn and hr, with en being the default one.
When I switch to any language other than the default one, the translations are loaded nicely. But, then, when I switch to the third, non-default language, the translations show the default language translations briefly, until the new language file is loaded.

So en => sr-Latn => OK
So en => hr => OK
So en => sr-Latn => hr => Glitch
So en => hr => sr-Latn => Glitch

How do I extend TranslateService to add local storage support?

To add support for storing/restoring a preferred language into/from local storage, I extended the TranslateService like this:

import { HttpClient } from '@angular/common/http';
import { EventEmitter, Inject, Injectable } from '@angular/core';
import { Settings } from '@app/app.settings';
import { Languages } from '@app/shared/models/language.model';
import { TranslateService, TranslateSettings, TRANSLATE_SETTINGS } from '@ngstack/translate';
import { LocalStorageUtil } from '@shared/helpers/local.storage.util';

@Injectable({
  providedIn: 'root'
})
export class LanguageService extends TranslateService {
  static readonly Language = Settings.Prefix + 'language';

  constructor(http: HttpClient, @Inject(TRANSLATE_SETTINGS) settings: TranslateSettings, private ls: LocalStorageUtil) {
    super(http, settings);
  }

  /**
   * The prefered language to use for the translations.
   */
  get preferedLanguage(): string {
    const defaultLanguage = Languages.find(l => l.default).i18n;
    const language = this.ls.readDecompressed(LanguageService.Language);
    return language || defaultLanguage;
  }

  /**
   * The language to use for the translations.
   */
  get language(): string {
    return this.activeLang;
  }

  /**
   * The language to use for the translations.
   * @param language Language to use for translation
   */
  set language(language: string) {
    this.activeLang = language;
    this.ls.saveCompressed(LanguageService.Language, language);
  }

  /**
   * Raised each time active language gets changed.
   */
  get language$(): EventEmitter<{
    previousValue: string;
    currentValue: string;
  }> {
    return this.activeLangChanged;
  }
}

The core module settings are the following:

import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { MatPaginatorIntl, MatSidenavModule } from '@angular/material';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router';
import { AppRoutingModule } from '@app/app-routing.module';
import { AvatarModule } from '@app/layout/avatar/avatar.module';
import { MainSidebarContentModule } from '@app/layout/main-sidebar-content/main-sidebar-content.module';
import { TopbarModule } from '@app/layout/topbar/topbar.module';
import { CodemirrorModule } from '@app/shared/components/codemirror/codemirror.module';
import { DataTablePaginatorIntl } from '@app/shared/components/data-table/data-table.paginator';
import { LoaderModule } from '@app/shared/components/loader/loader.module';
import { Languages } from '@app/shared/models/language.model';
import { WildcardRoutingModule } from '@app/wildcard-routing.module';
import { TranslateModule, TranslateService } from '@ngstack/translate';
import 'hammerjs';
import { LoaderInterceptor } from './interceptors/loader.interceptor';
import { TokenInterceptor } from './interceptors/token.interceptor';

// AoT requires an exported function for factories
export function setupTranslateService(service: TranslateService) {
  return () => service.load();
}

@NgModule({
  imports: [
    // Angular modules
    BrowserModule,
    BrowserAnimationsModule,
    RouterModule,
    AppRoutingModule,
    HttpClientModule,

    // Material modules
    MatSidenavModule,

    // Translate module
    TranslateModule.forRoot({
      translationRoot: 'assets/i18n',
      supportedLangs: Languages.map(language => language.i18n),
      activeLang: Languages.find(language => language.default).i18n
    }),

    // Loader module
    LoaderModule,

    // Layout modules
    TopbarModule,
    MainSidebarContentModule,
    AvatarModule,

    // CodeMirror
    CodemirrorModule,

    // WILDCARD ROUTER MUST BE AT LAST POSITION
    WildcardRoutingModule
  ],
  declarations: [],
  entryComponents: [],
  exports: [
    RouterModule,
    HttpClientModule,
    MatSidenavModule,
    LoaderModule,
    TopbarModule,
    MainSidebarContentModule,
    AvatarModule
  ],
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: setupTranslateService,
      deps: [TranslateService],
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: LoaderInterceptor,
      multi: true
    },
    { provide: MatPaginatorIntl, useClass: DataTablePaginatorIntl }
  ]
})
export class CoreModule {}

The activeLangChanged event emitter fires on a button click, with activeLang change, but the page doesn't get translated into selected language. Turning on the debugMode, I can see that the active language stays default. Tracking down the network activities, I can see that language files get loaded, but the page remains translated into default language.

Am I missing something here?

Attribute directive

Provide the attribute directive support similar to the following:

<div [translate]="key"></div>

Should provide translation into the host element content.

[QUESTION] Library-Monorepo problem

Thank you very much for this project, it is very good.

I have a question, how could I use in @ngstack/translate a library, and use that library in an application that also uses @ngstack/translate?

This library can be lazy loaded or it can be loaded normally. I wish it could work for both cases.
Of course, this use case is a common when you use Monorepo in your company.
I also have the need for the library to be publishable, so it should have its own translation

Allow to specify fallback language

Currently the fallback language is hard coded to "en". In my application I use locales instead of language abbreviation (ex: "en-US").

I'd like to be able to change the fallback language to "en-US" to fallback to when there isn't a match.

Maybe a new config property that can be passed to forRoot()

Something like:

TranslateModule.forRoot({
  activeLang: 'en-US',
  fallbackLang: 'en-US',
  supportedLangs: [
    // ...
  ]
}),

Multiple files per language

Is it possible to split a single language translation file into multiple .json files (per module or component)?

Single language file can get really huge for big apps and difficult to maintain.

Setting .json file to be loaded dynamically in routes would be an extra feature.

Wrong version number

Hi,

the version 1.2.2 is not backwards-compatible anymore due to the upgrade to Angular 8.

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.