Skip to content

fix(transloco): 🐛 Allow translateSignal API to access any scope#891

Open
maleetz wants to merge 1 commit intojsverse:masterfrom
maleetz:wait-scopes-with-auto-prefix-false
Open

fix(transloco): 🐛 Allow translateSignal API to access any scope#891
maleetz wants to merge 1 commit intojsverse:masterfrom
maleetz:wait-scopes-with-auto-prefix-false

Conversation

@maleetz
Copy link
Copy Markdown
Contributor

@maleetz maleetz commented Nov 21, 2025

Use the autoPrefixKeys=false configuration to find the best provider scope based on the requested key(s), which will then wait for any inline loaders to resolve before trying to translate.

This also includes a small refactor to move the autoPrefixKeys configuration logic into a private function.

Closes: #875

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

What is the current behavior?

Issue Number: #875

Using an app.config.ts with multiple TRANSLOCO_SCOPE providers with inline loaders, you can select translations from either scope using the pipe or directive but only have access to the first scope with the translateSignal API.

What is the new behavior?

To avoid introducing a breaking change but still fix this issue, we can utilize the new autoPrefixKeys configuration property from #868.

If the user has configured their transloco service to not automatically prefix the translation keys with the requested scopes then their translation keys will contain the full path of the translation, including the requested scope. We can utilize that full path to find the correct transloco scope in the providers array to support any inline loaders for that scope.

This PR does not address requesting translations from multiple scopes with inline loaders, if someone needs to do that they should create separate signals.

Does this PR introduce a breaking change?

  • Yes
  • No

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Nov 21, 2025

Open in StackBlitz

@jsverse/transloco

npm i https://pkg.pr.new/jsverse/transloco/@jsverse/transloco@891

@jsverse/transloco-locale

npm i https://pkg.pr.new/jsverse/transloco/@jsverse/transloco-locale@891

@jsverse/transloco-messageformat

npm i https://pkg.pr.new/jsverse/transloco/@jsverse/transloco-messageformat@891

@jsverse/transloco-optimize

npm i https://pkg.pr.new/jsverse/transloco/@jsverse/transloco-optimize@891

@jsverse/transloco-persist-lang

npm i https://pkg.pr.new/jsverse/transloco/@jsverse/transloco-persist-lang@891

@jsverse/transloco-persist-translations

npm i https://pkg.pr.new/jsverse/transloco/@jsverse/transloco-persist-translations@891

@jsverse/transloco-preload-langs

npm i https://pkg.pr.new/jsverse/transloco/@jsverse/transloco-preload-langs@891

@jsverse/transloco-schematics

npm i https://pkg.pr.new/jsverse/transloco/@jsverse/transloco-schematics@891

@jsverse/transloco-scoped-libs

npm i https://pkg.pr.new/jsverse/transloco/@jsverse/transloco-scoped-libs@891

@jsverse/transloco-utils

npm i https://pkg.pr.new/jsverse/transloco/@jsverse/transloco-utils@891

@jsverse/transloco-validator

npm i https://pkg.pr.new/jsverse/transloco/@jsverse/transloco-validator@891

commit: cf59c01

@maleetz
Copy link
Copy Markdown
Contributor Author

maleetz commented Nov 22, 2025

I also created a branch to showcase an alternative which consolidates the scope loading logic from the directive / pipe so that we can reuse it in selectTranslate for this use case: maleetz@213423c#diff-c07e4a02be032d92cac1cfbef925cccb4f35c3a4a1697a76a9993f7790ca296c.

Things I like about the consolidation:

  • Less duplicated code
  • More clarity that signals / directive / pipe should operate the same when not auto prefixing keys
  • Removes the confusing lang => path (lang + scope) => back to lang conversion. Instead I just keep a reference to the correct lang in the observable
  • Supports the case of selecting from multiple scopes with one selectTranslate call, but I can't imagine this is a likely use case.

Things I don't like:

  • Riskier change
  • Waits for more inline loaders that necessary most of the time

When configuring Transloco with scopes.autoPrefixKeys:false, the key
parameter for selectTranslate must include a scope prefix to select
scoped translations. Using that, we can then find the correct
ProviderScope with any inline loaders that must be resolved before
translating.

