@iplab/ngx-l10n

header.title.description

GitHub

header.selection.title

demo.title

dictionary content:

{}

Description

Pure Angular 2+ localization (l10n) library.

  • It's an Angular functional set (pipe, directive, service) used to localize application
  • Compatible with Angular 2+ versions
  • No external dependency
  • Supported some of the most used localization files: .properties, .po, .json
  • load localization file/s when you need it
  • easily manage localization storage
  • customizable localization loader
  • NativeScript ready check configuration

Getting started
  1. Fork and clone this repo
  2. npm install
  3. Open a terminal and type "npm run start"

or

  1. npm install @iplab/ngx-l10n --save
  2. ng serve --open

Installation

To install this component to an external project, follow the procedure:

  1. npm install @iplab/ngx-l10n --save
  2. import { L10nModule, L10nService } from '@iplab/ngx-l10n';
    ...
    ...
    
    
    @Injectable()
    export class LocalizationResolve implements Resolve<any> {
    
        constructor(private localization: L10nService){
            this.localization.languageChanges.subscribe(({ code }) => {
                /**
                 * second parameter is used if we don't want to merge result set
                 * to previus one, use this only if in memory we want to store only
                 * one language
                 */
                this.localization.setFromFile(`${code}.locales.properties`, false);
            })
        }
    
        public resolve(): Observable<any>|Promise<any> {
            return this.localization.setFromFile(`${this.localization.languageCode}.locales.properties`);
        }
    }
    
    
    
    @NgModule({
        imports: [
            BrowserModule,
            L10nModule.forRoot({ config: {defaultLanguage: LanguageCodes.EnglishUnitedStates} }),
            RouterModule.forRoot([
                { path: '', component: AppComponent, resolve: { localization: LocalizationResolve }}
            ])
        ],
        providers: [LocalizationResolve],
        bootstrap: [AppComponent]
    })
    export class AppModule {}

Configuration

By default, if you want to load .properties, .po or .json files you don't need to configure anything.
But depending on your needs, you can write your own loader, parser or storage provider,
also it is possible to write custom formatter and error handler

import { 
    IL10nLoaderResponse, 
    IL10nFileRequest, 
    L10nBaseLoader,
    L10nBaseStorage,
    L10nBaseParser,
    L10nBaseFormatter,
    L10nBaseErrorHandler,
    LanguageCodes
} from '@iplab/ngx-l10n';
                    

@Injectable()
export class CustomLoader extends L10nBaseLoader {

    constructor( private http: HttpClient ){
        super();
    }

    public getFile({ url, code }: IL10nFileRequest ): Observable<IL10nLoaderResponse> {
        let subject = new Subject<IL10nLoaderResponse>();
        let fileType = this.getFileExtension( url );                           

        ...
        return subject.asObservable();
    }
}


@Injectable()
export class CustomStorage extends L10nBaseStorage {

    public getSentance( key: string ): string {
        return this._dictionary[key];
    }

    public setSentence( key: string, sentence: string ): void {
        this._dictionary[ key ] = sentence;
    }
}


@Injectable()
export class CustomParser extends L10nBaseParser {
    public parse( response: any, fileType: string ): Observable<{ key: string; sentence: string }> {}
}


@Injectable()
export class CustomFormatter extends L10nBaseFormatter {
    public abstract interpolate(sentence: string, args: IL10nArguments): string;
}


@Injectable()
export class CustomErrorHandler extends L10nBaseErrorHandler {
    public abstract handleError(error: any): void;
}


@NgModule({
    imports: [
        BrowserModule,
        // each config property is optional
        L10nModule.forRoot({ 
            loader: CustomLoader, 
            storage: CustomStorage, 
            parser: CustomParser, 
            formatter: CustomFormatter,
            errorHandler: CustomErrorHandler,
            config: { 
                defaultLanguage: LanguageCodes.EnglishUnitedStates, 
                bindingProperty: 'textContent'
            } 
        })
    ],
    providers: [CustomLoader, CustomStorage, CustomParser, CustomFormatter],
    bootstrap: [AppComponent]
})
export class AppModule {}
            

Define the translations

Once you've configurated L10n module, you can put your translations to file/s or use existing one.

.properties

app.hello.key = Hello ${value}

.po

msgid "app.hello.key"
msgstr "Hello ${value}"

.json

{
"app.hello.key": "Hello ${value}"
}

default defined interpolations are ${ } or {{ }} or { }.

Hello ${value}  =>  L10nService.get('app.hello.key', {value: "world"})  =>  Hello world
Hello ${0}      =>  L10nService.get('app.hello.key', ["world"])         =>  Hello world
Hello {value} => L10nService.get('app.hello.key', {value: "world"}) => Hello world Hello {0} => L10nService.get('app.hello.key', ["world"]) => Hello world
Hello {{value}} => L10nService.get('app.hello.key', {value: "world"}) => Hello world Hello {{0}} => L10nService.get('app.hello.key', ["world"]) => Hello world

It is possible to change default interpolations with something custom

import { L10nModule, L10nBaseFormatter, defineInterpolation, IL10nArguments } from '@iplab/ngx-l10n';

