DEV Community

Angular - Translate Enums (i18n)

Piotr Lewandowski on July 08, 2019

Built-in Angular translation engine supports (so-far) only translation within templates, so you might think it's not possible to translate parts of...
Collapse
 
hopmandahinda profile image
HopmanDahinda

I have gotten the ICU approach working, but I've come across another problem, I would like to have a hover title on my enum. Do you have any idea how to accomplish that? I've tried everything I could think of.

Collapse
 
constjs profile image
Piotr Lewandowski • Edited

Right, it might cause duplication in ICU approach. I'd go with first solution then.

@Component({
  selector: 'todo-state-i18n',
  template: `
  <ng-container [ngSwitch]="key">
    <span i18n *ngSwitchCase="todoState.TODO" title="not started" i18n-title>not started</ng-container>
    <span i18n *ngSwitchCase="todoState.IN_PROGRESS" title="started" i18n-title>started</ng-container>
    <span i18n *ngSwitchCase="todoState.DONE" title="finished" i18n-title>finished</ng-container>
    <span i18n *ngSwitchDefault title="not defined" i18n-title>not defined</ng-container>
  </ng-container>
  `,
})
export class TodoStateI18n {

  // enum has to be accessed through class field
  todoState = TodoState;

  @Input()
  key: TodoState;
}
Enter fullscreen mode Exit fullscreen mode

Notes:

  • Elements cannot be <ng-container> anymore with title attribute. I went with <span>
  • You have to add i18n-title to translate attribute

Ivy update

If you use Ivy (and the best Angular 11.2+), there is new package @angular/localize, that can translate strings in TypeScript.

So you could have re-usable function for providing messages:

getStateMessage(state: TodoState) {
  switch(state) {
    case TodoState.TODO:
      return $localize`not started`;
    case TodoState.IN_PROGRES:
      return $localize`started`;
    case TodoState.DONE:
      return $localize`finished`;
    default:
      return $localize`unknown`;
  }
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
hopmandahinda profile image
HopmanDahinda

Wow, thanks for the really quick reply!

I thought that the first approach is not suitable for const enum, and since our company prefers using const enums I would rather not change that. Do you know any other way? If not, I will use the first approach anyway.

We don't use Ivy unfortunately.

Thread Thread
 
constjs profile image
Piotr Lewandowski • Edited

Yep, it has to be regular enum.

I also maintain practice to keep const enum, but they do not fit in every use case. Overhead is slightly bigger but not crazy

Thread Thread
 
hopmandahinda profile image
HopmanDahinda

Ok I will change it to a regular enum. Thank you for your help!

Collapse
 
plondrein profile image
Dominik

This is one nice approach ! One minor detail, though - can this default value in ngSwitch might lead to hide new untranslated enum values?

For third final note, I imagine test enumerating through all enum values that reminds us about new values to translate.

Collapse
 
constjs profile image
Piotr Lewandowski • Edited

for enums that tends to change or grow, we write unit tests that iterate over enum and make sure all values are included.

Example could be as simple as:

describe('TodoStateI18n', () => {
  let component: TodoStateI18n;
  let fixture: ComponentFixture<TodoStateI18n>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        TodoStateI18n,
      ],
    })
      .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(TodoStateI18n);
    component = fixture.componentInstance;
  });

  // regular specs
  it('should display correct text for TODO', () => {
    component.value = TodoState.TODO;

    fixture.detectChanges();

    expect(fixture.nativeElement.textContent)
      .toBe('not started');
  });

  // checking if everything is translated
  // Cannot be `const enum`
  Object.values(TodoState)
    .forEach((value) => {
      it(`should translate ${value}`, () => {
        component.value = value;

        fixture.detectChanges();

        expect(fixture.nativeElement.textContent)
          .not
          .toBe('unknown');
      });
    });
});