Nebular: i18n support

Created on 27 Apr 2018  路  15Comments  路  Source: akveo/nebular

Issue type

I'm submitting a ... (check one with "x")

  • [ ] bug report
  • [x] feature request

Issue description

In my ngx-admin fork, I have setup translation using ngx-translate and it looks like this :
image

My issue is regarding nebular components, because it appears (unless I'm missing something) that the only way to add i18n support to those, is to copy them into my ngx-admin project and add translation from there.
I can help you out with this if you want, but I'm not sure what would be the best way to include it in the package in a clean way

Current behavior:

Nebular components are not translated.

Expected behavior:

There should be a way to change the language at start and on the fly.

Steps to reproduce:

Related code:

Other information:

npm, node, OS, Browser

Angular, Nebular

urgent theme needs docs needs investigation

Most helpful comment

@ruisebastiao

I've used your approach to solve the problem and came up with a refinement which may be useful to you.

import { NbMenuItem } from '@nebular/theme';
import { TranslateService } from '@ngx-translate/core';

export class TranslatedMenu {
  MENU_ITEMS: NbMenuItem[] = [];

  constructor(private translateService: TranslateService) {
    this.MENU_ITEMS = [
      {
        title: 'pages.dashboard',
        icon: 'nb-home',
        link: '/pages/dashboard',
        home: true
      },
      {
        title: 'pages.molds',
        icon: 'nb-locked'
      }
    ];

    for (const each of this.MENU_ITEMS ) {
      this.translateService.stream(each.title).subscribe(res => {
        each.title = res;
      });
      if (each.children) {
        for (const eachChild of each.children) {
          this.translateService.stream(eachChild.title).subscribe(res => {
            eachChild.title = res;
          });
        }
      }
    }

  }
}

This allows you to change MENU_ITEMS order and add new items without bothering about translate streams. I've also took in account items' children, in case there will be any.

All 15 comments

Hi @dizco, as far as I can tell Nebular components (probably except for the auth forms) don't have any translatable text, so the text you pass inside could be translated on your app side.
Could you describe what doesn't work in your particular case?

@nnixaa yes sorry I was not clear enough, I meant auth components! If you don't plan on supporting this within Nebular I can understand too, take this issue more as a suggestion of a nice-to-have.

@dizco I see. I would say it would be great to make the auth components translatable too. What would be the best way you think? Last time I checked there were two most popular options - native angular i18n and ngx-translate. Both have pros and cons, I personally would prefer ngx-translate as it allows a hot-reload of a locale, while with i18n we need to rebuild the app (though this could have changed since then).
But we cannot force any of the approaches as this should be an end user decision.
Probably we can move all wordings into a configuration, then the user could choose the best option depending on the project needs. What do you think?

@nnixaa I've only used ngx-translate myself, but from reading the native angular i18n docs, I don't see how we could support both the native and ngx-translate options... It seems that with the native solution, we need to reload the app if using JIT, while the AOT pre-builds a version for each langage. Also, it seems to only support html translations for now, so I'm unsure how we would allow users any choice with this.

What I personally would do is plug ngx-translate internally, and fallback on English as default, which would ensure backward compatibility. We could also provide ways for users to customize their messages dynamically, probably something like fullcalendar and moment do.

@dizco, to be honest, we don't want to introduce a new dependency really. I would personally vote for moving input titles/placeholders into the configuration as we did for validation rules (https://github.com/akveo/nebular/blob/master/src/framework/auth/auth.options.ts#L67).

We probably even can make the forms dynamically configurable outside (so that the user is able to add and configure additional fields). But for starters, we can move titles and validation messages into the configuration. What do you think?

@nnixaa yes, that's definitely a valid concern for a package like this one. I agree that everything could be wrapped inside configuration. My concern is regarding hot-reloading though, do configurations support such a thing? Passing the whole array of languages in the configuration is not a valid option I believe, so we would need a way to change those specific configurations on the fly.

Do you have any idea if this is actually already possible with our configurations?

i'm using ngx-translate with http-loader in my app, i like hot-reloading of ngx-translate, so i'm paying attention in this issue. In my case for example i want to translate the sidebar title menus this was my workaround:

import { NbMenuItem } from '@nebular/theme';
import { TranslateService } from '@ngx-translate/core';

export class TranslatedMenu {
  MENU_ITEMS: NbMenuItem[] = [];

  constructor(private translateService: TranslateService) {
    this.MENU_ITEMS = [
      {
        title: 'Dashboard',
        icon: 'nb-home',
        link: '/pages/dashboard',
        home: true
      },
      {
        title: 'Molds',
        icon: 'nb-locked'
      }
    ];
    this.translateService.stream('pages.dashboard').subscribe(res => {
      this.MENU_ITEMS[0].title = res;
    });
    this.translateService.stream('pages.molds').subscribe(res => {
      this.MENU_ITEMS[1].title = res;
    });
  }
}


I think there should be a kind of a translation provider and the we could specify the translation key (if using ngx-translation), my example could be something like this:

export const MENU_ITEMS: NbMenuItem[] = [
  {
    title: 'Dashboard',
    icon: 'nb-home',
    link: '/pages/dashboard',
    home: true,

   usetranslation:true,
   translation-key:'pages.dashboard'
  },
  {
    title: 'Molds',
    icon: 'nb-locked',

   usetranslation:true,
   translation-key:'pages.molds'
  },
];


@ruisebastiao

I've used your approach to solve the problem and came up with a refinement which may be useful to you.

import { NbMenuItem } from '@nebular/theme';
import { TranslateService } from '@ngx-translate/core';

export class TranslatedMenu {
  MENU_ITEMS: NbMenuItem[] = [];

  constructor(private translateService: TranslateService) {
    this.MENU_ITEMS = [
      {
        title: 'pages.dashboard',
        icon: 'nb-home',
        link: '/pages/dashboard',
        home: true
      },
      {
        title: 'pages.molds',
        icon: 'nb-locked'
      }
    ];

    for (const each of this.MENU_ITEMS ) {
      this.translateService.stream(each.title).subscribe(res => {
        each.title = res;
      });
      if (each.children) {
        for (const eachChild of each.children) {
          this.translateService.stream(eachChild.title).subscribe(res => {
            eachChild.title = res;
          });
        }
      }
    }

  }
}

This allows you to change MENU_ITEMS order and add new items without bothering about translate streams. I've also took in account items' children, in case there will be any.

Hi, I'm also using this translation feature thank to all of the comments here that made me achieved.
By the way, I found some little improvement from above code which can fix a couple issues

  1. Side menu no longer translating if went to login page and come back (logout and login again) because the translation key has been replaced with a translated value.
  2. Subscriptions of stream() need to be unsubscribed, otherwise it will add another subscribe on top if component get destroyed and re-initialized (from above case).

Here is my solution,

  1. Add a new service, pages-menu-translator.ts
  2. Declare a subscription variable to store translate service's streams.
  3. unsubscribe the subscription in ngOnDestroy().
import { NbMenuItem } from '@nebular/theme';
import { TranslateService } from '@ngx-translate/core';
import { Injectable, OnDestroy } from '@angular/core';
import { Subscription } from "rxjs/Subscription";

@Injectable()
export class PagesMenuTranslator implements OnDestroy {
  private menuItem$ = new Subscription();

  constructor(private translateService: TranslateService) {
  }

  translate(menuItems: NbMenuItem[]) : NbMenuItem[] {
    // deep copy array (without reference)
    const translationMenu = JSON.parse(JSON.stringify(menuItems));

    for (let mainIndex = 0; mainIndex < menuItems.length; mainIndex++) {
      const mainSubscription = this.translateService.stream(menuItems[mainIndex].title).subscribe(res => {
        const mainMenu = translationMenu[mainIndex];
        mainMenu.title = res;
      });

      this.menuItem$.add(mainSubscription);

      if (menuItems[mainIndex].children) {
        for (let childIndex = 0; childIndex < menuItems[mainIndex].children.length; childIndex++) {
          const childSubscription = this.translateService.stream(menuItems[mainIndex].children[childIndex].title).subscribe(res => {            
            const subMenu =  translationMenu[mainIndex].children[childIndex];            
            subMenu.title = res;
          });

          this.menuItem$.add(childSubscription);          
        }
      }
    }

    return translationMenu;
  }

  ngOnDestroy() {
    this.menuItem$.unsubscribe();
  }

}

Then modify the pages.component.ts,

  1. Add PagesMenuTranslator as a private provider
  2. call translator.translate() in ngOnInIt()
import { Component, OnInit } from '@angular/core';
import { NbMenuItem } from '@nebular/theme';

import { MENU_ITEMS } from './pages-menu';
import { PagesMenuTranslator } from './pages-menu-translator';

@Component({
  selector: 'ngx-pages',
  template: `
    <ngx-sample-layout>
      <nb-menu [items]="menu"></nb-menu>
      <router-outlet></router-outlet>
    </ngx-sample-layout>
  `,
  providers: [PagesMenuTranslator]
})
export class PagesComponent implements OnInit { 

  private menu: NbMenuItem[];

  constructor(private translator: PagesMenuTranslator) {    
  }

  ngOnInit() {
    // if put on constructor it will doing twice when refresh a page.
    this.menu = this.translator.translate(MENU_ITEMS);
  }
}

Finally, change a title to be translation key in pages-menu.ts

import { NbMenuItem } from '@nebular/theme';

export const MENU_ITEMS: NbMenuItem[] = [
  {
    title: 'Menus.Dashboard.Title',
    icon: 'nb-home',
    link: '/pages/dashboard',
    home: true,
  },
  ...
}

The Auth messages and errors are hard coded in the source code.
Anyone manages to translate them?
For example: Lines 51-52, 63-64 in this file
https://github.com/akveo/nebular/blob/master/src/framework/auth/strategies/password/password-strategy-options.ts

Do you plan to export the hard coded text lines of the Auth to an option in the settings const? so there will be an option to change it?

Update:
My workaround for translating these messages is to add them as text code

_('Login/Email combination is not correct, please try again');
_('You have been successfully logged in');
_('Something went wrong, please try again');
_('You have been successfully registered');

This way I can use ngx-translate-extract to add them to my translations JSON files

Hi @noamvaza, you can provide these messages through the Strategy configuration.

@nnixaa Thanks, It works. Sorry

Hi,

Is there a way to translate the forms, buttons, etc. of the auth components?

Thanks.

@alobaton as far as I know, no. If you look at login.component.html from nebular, you can see that the buttons text is hardcoded. The only way to translate those as it stands is to copy the component into your own app and modify them from there. This is actually exactly the point raised by this issue :)

Hi @dizco,

Yes, I've noticed it later...

Thanks for answering! What I did was override the Auth, login, register, reset and recovery password components using the TranslatePipe of ngx-translate.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

suku-h picture suku-h  路  3Comments

bnbs picture bnbs  路  4Comments

johnsnow20087349 picture johnsnow20087349  路  3Comments

obarazan picture obarazan  路  3Comments

ChristianVega5421 picture ChristianVega5421  路  3Comments