transloco: Language ready event missing

I’m submitting a…


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[x] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

I have a list of objects used for a dropdown containing a key from which a localized label is created. As the list is to be sorted by its localized label alphabetically the translation has to be done in code instead of within the template. The user is able to switch the language during app usage. The documentation mentions the langChanges$ Observable to get informed on these language changes so we can update the list:

transloco.langChanges$.pipe(
  takeUntil(this.unsubscribe$)
).subscribe(() => this.updateList());

If the language is switched to for the first time the langChange event is emitted before the corresponding language file has been loaded and thus errors are written to the console as well as leaving the labels untranslated. First workaround is to wait for the load event of the translation file too:

transloco.langChanges$.pipe(
  takeUntil(this.unsubscribe$),
  switchMap(() => transloco.events$)
).subscribe(() => this.updateList())

But then of course this won’t work if the file has already been fetched, getting us to

transloco.langChanges$.pipe(takeUntil(this.unsubscribe$)).subscribe(() => {
  const testKey: string = 'existing-key';
  if (testKey !== transloco.translate('existing-key')) {
    this.determineFilterKeys();
  } else {
    transloco.events$.pipe(take(1)).subscribe(() => this.updateList()));
  }
);

But you still get errors logged on the console and this does feel way too complicated.

Expected behavior

The langChanges event should be emitted if the language is ready in that either the file with translations is already present or successfully loaded. Alternatively there should be a languageReady event or similar.

What is the motivation / use case for changing the behavior?

Loading of translation should be part of the language switch.

Environment


Angular version: 8.2.14
transloco version: 2.12.1


Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: 12.2.0  
- Platform:  Mac/Windows

Others:

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 8
  • Comments: 22 (10 by maintainers)

Most upvoted comments

This still isn’t merged yet - where is the problem to add a cleanway to allow this… for example only fire the language switch event after the required lang is available… i have the problem that i have to translate the labels on an object in code… and in the view i can’t bind to an observable because it comes from a lib. So when i switch from initial lang to alternating lang and trigger my translation onChange - i get the keys back because the language isn’t there yet… all order ways are somehow hacky… This should just work imho

@shaharkazaz Thanks for the demo. I had a few problems with the loader as I want to reuse it across many components, so updated that to

export const availableLangs = ['en', 'es'];

export const scopeLoader = (importer, scope = null, root = 'i18n') => {
  return availableLangs.reduce((acc, lang) => {
    acc[scope ? `${scope}/${lang}` : lang] = () => importer(lang, root);
    return acc;
  }, {});
};

This can then be included on general components which don’t need “in code” translations, omitting the scope in the loader:

@Component({
  selector: 'app-general',
  template: `
    <ng-container *transloco="let t; read: 'generalComponent'">{{
      t('title')
    }}</ng-container>
  `,
  providers: [
    {
      provide: TRANSLOCO_SCOPE,
      useValue: {
        scope: 'generalComponent',
        loader: scopeLoader((lang, root) => import(`./${root}/${lang}.json`)),
      },
    },
  ],
})
export class GeneralComponent

And now can also be used in components which require “in code” translations

const loader = scopeLoader(
  (lang, root) => import(`./${root}/${lang}.json`),
  'inCode'
);

@Component({
  selector: 'app-in-code',
  template: `
    <div #view></div>
  `,
  providers: [
    {
      provide: TRANSLOCO_SCOPE,
      useValue: {
        scope: 'inCode',
        loader
      },
    },
  ],
})
export class InCodeComponent implements OnInit {
  constructor(
    protected translocoService: TranslocoService,
    @Inject(TRANSLOCO_SCOPE) protected scope,
  ) {}

  ngOnInit() {
    this.translocoService.langChanges$
      .pipe(
        switchMap(lang => {
          console.log('Active lang is:', lang);
          return this.translocoService.load(`${this.scope.scope}/${lang}`, {
            inlineLoader: loader,
          });
        })
      )
      .subscribe(v => {
        console.log('Lang changed and scope loaded!', v);
      });
  }
}

Tested and working! Will look forward to the new function.

@ChazUK Seems like Stackblitz doesn’t support Webpack’s dynamic imports and that’s why it’s not working for you. I have created an implementation on code sandbox, please try it there.

We are thinking about exposing a new function from the service that will take most of the boilerplate code in my example into a single function.

@darkv I’m not suggesting to listen to a language change via some key from the translation. it was an example since it seems like he is looking for a translation value to pass into his graph service.

If you just want an indication that both the language has changed but the new translation is loaded (from either cache or server) why not do:

this.translocoService.langChanges$.pipe(switchMap(() => this.selectTranslation())).subscribe(() => {
...do some translations
});

And if you want to wait for a specific scope you can pass the value into the selectTranslation with the lang given in the langChanges$

Let me think about it, maybe we need to expose something from the service that replaces that code.

I have a list of objects used for a dropdown containing a key from which a localized label is created. As the list is to be sorted by its localized label alphabetically the translation has to be done in code instead of within the template.

  1. I would do that with pipe
  2. If you still need that to be on the ts file, I suggest you use selectTranslationObject method.