2

I want to add multilingual capabilities to an Angular 5 app, but I'm not sure I've chosen the right approach for it.

I have defined this 2 types in a service:

type appMsgTuple = {
  [key : string] : string | string[]
}

type appMsgs = {
  es : appMsgTuple,
  en : appMsgTuple
}

So, I define variables to hold the translations for the different components of the app like:

export var MSG_USER : appMsgs = {
  "es" : {
    "MSG_USER_1" : "Por favor, introduzca sus datos de acceso",
    "MSG_USER_BUTTONS" : [ "ACCEDER", "Síganos" ]
  },
  "en" : {
    "MSG_USER_1" : "Please, type your login details",
    "MSG_USER_BUTTONS": [ "LOGIN", "Follow us"]
  }
}

In each component, I import the appropriate strings from the service:

// USER COMPONENT
[ ... ]
import { MSG_USER } from 'app/services/languages.service';

public MSGS : any = {};   // We'll use this to 'point' to the right language in MSG_USER (see below)

And, to make it more concise, I assign the variable MSGS to the right language (chosen by the user) like this:

// USER COMPONENT
[ ... ]

ngOnInit() {
  this.MSGS = MSG_USER[selUsr.selectedLanguage];
  [ ... ]
}

Finally, in the template I use the MSGS variable to display the different strings, in this way:

<h5 class="fwcBlue">{{ MSGS['MSG_USER_1'] }}</h5>

<button label="{{ MSGS['MSG_USER_BUTTONS'][1] }}" [disabled]="!formModel.valid || formModel.pristine" (click)="onLogin()">    
</button>

This works, BUT there's a big problem with it: if I mistype any of the keys (for example, I type MSGS['MSG_USER_BUTTTTTTTONS'][1], the application crashes because it doesn't exist and Javascript can't access position 1 of undefined.

How could I avoid this potential risk? Thanks in advance,

1
  • See here Commented Apr 27, 2018 at 15:34

2 Answers 2

1

Angular templates support safe navigation operator (Elvis operator) for this purpose.

It isn't supported for bracket notation, so a safeguard is necessary in this case:

<button
  *ngIf="MSGS['MSG_USER_BUTTONS']"
  label="{{ MSGS['MSG_USER_BUTTONS'][1] }}"
  [disabled]="!formModel.valid || formModel.pristine" (click)="onLogin()"
> 
</button>

Because of this reason, tuples aren't a good choice for this purpose. Key-value pairs wouldn't have such problem:

  "es" : {
    "MSG_USER_1" : "Por favor, introduzca sus datos de acceso",
    "MSG_USER_BUTTONS" : { ACCEDER: Síganos }
  }

It is beneficial to provide an abstraction that won't require to access message object directly - a service and a pipe that will return a value for language in use and specified path, like {{ 'MSG_USER_BUTTONS.LOGIN' | translate }}, this is how it is usually done. There are several existing i18n solutions that use this approach, e.g. ngx-translate.

Sign up to request clarification or add additional context in comments.

2 Comments

ngx-translate is great choice, instead of re-inventing the wheel by writing one's own i18n logic.
I've reviewed ngx-translate and it has everything I need for translating the application. Thanks for the suggestion!
1

You can write like below:

MSGS['MSG_USER_BUTTTTTTTONS'] && MSGS['MSG_USER_BUTTTTTTTONS'][1]

If MSGS['MSG_USER_BUTTTTTTTONS'] is null or undefined - then, code after && will not execute - so no index error will appear

2 Comments

This may result in undesirable output if MSGS['MSG_USER_BUTTTTTTTONS'] is falsy (false, 0) but will likely work in this case because of MSG_USER type restrictions.
This was the solution I thought at first, but I didn't like to duplicate each and all of the translation strings in all the templates.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.