✅ Closes: jsverse#875
@maleetz maleetz force-pushed the wait-scopes-with-auto-prefix-false branch from 894a21d to cf59c01 Compare November 22, 2025 12:05
Copy link
Copy Markdown
Collaborator

@shaharkazaz shaharkazaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @maleetz, thanks for the continued effort on this — really appreciate you sticking with it.

I've merged PR #866 as an immediate fix (takes [lang.length - 1] instead of [0]), which addresses the most common case. However, looking at this PR more closely, I think we should go with the alternative approach you yourself explored in 213423c, your instincts there were right.

The approach you explored is closer, but it still has the same gap - when autoPrefixKeys is on (the default), it doesn't load all scopes. The fix needs to unconditionally forkJoin-load every scope in the array, exactly like the directive does today. The autoPrefixKeys setting should only affect how keys are prefixed, not which scopes get loaded.

To summarize what the implementation should do:

  1. When selectTranslate receives a scope array, forkJoin-load all scopes (unconditionally, regardless of
    autoPrefixKeys)
  2. Then resolve the translation key against the appropriate scope

The concerns you raised about it are valid but manageable.

  1. The "riskier change" is worth it for correctness. What risks do you foresee?
  2. The extra inline loader wait is a one-time cost per scope, and you already wait for it when working with the pipe/directive.

Let me know if you want to pick this up or if you'd prefer I handle it!

Comment on lines -325 to +322
lang?: string | TranslocoScope | TranslocoScope[],
lang?: TranslocoScope | TranslocoScope[],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string here was to mark that this also accepts a language. I agree that the string is a bit too general, but please revert it for the time being.

@maleetz
Copy link
Copy Markdown
Contributor Author

maleetz commented Apr 12, 2026

Thanks for the reply!

As far as I can tell, the auto prefixing functionality only applies to the signal / observable API right now. The directive and pipe do not use auto prefixing as shown here #875.

Demo component:

@Component({
  selector: 'app-root',
  imports: [TranslocoDirective, TranslocoPipe],
  template: `
    <ul *transloco="let t">
      <li>t("a.title"): {{ t('a.title') }}</li>
      <li>t("b.title"): {{ t('b.title') }}</li>
      <li>"a.title" | transloco: {{ 'a.title' | transloco }}</li>
      <li>"b.title" | transloco: {{ 'b.title' | transloco }}</li>
      <li>translateSignal('a.title'): {{ aTitle() }}</li>
      <li>translateSignal('b.title'): {{ bTitle() }}</li>
      <li>translateSignal('title'): {{ title() }}</li>
    </ul>
  `,
})
export class AppComponent {
  public aTitle = translateSignal('a.title');
  public bTitle = translateSignal('b.title');
  public title = translateSignal('title');
}

Config:

    provideTranslocoScope({
      scope: 'a',
      loader: { en: () => import('./i18n/a.json') },
    }),
    provideTranslocoScope({
      scope: 'b',
      loader: { en: () => import('./i18n/b.json') },
    }),

Output:

t("a.title"): A Title
t("b.title"): B Title
"a.title" | transloco: A Title
"b.title" | transloco: B Title
translateSignal('a.title'): a.a.title
translateSignal('b.title'): a.b.title
translateSignal('title'): A Title

This PR

The goal of this PR is to allow the caller to access all scoped translations with inline loaders via the signal / observable API. Since we can now request the translation without auto prefixing, the only remaining task is to make sure to wait for the required inline loader to load. This can be accomplished by finding the correct loader or, as you found in my previous commit (213423c), waiting for all of them to load.

I chose to go with the "find logic" to keep risk to a minimum since the current selectTranslate code already operated with one scope (even if multiple were provided).

Feel free to take on the refactor to wait for them all.

Also, I think this sounds like a good idea (#866 (comment)):

Update provideTranslocoScope to only use multi: true when multiple scopes are passed in a single call (scopes.length > 1). Single-scope calls will go back to the original non-multi behavior.

Might be a bit challenging for people to adopt though since they would have to make a lot of changes in their apps.

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

Successfully merging this pull request may close these issues.

Bug(transloco): Cannot easily access all TRANSLOCO_SCOPE (multi:true) providers with translateSignal API

2 participants