jsverse / transloco

🚀 😍 The internationalization (i18n) library for Angular
https://jsverse.github.io/transloco/
MIT License
2.04k stars 197 forks source link

Bug(transloco): Can't translate with scope/alias inside a data provider (ResolveFn) #686

Open guillerot opened 1 year ago

guillerot commented 1 year ago

Is there an existing issue for this?

Which Transloco package(s) are the source of the bug?

Transloco

Is this a regression?

Yes

Current behavior

Translating with a scope/alias inside a data provider (https://angular.io/api/router/ResolveFn) can break future translations with same alias

Example, using the data propriver below in lazy-scope-alias.routes.ts of transloco-playground

const translationFn: (key: string) => ResolveFn<Translation> = (key: string) => () => {
  const translocoService = inject(TranslocoService);
  return translocoService.selectTranslate(key,undefined,{ scope: 'lazy-scope-alias', alias: 'myScopeAlias' })
}

export const LAZY_SCOPE_ALIAS_ROUTES: Route = {
  path: 'lazy-scope-alias',
  loadComponent: () =>
    import('./lazy-scope-alias.component').then(
      (LazyScopeAliasComponent) => LazyScopeAliasComponent
    ),
  providers: [
    provideTranslocoLoadingTpl(
      `<span id="default-loading-template">Loading template...</span>`
    ),
  ],
  resolve: {
    title: translationFn('title')
  }
};

Will cause

transloco-missing-handler.ts:22 Missing translation for 'myScopeAlias.title'

image

ℹ️ The issue no longer occurs if scope mapping is provided in transloco config ℹ️

    provideTransloco({
      config: {
        prodMode: !isDevMode(),
        availableLangs: [
          { id: 'en', label: 'English' },
          { id: 'es', label: 'Spanish' },
        ],
        reRenderOnLangChange: true,
        fallbackLang: 'es',
        defaultLang: 'en',
        missingHandler: {
          useFallbackTranslation: false,
        },
        scopeMapping: {
          'lazy-scope-alias': 'myScopeAlias',
        }
        // interpolation: ['<<<', '>>>']
      } as unknown as Partial<TranslocoConfig>,
      loader: TranslocoHttpLoader,
    }),

Expected behavior

Translations can still be made after using an alias inside a data provider

Please provide a link to a minimal reproduction of the bug, if you won't provide a link the issue won't be handled.

https://codesandbox.io/s/ngneat-transloco-forked-r6fgjs?file=/src/app/lazy-scope-alias/lazy-scope-alias.routes.ts

Transloco Config

No response

Please provide the environment you discovered this bug in

Transloco: 
Angular: 
Node: 
Package Manager: npm
OS:

Browser

No response

Additional context

No response

I would like to make a pull request for this bug

No

shaharkazaz commented 1 year ago

@guillerot I think this is an issue with the selectTranslate signature as the only property that's used from the ScopeProvider is the scope which is used to load any scope dependencies.

But I do agree that the scenario that your provided should work I just think the solution should be related to the scope registration flow, what happens is:

selectTranslate ==> load scope ==> loaded ==> set aliased scope

But there is no alias provided since it's resolved at the component level using the ScopeResolver which registers the aliases from the scope provider

shaharkazaz commented 1 year ago

@guillerot After digging into this, there are 2 things that need to be done:

  1. Scopes should self-register the alias when provided, currently they are set by something called the scope resolver.
  2. There is no way to support this scenario with the current API, WDYT about allowing the users to set a scope alias via the service? another alternative is to bring back the scopeMapping config property. I need to think which way is preferred, but generally speaking, it would be best if I have found a way to manage this without the user managing this by hand.
guillerot commented 1 year ago

@shaharkazaz I would say having a TranslocoService with self-registered scopes could be great (less hackish than scopeMapping managed by user)

I didn't go deep into the transloco code. But based on namespace doc, could it possible when calling selectTranslate with a TranslocoScope to get this behaviour :

shaharkazaz commented 1 year ago

@guillerot The main issue with that suggestion is that it's not the selectTranslate's job to register scopes, I feel that it's giving that method responsibility that it should have.

Not sure I understand all your cases, can you share code examples before I respond? just to make sure I understood them correctly 🙂

guillerot commented 1 year ago

Sorry, I got a bit lost in my suggestions. One source of the issue here is the TranslocoService cache. An observable of the translation made without the scope mapped key is stored.

A (dirty?) solution could be to force set of translation even if cached when the scope is mapped

  ...
  private isMappedScope(scope: string): boolean {
    const { scopeMapping = {} } = this.config;
    return !!scopeMapping[scope];
  }

  load(path: string, options: LoadOptions = {}): Observable<Translation> {
    const cached = this.cache.get(path);
    if (cached) {
      const isMappedScope = this._isLangScoped(path) && this.isMappedScope(getScopeFromLang(path));
      if(isMappedScope) { // an other condition should be used to avoid setting this translation each time load is called
        cached.subscribe((translation) => this.setTranslation(translation, path, { emitChange: false }));
      }
      return cached;
    }
    ...