@Injectable()
export class CustomFormatter extends L10nBaseFormatter {
    public interpolate(sentence: string, args: IL10nArguments): string {
        return sentence.replace( defineInterpolation(['[[', ']]']), (match, interpolates) => {    
            if (!IsNullOrEmpty(args)) {
                return args[this.trim(interpolates)];
            }

            return match;
        });
    }
}

@NgModule({
    imports: [
        ...,
        L10nModule.forRoot({ 
            formatter: CustomFormatter
        })
    ]
})
export class AppModule {}
app.hello.key = Hello [[value]]

possible is to use other key from dictionary as value

app.world.key = world
app.hello.key = Hello ${app.world.key}

L10nService.get('app.hello.key')  =>  Hello world

Add values to dictionary

when setting up a dictionary it is possible to use three built-in methods, setFromObject(), setFromFile() or set()

L10nService.setFromObject({ 'app.hello.key': 'Hello ${app.hello.world}', 'app.hello.world': 'world' });
L10nService.setFromFile('http://some.url');
L10nService.set('app.hello.key', 'Hello world!');

Service

you can use Ln10 service:

let translation = L10nService.get('app.hello.key', { value: 'world' });
console.log( translation ); => Hello world

it's possible to observe changes on specific key, an observer will also initially return a translated value

let subscription = L10nService.observe('app.hello.key', { value: 'world' })
                              .subscribe((sentence) => { console.log( sentence );  });

Directive

directive by default use HTMLElement.textContent as property for translation,
and this is how you use it:

<div l10n="app.hello.key" [l10n-args]="params"></div>
<div l10n="app.hello.key" [l10n-args]="{value: 'world'}"></div>
<div l10n="app.hello.key" l10n-args="{value: 'world'}"></div>

<div [l10n]="'app.hello.key'" [l10n-args]="params"></div>
<div [l10n]="'app.hello.key'" l10n-args="{value: 'world'}"></div>

l10n-args can be property in component or inline written JSON


Pipe

also easily you can use the pipe

<div>{{'app.hello.key' | l10n:param }}</div>
<div [innerHTML]="'app.hello.key' | l10n"></div>
<div>{{'app.hello.key' | l10n: {'key': 'value'} }}</div>

param can be property in component or inline written JSON


NativeScript

To configure and use with NativeScript is easy

tns plugin add @iplab/ngx-l10n

Now we need to prepare custom File loader and Localization resolver

import { L10nModule, L10nService, L10nBaseLoader } from '@iplab/ngx-l10n';
import { knownFolders } from "file-system";
import { Subject, Observable, from } from 'rxjs';
import { map } from 'rxjs/operators';
import { Resolve } from "@angular/router";
...
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { NativeScriptRouterModule } from "nativescript-angular/router";
...
...

@Injectable()
export class CustomLoader extends L10nBaseLoader {

    private readonly folderName = 'locales'; // folder where you place your locale files, 
                                            // in our case that is locales/en.locales.properties
    private readonly documents = knownFolders.currentApp();
    private readonly folder = this.documents.getFolder(this.folderName);

    public getFile({ url, code }: IL10nFileRequest ): Observable<IL10nLoaderResponse> {
        let fileType = this.getFileExtension( url );                           
        let file = this.folder.getFile(url);

        return from(file.readText())
                .pipe(map((response) => {
                    return { response, fileType }
                }));
    }
}

                                                        
@Injectable()
export class LocalizationResolve implements Resolve<any> {
                            
    constructor(private localization: L10nService){
        this.localization.languageChanges.subscribe(({ code }) => {
            this.localization.setFromFile(`${code}.locales.properties`);
        })
    }
                            
    public resolve(): Observable<any>|Promise<any> {
        return this.localization.setFromFile(`${this.localization.languageCode}.locales.properties`);
    }
}


@NgModule({
    imports: [
        NativeScriptModule,
        L10nModule.forRoot({ 
            config: {defaultLanguage: LanguageCodes.EnglishUnitedStates, bindingProperty: 'text' },
            loader: CustomLoader
        }),
        NativeScriptRouterModule.forRoot([
            { path: '', component: AppComponent, resolve: { localization: LocalizationResolve }}
        ])
    ],
    providers: [
        LocalizationResolve,
        L10nService, // because currently NativeScript doesn't work with @Injectable({ providedIn: 'root' })
        CustomLoader
    ],
    bootstrap: [AppComponent]
})
export class AppModule {}

Use case is same as it is for HTML

<ActionBar title="{{ 'app.header.title' | l10n }}" class="action-bar"></ActionBar>

List of service methods
PropertyTypeDescription
languageSetter.StringUsed to set language
languageGetter.StringReturn language ISO code
languageCodeGetter.StringReturn language code from ISO string
directionGetter.StringReturn direction (ltr|rtl) of the current language
dictionaryGetter.ObjectReturn dictionary object
getFunctionUsed to return localized word
observeFunction.ObservableUsed to return the localized word and observe any changes on it
setFunction.VoidUsed to add word by word to dictionary
setFromObjectFunction.PromiseUsed to add words to dictionary
setFromFileFunction.PromiseUsed to add words to dictionary
clearFunction.VoidUsed to clear dictionary storage and maped urls by loader class
languageChangesGetter.ObservableUsed to observe language change
dictionaryReadyGetter.ObservableUsed to be notified when data are loaded, parsed and mapped in dictionary