Custom Angular library with ngx-translate
Asked Answered
C

4

10

I've created my own private angular library with multiple components to make my life easier. I made this library by following this tutorial. Everything worked out great I even added it to another project to test it out.

Now comes the part where I have to support multiple languages because I live in Belgium πŸ˜‹. I've used the ngx-translate package in the past without any problem so i thought this would be easy.

I added an assets/i18n folder with the translation json's in my project.enter image description here

I've found out that angular cli doesn't include the assets on build so you have to add them manually or with gulp to the dist folder before making the package. After doing that I noticed that the labels were not being translated unless I included the translation to the json files of the Main application.

Each component in my library has its own module so I thought I just needed to add the translation module in those modules. So I did the following.

export function httpLoaderFactory(http: HttpClient) {
    return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}

@NgModule({
    imports: [
        CommonModule,
        FormsModule,
        DirectivesModule,
        ButtonModule,
        IconModule,
        PillModule,
        TranslateModule.forChild({
            loader: {
                provide: TranslateLoader,
                useFactory: (httpLoaderFactory),
                deps: [HttpClient]
            },
            isolate: true
        })
    ],
    declarations: [
        FilterComponent
    ],
    exports: [
        FilterComponent
    ]
})

This made things even worse, not only were the labels still not being translated, my main applications didn't even use its own translations. The main application didn't have a problem with its translations before those changes in the library modules...

So yeah you guessed it, I am stuck... I can't seem to find the right solution.

Cuthbert answered 29/12, 2018 at 18:20 Comment(2)
Hello! I understand your problem. I have a doubt about having directly the translation in your library component. You become dependent of your library update. Is it possible to pass the translated words/sentences as @Input ? – Mesitylene
I know what you mean but the translations are for aria-labels, titles on anchor tags (only visible on hover). Plus, this library is only for personal use so I want a self-supporting library which just works out of the box. – Cuthbert
C
12

Because I was getting nowhere I tried out another approach as described in this post. So I converted my json files to ts files returning a json object. I then created my own translateService which adds the translations on top of the existing one's (those added by the json files of the main application) as described in the post.

This worked but overruled the previous translations or even loaded too late. This resulted in the application just showing the translationkeys instead of the translation. So instead of initializing the translations like in the post, I used a subscribe to wait on the main translations first.

//Library
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { en } from "../localization/en";
import { nl } from "../localization/nl";

@Injectable()
export class TranslateUiService {
    private availableLanguages = { en, nl };

    constructor(private translateService: TranslateService) {
    }

    public init(language: string = null): any {
        if (language) {
            //initialize one specific language
            this.translateService.setTranslation(language, this.availableLanguages[language], true);
        } else {
            //initialize all
            Object.keys(this.availableLanguages).forEach((language) => {
                this.translateService.setTranslation(language, this.availableLanguages[language], true);
            });
        }
    }
}

//App
constructor(private translateUiService: TranslateUiService, private translateService: TranslateService) {
        this.translateService.setDefaultLang('en');
        this.translateService.use('en').subscribe(() => {
            this.translateUiService.init('en');
        });
    }
Cuthbert answered 30/12, 2018 at 11:52 Comment(3)
"this.translateService.setTranslation(...)" with the third parameter merge=true was the trick. The module-config isolated=true didn't work at all for me. And "merge" was the next best solution for me. – Manasseh
This approach is not working as expected - at least for me. Languages are merged and so not isolated. If library contains a translation key "hello", and the project using it has the same translation key, one of these translations will be overrided. – Guillema
You should add a prefix to your keys of the package to prevent this. – Cuthbert
P
1

I did it in a similar way as Beejee did, but I extended the translation service. In the extension of the TranslateService I add the library's translation under a sublevel (ui.[language]) of the global app's translations as we use the same instance as the root app does and we don't want to override translations of the root app. Then I provided the extension instead of the normal TranslateService at component level so it is used even for the translate directive within this component and it is isolated, meaning that we don't destroy root app's translation by overriding the getters for currentLang and defautlLang.

ui-translate.service.ts:


