Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
1 task done
guillerot opened this issue Aug 9, 2023 · 5 comments
Open
1 task done

Comments

@guillerot
Copy link

guillerot commented Aug 9, 2023

Is there an existing issue for this?

  • I have searched the existing issues

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
Copy link
Collaborator

@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
Copy link
Collaborator

@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
Copy link
Author

@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 :

  • if scope is the only one provided : register the scope with a camel case alias if not already registered
  • if alias is the only one provided: use it if registration has been made before
  • if scope and alias are provided together : register the scope with this alias
    • Would it be wrong to have multiple aliases for the same scope ?
    • Or should it overwrite the existing registration ?

@shaharkazaz
Copy link
Collaborator

@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
Copy link
Author

guillerot commented Aug 24, 2023

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;
    }
    ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants