feat(transloco): add injectTransloco composable API - #941
Conversation
Introduces injectTransloco(), a signal-based composable alternative to the *transloco structural directive. Unlike the directive, it doesn't delay view initialization, so it can be used alongside a static ViewChild/viewChild (jsverse#846), and it gives templates a lean, signal-backed t(key) without the toSignal(selectTranslate(...)) boilerplate (jsverse#781). t() is callable and memoized per key+params, t.read(prefix) derives an independent, prefixed instance, and t.activeLang passes through TranslocoService.activeLang directly. The *transloco directive and pipe are unchanged and remain fully supported. As an internal prerequisite, translateSignal/translateObjectSignal now load through _loadDependencies (ensuring the global lang loads alongside a scope, like the directive/pipe already do) and resolve TRANSLOCO_LANG/ TRANSLOCO_SCOPE/reRenderOnLangChange the same way the directive and pipe do. Previously, translateSignal/translateObjectSignal ignored a provided TRANSLOCO_LANG and always reacted to the active language regardless of reRenderOnLangChange when no explicit lang argument was passed - this is a behavior change for those specific configurations, flagged here for reviewers to decide whether it warrants a BREAKING CHANGE footer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…on and scope resolution
Addresses code-review findings on the injectTransloco commit:
- TranslocoRef's translate/translateObject get-or-create-memo blocks were
identical but for which Map/signal factory they used; extracted into a
shared readMemoized helper.
- injectTransloco previously joined its scope/lang options into a single
string just for translateSignal to immediately re-split them via
splitInlineScopeOrLang. translateSignal's internal engine is now exposed
as createTranslationSignal, taking an already-split { scope, lang } pair,
so injectTransloco can call it directly without the join/re-split
round-trip. translateSignal/translateObjectSignal still do the
single-argument split once, at the public entry point, since external
callers still pass one conflated argument.
- resolveTranslation$'s bare isObject boolean is now a named { isObject }
option at call sites instead of an unlabeled positional flag.
Left as documented tech debt (not fixed here): resolveTranslation$ still
duplicates the shape of TranslocoDirective/TranslocoPipe's own scope/lang
resolution rather than sharing one engine. Unifying them isn't a like-for-like
extraction - the directive/pipe resolve imperatively (mutating instance
fields), while this is a pure Observable pipeline - and touches
transloco.directive.ts/transloco.pipe.ts, which is out of scope for this
change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
resolveTranslation$ now returns Observable<TranslationValue> (string | string[] | Translation | Translation[]) instead of Observable<any>, with explicit generics on service.translate/translateObject instead of relying on their any-defaulting generic params. createTranslationSignal is overloaded on options.isObject (a literal true/false, not boolean) so callers get back TranslateSignalRef<T>/ TranslateObjectSignalRef<T> precisely - the implementation body can only prove the broader TranslationValue statically, since that split is a runtime value, so it does one explicit, overload-justified cast at the return instead of leaking any to callers. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CONTRIBUTING.md requires all public API methods to be documented - TranslocoRef's members (the callable itself, translate, translateObject, activeLang, read) and InjectTranslocoOptions' fields had none. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lateObjectSignal
translateSignal/translateObjectSignal have always auto-prefixed keys with
the active scope (config.scopes.autoPrefixKeys, default true) - see the
existing translateSignal docstring example, which passes 'hello' unprefixed
alongside { scope: 'todos' }. This is different from the *transloco
directive/pipe, which require the caller to prefix keys manually
(translocoPrefix, or writing the scope into the key by hand).
The scope/lang resolution rewrite in a prior commit on this branch broke
this silently: it computed a scope-stripped plain lang (mirroring how the
directive derives its own currentLang) before calling
service.translate/translateObject, instead of passing the scope-embedded
path those methods need to auto-prefix. No test caught it because
signal.spec.ts never exercised the scope argument - found via the
playground's InlineLoadersComponent, whose unprefixed `translateSignal
('title')` stopped resolving.
Fix: pass the resolved (possibly scope-embedded) path straight through to
service.translate/translateObject instead of stripping it first -
resolveLangBasedOnScope is no longer needed for this. Also fixes two of
injectTransloco's own tests that had been manually prefixing the key
(masking this exact bug the same way the directive's convention does),
and adds a dedicated regression test in signal.spec.ts covering both
translateSignal and translateObjectSignal with an unprefixed scoped key.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds an "Inject" column to the home page, alongside the existing Structural Directive/Directive/Pipe/Signal ones, covering the callable form, translateObject, dynamic key/params, translation reuse, read() prefixing, and the activeLang passthrough. Extends the e2e home-page assertions (including the dynamic key/params click round-trips) to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No test visited /inline-loaders before, which is exactly how the translateSignal scope auto-prefixing regression (fixed in a prior commit on this branch) went unnoticed - the page's own Signal row was silently broken. Adds data-cy hooks to its Directive/Directive-Global-Scope/Pipe/ Async/Signal rows and a testInlineLoadersContent helper, registered in full-cycle.spec.ts the same way every other page is. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds translateSignal rows (and e2e assertions) to three more playground
pages, alongside their existing Directive/Pipe rows:
- Lazy: scope auto-prefixing via a route-provided scope (ambient
TRANSLOCO_SCOPE) and via an inline scope argument.
- Scope sharing: auto-prefixing via a scope's *explicit* alias
(provideTranslocoScope({ scope, alias })), not just the default
camelCase mapping - a case the Lazy page doesn't exercise. A scope-free
"global" row was attempted here but dropped: unlike the directive/pipe
(which never auto-prefix), translateSignal always prefixes onto
whatever scope is ambiently active, so there's no way to reach a truly
global key from a component sitting under a TRANSLOCO_SCOPE provider -
documented in a comment instead.
- Multi langs: TRANSLOCO_LANG provider resolution, inline-lang-wins-over-
provider precedence, and combined inline lang+scope resolution (the
'scope/lang' combo form, e.g. 'lazy-page/es'), mirroring the directive
rows exactly - including only asserting the provider row on the pass
where it's guaranteed correct, same as the existing directive assertion.
generateLazyContent is shared between /lazy and /lazy-multiple-scopes
(same data-cy vocabulary) but the latter's scopes/aliases don't match
what a shared Signal assertion would expect, so the new Signal checks
were split into their own generateLazySignalContent, called only for
/lazy.
Also adds a unit test in signal.spec.ts covering the other branch of the
auto-prefix behavior: with config.scopes.autoPrefixKeys disabled,
translateSignal must not prefix an unprefixed key with the active scope -
a case that can't be demonstrated in the playground app, since that flag
is a single global config shared by every other scope demo page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rse#866 Cross-reference the merged upstream fix this code already follows for multi: true TRANSLOCO_SCOPE providers, and the open gap (jsverse#875) it doesn't close. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…abled injectTransloco never decides on its own whether to auto-prefix a key - it hands the resolved path to service.translate()/translateObject(), which is the only place scopes.autoPrefixKeys is honored. This confirms disabling that config is already a supported migration path with no code changes needed: an unprefixed key correctly stops resolving, while a fully-qualified key (written by hand or produced via t.read(prefix)) keeps working. Relevant to the ongoing jsverse#919 discussion on whether scope auto-prefixing should be opt-in. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughChangesThe PR adds Translation API and playground integration
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@jsverse/transloco
@jsverse/transloco-locale
@jsverse/transloco-messageformat
@jsverse/transloco-optimize
@jsverse/transloco-persist-lang
@jsverse/transloco-persist-translations
@jsverse/transloco-preload-langs
@jsverse/transloco-schematics
@jsverse/transloco-scoped-libs
@jsverse/transloco-utils
@jsverse/transloco-validator
commit: |
|
Hey @Rodrigo54!
There is a |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
apps/transloco-playground/src/app/lazy/lazy.component.ts (1)
14-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMark signal properties as
readonly.Across the playground components, the new translation signals are declared as mutable properties. Since signals hold their own reactive state internally, reassigning the property itself is usually unintended and can easily break reactive bindings in the template. Consider marking these signal properties as
readonly.
apps/transloco-playground/src/app/lazy/lazy.component.ts#L14-L15: Add thereadonlymodifier toadminTitleandlazyPageTitle.apps/transloco-playground/src/app/multi-langs/multi-langs.component.ts#L14-L17: Add thereadonlymodifier toglobalandwithScope.apps/transloco-playground/src/app/multi-langs/provider-lang/provider-lang.component.ts#L18-L20: Add thereadonlymodifier toinProviderandinline.apps/transloco-playground/src/app/scope-sharing/scope-sharing.component.ts#L21-L21: Add thereadonlymodifier totodosTitle.Fun fact: Did you know that the word "i18n" is actually a numeronym? The number 18 represents the exact number of letters between the first 'i' and the last 'n' in the word "internationalization"!
♻️ Proposed refactors
apps/transloco-playground/src/app/lazy/lazy.component.ts
- adminTitle = translateSignal('title'); - lazyPageTitle = translateSignal('title', undefined, 'lazy-page'); + readonly adminTitle = translateSignal('title'); + readonly lazyPageTitle = translateSignal('title', undefined, 'lazy-page');apps/transloco-playground/src/app/multi-langs/multi-langs.component.ts
- global = translateSignal('home'); - // 'lazy-page/es' is translateSignal's fully-resolved `scope/lang` combo form - - // the equivalent of the directive's separate `lang: 'es'; scope: 'lazy-page'` inputs. - withScope = translateSignal('title', undefined, 'lazy-page/es'); + readonly global = translateSignal('home'); + // 'lazy-page/es' is translateSignal's fully-resolved `scope/lang` combo form - + // the equivalent of the directive's separate `lang: 'es'; scope: 'lazy-page'` inputs. + readonly withScope = translateSignal('title', undefined, 'lazy-page/es');apps/transloco-playground/src/app/multi-langs/provider-lang/provider-lang.component.ts
- // Picks up the TRANSLOCO_LANG provider above (no explicit lang argument). - inProvider = translateSignal('home'); - // Inline lang wins over the TRANSLOCO_LANG provider, same as the directive below. - inline = translateSignal('home', undefined, 'en'); + // Picks up the TRANSLOCO_LANG provider above (no explicit lang argument). + readonly inProvider = translateSignal('home'); + // Inline lang wins over the TRANSLOCO_LANG provider, same as the directive below. + readonly inline = translateSignal('home', undefined, 'en');apps/transloco-playground/src/app/scope-sharing/scope-sharing.component.ts
- todosTitle = translateSignal('title'); + readonly todosTitle = translateSignal('title');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/transloco-playground/src/app/lazy/lazy.component.ts` around lines 14 - 15, Mark the translation signal properties as readonly to prevent unintended reassignment: update adminTitle and lazyPageTitle in apps/transloco-playground/src/app/lazy/lazy.component.ts (lines 14-15), global and withScope in apps/transloco-playground/src/app/multi-langs/multi-langs.component.ts (lines 14-17), inProvider and inline in apps/transloco-playground/src/app/multi-langs/provider-lang/provider-lang.component.ts (lines 18-20), and todosTitle in apps/transloco-playground/src/app/scope-sharing/scope-sharing.component.ts (line 21).libs/transloco/src/lib/transloco.inject.ts (1)
114-186: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument that
read(prefix)is meant to be cached, not re-invoked per render.
createTranslocoRefallocates freshtranslateMemo/translateObjectMemomaps and registers a newctx.destroyRef.onDestroy(...)callback every time it runs — including every call toread(). All examples/tests correctly cache the result in a field (nested = this.t.read('nested')), so this is fine in practice, but nothing in thereaddoc comment (Lines 56-67) warns against calling it directly inside a template binding or a reactive computation, where each re-evaluation would spin up a brand-new ref (new signals, new subscriptions kept alive until the owning injector is destroyed) instead of reusing the same memoized one.A short addition to the
readdoc comment (e.g. "cache the result — don't call this from inside a template binding orcomputed") would close that gap cheaply.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libs/transloco/src/lib/transloco.inject.ts` around lines 114 - 186, Update the documentation for the read method returned by createTranslocoRef to state that its result should be cached and not invoked directly from template bindings or reactive computations such as computed. Keep the existing read behavior unchanged and place the guidance in the read doc comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/transloco-playground-e2e/src/full-cycle.spec.ts`:
- Around line 92-99: Add Given-When-Then comments to the “Inline loaders page”
test: mark page navigation as Given, language changes as When, and each content
assertion as Then, following the surrounding test convention without changing
test behavior.
In `@libs/transloco/src/lib/transloco.signal.ts`:
- Around line 166-174: Update resolveInitialValue so non-object array keys
return a placeholder array with the same length as key, filling every position
with an empty string; preserve the existing scalar and object initial-value
behavior.
---
Nitpick comments:
In `@apps/transloco-playground/src/app/lazy/lazy.component.ts`:
- Around line 14-15: Mark the translation signal properties as readonly to
prevent unintended reassignment: update adminTitle and lazyPageTitle in
apps/transloco-playground/src/app/lazy/lazy.component.ts (lines 14-15), global
and withScope in
apps/transloco-playground/src/app/multi-langs/multi-langs.component.ts (lines
14-17), inProvider and inline in
apps/transloco-playground/src/app/multi-langs/provider-lang/provider-lang.component.ts
(lines 18-20), and todosTitle in
apps/transloco-playground/src/app/scope-sharing/scope-sharing.component.ts (line
21).
In `@libs/transloco/src/lib/transloco.inject.ts`:
- Around line 114-186: Update the documentation for the read method returned by
createTranslocoRef to state that its result should be cached and not invoked
directly from template bindings or reactive computations such as computed. Keep
the existing read behavior unchanged and place the guidance in the read doc
comment.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 53cdf077-f385-485f-83ec-b63024c21f54
📒 Files selected for processing (22)
apps/transloco-playground-e2e/src/full-cycle.spec.tsapps/transloco-playground-e2e/src/home.tsapps/transloco-playground-e2e/src/inline-loaders.tsapps/transloco-playground-e2e/src/lazy.tsapps/transloco-playground-e2e/src/multi-lang.tsapps/transloco-playground-e2e/src/scope-sharing.tsapps/transloco-playground/src/app/home/home.component.htmlapps/transloco-playground/src/app/home/home.component.tsapps/transloco-playground/src/app/inline-loaders/inline-loaders.component.htmlapps/transloco-playground/src/app/lazy/lazy.component.htmlapps/transloco-playground/src/app/lazy/lazy.component.tsapps/transloco-playground/src/app/multi-langs/multi-langs.component.htmlapps/transloco-playground/src/app/multi-langs/multi-langs.component.tsapps/transloco-playground/src/app/multi-langs/provider-lang/provider-lang.component.htmlapps/transloco-playground/src/app/multi-langs/provider-lang/provider-lang.component.tsapps/transloco-playground/src/app/scope-sharing/scope-sharing.component.htmlapps/transloco-playground/src/app/scope-sharing/scope-sharing.component.tslibs/transloco/src/index.tslibs/transloco/src/lib/tests/inject.spec.tslibs/transloco/src/lib/tests/signal.spec.tslibs/transloco/src/lib/transloco.inject.tslibs/transloco/src/lib/transloco.signal.ts
|
Fair catch, and sorry for the confusion - that line in the PR description is leftover phrasing from #781's original body, which predates
I'll fix the PR description to describe the actual current state (translateSignal already exists) instead of #781's original wording. Thanks for flagging it! |
…ch the key array resolveInitialValue always returned a single-element [''] placeholder for array keys, regardless of how many keys were requested. Before the first real emission, translateSignal(['a', 'b'])()[1] resolved to undefined instead of '' - a harmless but visible blip for templates indexing past position 0. Pre-existing (carried over from before this branch's rewrite), caught by CodeRabbit on jsverse#941, not previously covered by any test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@Rodrigo54 Thanks for the clarification. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
libs/transloco/src/lib/tests/signal.spec.ts (1)
42-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider adding an explicit return type.
While TypeScript correctly infers
Signal<string[]>fromtranslateSignal, adding an explicit type annotation can improve consistency with theoutsideInjectionContext...properties defined further down in this component.(Fun i18n fact: Did you know "i18n" is a numeronym that was coined at Digital Equipment Corporation in the 1980s because "internationalization" was simply too long to type? And while we're sharing languages, the word for "hello" in Swahili is "Jambo"!)
♻️ Proposed fix
- translatedArrayKeys = translateSignal(['home', 'b']); + translatedArrayKeys: Signal<string[]> = translateSignal(['home', 'b']);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libs/transloco/src/lib/tests/signal.spec.ts` at line 42, Annotate the translatedArrayKeys property initialized with translateSignal(['home', 'b']) as Signal<string[]>, matching the explicit return-type style used by the nearby outsideInjectionContext... properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@libs/transloco/src/lib/tests/signal.spec.ts`:
- Line 42: Annotate the translatedArrayKeys property initialized with
translateSignal(['home', 'b']) as Signal<string[]>, matching the explicit
return-type style used by the nearby outsideInjectionContext... properties.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 13ed844e-c614-44d2-a76b-70256ab1c38a
📒 Files selected for processing (2)
libs/transloco/src/lib/tests/signal.spec.tslibs/transloco/src/lib/transloco.signal.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- libs/transloco/src/lib/transloco.signal.ts
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
*translocois a structural directive, so like all structural directives it delays view initialization — a staticViewChild/viewChildquery on a component using it never resolves (#846).translateSignal/translateObjectSignalalready exist as a lean signal API - they were themselves the response to #781's original ask for an alternative to thetoSignal(this.translocoService.selectTranslate('hello'))boilerplate. But each call is for one key, pre-declared as its own class field ahead of time. They don't give you the directive's$implicit-style ergonomic: a callablet(key)usable with any key, inline in the template, without pre-declaring anything - which is the remaining piece #846 wants, without the structural-directive cost.Issue Number: #846, #781
What is the new behavior?
Adds
injectTransloco(), a signal-based composable alternative to*transloco:t(key, params?)is callable and memoized per key+params, reactive under a tracked context (template/computed/effect) -t.translate(...)is the same function under an explicit name. UnliketranslateSignal, no class field needs pre-declaring per key - closing the remaining gap from Feature(transloco): Signal based translation inside templates #781's original ask, on top of whattranslateSignalalready provides.t.translateObject(key, params?)for keys that resolve to an object.t.activeLangis a direct passthrough ofTranslocoService.activeLang.t.read(prefix)derives a new, independentTranslocoRefthat prefixes every key - pure key prefixing, never triggers a new scope load, doesn't mutate the instance it's called on.*translocoand thetranslocopipe are unchanged, not deprecated, and continue to work exactly as before.As an internal prerequisite (design decision, not incidental),
translateSignal/translateObjectSignalnow load through_loadDependencies(ensuring the global lang always loads alongside a scope, same as the directive/pipe) and resolveTRANSLOCO_LANG/TRANSLOCO_SCOPE/reRenderOnLangChangethe same way the directive/pipe do - see "breaking change" note below.Also fixes a regression found while working on this:
translateSignal/translateObjectSignalhave always auto-prefixed an unprefixed key with the active scope (config.scopes.autoPrefixKeys, defaulttrue) - the scope/lang resolution rewrite above initially broke this silently (no existing test exercised the scope argument); restored and covered by a dedicated regression test, plus new e2e coverage on the playground's/inline-loaderspage where the break was actually caught.Does this PR introduce a breaking change?
translateSignal/translateObjectSignalnow respect aTRANSLOCO_LANGDI provider and thereRenderOnLangChangeconfig when no explicitlangargument is passed - previously both were silently ignored in that case (always followed the globally active language, always reacted to lang changes). This only affects callers relying on the old (arguably buggy, definitely inconsistent with the directive/pipe) behavior under those specific configurations. Flagged here for reviewers/maintainers to decide whether it warrants aBREAKING CHANGE:footer for semver purposes - deliberately not added by this PR.Other information
This PR lands in an area with active, unsettled design discussion around scope resolution. For context before reviewing, here's everything related, referenced by number below:
translateSignalscope auto-prefixing be default, opt-in, or a separate API?service.translate()/selectTranslate()never infer scope from DI, buttranslateSignal()/the directive/pipe do - a maintainer-endorsed RFC was proposed to settle this holistically.translateSignalcan only reach one of several simultaneously-providedTRANSLOCO_SCOPEscopes.TranslocoService.selectTranslate()level.selectTranslate()to load the last provided scope instead of the first, when several are provided.translateSignal('key')to inherit a component-provided scope (inline loader included) automatically.SCOPE_MULTI_PROVIDER_INVESTIGATION.mddevelop- prior investigation intomulti: trueTRANSLOCO_SCOPEbehavior and its staged fix plan.Auto-prefixing (#919, #855, #903): this PR restores/extends
translateSignal's pre-existing default-auto-prefix behavior ontoinjectTransloco, rather than resolving the open debate - so #919's outcome could still affectinjectTransloco's design later. Whichever way that discussion lands, this PR is already prepared for it with zero code changes needed on this side:resolveTranslation$never decides on its own whether to prefix a key, that's entirely delegated toservice.translate()/translateObject(). That holds whether the outcome is (a) auto-prefixing staying opt-in behind a config flag (scopes.autoPrefixKeys, already tested against this PR) or (b) auto-prefixing being removed fromtranslateSignalaltogether, requiring fully-qualified keys everywhere - functionally identical frominjectTransloco's point of view, since it never implements the decision itself either way.t.read(prefix)(independent of that config) is already the ergonomic replacement for auto-prefixing once it's off, covered by a dedicated test.Our preference, for what it's worth: default
scopes.autoPrefixKeystofalsein a future major. #875 and #903 were filed independently, for unrelated reasons, and both trace back to the same root cause - auto-prefixing hides a scope-selection decision that only exists at runtime, which breaks down whenever something needs to know it ahead of time (multiple simultaneously-active scopes for #875, static extraction tooling for #903). The directive/pipe already show that requiring the fully-qualified key works fine in practice at scale, and it's whatservice.translate()/selectTranslate()already do -translateSignalis the one outlier.Separately, #903's own runtime concern (bare-key scope + inline-loader inheritance) already works with this PR - its actual blocker is
transloco-keys-manager's static extraction needing a literal scope string, a separate package untouched here.Multiple simultaneous scopes (#875, #891, #866): this PR's multi-scope handling already follows the same approach as #866 (load every provided scope, resolve against the last) - enough for loading, but not for what #875 actually asks: a fully-qualified key still gets prefixed with the wrong (last) scope on top of its own, so it's never found. #891 attempts a further fix, but only patches
TranslocoService.selectTranslate()- and since this PR'stranslateSignal/translateObjectSignalno longer callselectTranslate()at all (replaced byresolveTranslation$callingservice.translate()/translateObject()directly), merging #891 as-is would stop reachingtranslateSignal/injectTransloco, unlike today. A contained fix looks feasible entirely withintransloco.signal.ts, but given how unsettled and cross-cutting this whole area already is, it felt safer to flag the dependency than add a third, possibly-diverging implementation.Improvement opportunity for
TranslocoDirective/TranslocoPipe(unrelated to the scope-resolution discussion above):resolveTranslation$(transloco.signal.ts) currently has its own implementation of the scope/lang resolution the directive/pipe already do, rather than sharing one engine. There's an opportunity to extract a single, well-tested resolution engine that the directive/pipe could also adopt (they currently resolve imperatively, mutating instance fields, which this pure-Observable version doesn't need to). Left out of this PR's scope since it touchestransloco.directive.ts/transloco.pipe.ts- core, heavily-tested code beyond what's needed here. Commented in the code. Happy to open a follow-up PR for this if there's interest.How to validate
Summary by CodeRabbit