const availableLanguages: any = {
  'de' : { ... YOUR DE TRANSLATIONS ... },
  'en' : { ... YOUR EN TRANSLATIONS ... }
  ...
};
const langPrefix = 'ui';

@Injectable({
  providedIn: 'root'
})
export class UiTranslateService extends TranslateService implements TranslateService {


  constructor(public store: TranslateStore,
              public currentLoader: TranslateLoader,
              public compiler: TranslateCompiler,
              public parser: TranslateParser,
              public missingTranslationHandler: MissingTranslationHandler,
              @Inject(USE_DEFAULT_LANG) useDefaultLang: boolean = true,
              @Inject(USE_STORE) isolate: boolean = false) {
    super(store, currentLoader, compiler, parser, missingTranslationHandler, useDefaultLang, isolate);

    this.onTranslationChange.subscribe((_res: TranslationChangeEvent) => {
      // ensure after translation change we (re-)add our translations
      if (_res.lang.indexOf(langPrefix) !== 0) {
        this.applyUiTranslations();
      }
    });

    this.applyUiTranslations();
  }

  private applyUiTranslations() {
    for (var lang in availableLanguages) {
      if (availableLanguages.hasOwnProperty(lang)) {
        this.setTranslation(langPrefix + '.' + lang, availableLanguages[lang], true);
      }
    }
  }

  /**
   * The default lang to fallback when translations are missing on the current lang
   */
  get defaultLang(): string {
    return langPrefix + '.' + this.store.defaultLang;
  }

  /**
   * The lang currently used
   */
  get currentLang(): string {
    return this.store.currentLang === undefined ? undefined : langPrefix + '.' + this.store.currentLang;
  }

  public getParsedResult(translations: any, key: any, interpolateParams?: Object): any {
    // apply our translations here
    var lang = (this.currentLang || this.defaultLang).split('.')[1];
    translations = lang == 'de' ? de : en;
    return super.getParsedResult(translations, key, interpolateParams);
  }
  public get(key: string | Array<string>, interpolateParams?: Object): Observable<string | any> {
    return super.get(key, interpolateParams);
  }
}

my.component.ts:

    @Component({
        selector: 'xxx',
        template: 'xxx',
        providers: [
            { provide: TranslateService, useClass: UiTranslateService }
        ]
    })
    export class MyComponent implements OnInit { }

my.module.ts:

    @NgModule({
        imports: [
            CommonModule,
            TranslateModule
        ]
     })
     export class ComponentsModule {}
Paba answered 3/7, 2019 at 13:4 Comment(1)
i cant get it to work i've added everything as expected but the page presents the translation key – Prichard
O
0

I suspect that your build step is overwriting your main application's en.json, etc with the library's file. Hence missing the main application's translations.

One thing you could consider instead of the isolation option (which will require more requests to the server to load translations) would be to change your build step to merge the files in your library with the main application's translation files.

I would recommend namespacing the library translation keys in some way to avoid any possible collisions.

Then in the library you can just use TranslateModule.forChild()

Alternatively if you want to keep the isolation try putting the library's translation files in a sub-directory of i18n and changing the httpLoaderFactory as appropriate

Ostosis answered 29/12, 2018 at 19:51 Comment(1)
could you explain your answer? I've struggled with the same problem and have tried all these solutions, but either my App does not compile (NullInjectorError) or the lib translations are ignored, – Rinse
T
-2

You can make something like this.

<div (click)="switchLanguage('en')"></div>

switchLanguage(language: string) {
        this.selectedLanguage = language
    }

in Another component just write in HTML something like this.

<button><i class="fa fa-history"></i>{{'general.#time'|translate}}</button>

This is the en.json

"general": {
        "#time": "Time"
}
Tarmac answered 29/12, 2018 at 19:24 Comment(3)
I know how to use it in a normal application but this is in a library wich will be imported in an application – Cuthbert
For making your library I am not able to say what do you need but does it show you errors or you can make some console logs because so it is little hard to achieve that. – Tarmac
It doesn't even close to what is asked here in the question – Belting

© 2022 - 2024 β€” McMap. All rights reserved.