Skip to content

feat(transloco): add injectTransloco composable API - #941

Open
Rodrigo54 wants to merge 13 commits into
jsverse:masterfrom
Rodrigo54:feature/inject-transloco
Open

feat(transloco): add injectTransloco composable API#941
Rodrigo54 wants to merge 13 commits into
jsverse:masterfrom
Rodrigo54:feature/inject-transloco

Conversation

@Rodrigo54

@Rodrigo54 Rodrigo54 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

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?

*transloco is a structural directive, so like all structural directives it delays view initialization — a static ViewChild/viewChild query on a component using it never resolves (#846).

translateSignal/translateObjectSignal already exist as a lean signal API - they were themselves the response to #781's original ask for an alternative to the toSignal(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 callable t(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:

  • Not a structural directive, so it doesn't delay view initialization (fixes Feature(scope): control-flow style declaration #846).
  • 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. Unlike translateSignal, 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 what translateSignal already provides.
  • t.translateObject(key, params?) for keys that resolve to an object.
  • t.activeLang is a direct passthrough of TranslocoService.activeLang.
  • t.read(prefix) derives a new, independent TranslocoRef that prefixes every key - pure key prefixing, never triggers a new scope load, doesn't mutate the instance it's called on.
  • *transloco and the transloco pipe are unchanged, not deprecated, and continue to work exactly as before.

As an internal prerequisite (design decision, not incidental), translateSignal/translateObjectSignal now load through _loadDependencies (ensuring the global lang always loads alongside a scope, same as the directive/pipe) and resolve TRANSLOCO_LANG/TRANSLOCO_SCOPE/reRenderOnLangChange the same way the directive/pipe do - see "breaking change" note below.

Also fixes a regression found while working on this: translateSignal/translateObjectSignal have always auto-prefixed an unprefixed key with the active scope (config.scopes.autoPrefixKeys, default true) - 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-loaders page where the break was actually caught.

Does this PR introduce a breaking change?

  • Yes
  • No

translateSignal/translateObjectSignal now respect a TRANSLOCO_LANG DI provider and the reRenderOnLangChange config when no explicit lang argument 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 a BREAKING 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:

Ref What it is
#919 Open discussion: should translateSignal scope auto-prefixing be default, opt-in, or a separate API?
#855 service.translate()/selectTranslate() never infer scope from DI, but translateSignal()/the directive/pipe do - a maintainer-endorsed RFC was proposed to settle this holistically.
#875 translateSignal can only reach one of several simultaneously-provided TRANSLOCO_SCOPE scopes.
#891 Open PR attempting a partial, opt-in fix for #875, at the TranslocoService.selectTranslate() level.
#866 Merged: fixed selectTranslate() to load the last provided scope instead of the first, when several are provided.
#903 Wants a bare translateSignal('key') to inherit a component-provided scope (inline loader included) automatically.
SCOPE_MULTI_PROVIDER_INVESTIGATION.md Already in develop - prior investigation into multi: true TRANSLOCO_SCOPE behavior and its staged fix plan.

Auto-prefixing (#919, #855, #903): this PR restores/extends translateSignal's pre-existing default-auto-prefix behavior onto injectTransloco, rather than resolving the open debate - so #919's outcome could still affect injectTransloco'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 to service.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 from translateSignal altogether, requiring fully-qualified keys everywhere - functionally identical from injectTransloco'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.autoPrefixKeys to false in 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 what service.translate()/selectTranslate() already do - translateSignal is 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's translateSignal/translateObjectSignal no longer call selectTranslate() at all (replaced by resolveTranslation$ calling service.translate()/translateObject() directly), merging #891 as-is would stop reaching translateSignal/injectTransloco, unlike today. A contained fix looks feasible entirely within transloco.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 touches transloco.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

npm run ci:lint
npx nx test-library transloco
npx nx test transloco-playground
npx nx e2e transloco-playground-e2e

Summary by CodeRabbit

  • New Features
    • Added signal-based translation coverage across lazy, multi-language, inline-loader, and scope-sharing pages.
    • Introduced a new injection-based translation API (including prefix/read, dynamic keys/params, and active language support) and re-exported it from the main package.
    • Expanded the home page with an “Inject” section demonstrating injected translation patterns.
  • Bug Fixes
    • Improved scoped and inline-language resolution behavior, including signal auto-prefixing regressions.
  • Tests
    • Extended end-to-end coverage (including a new “Inline loaders” page) and added signal/injection unit tests, including array-of-keys and disabled auto-prefix scenarios.

Rodrigo54 and others added 12 commits November 18, 2025 10:27
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>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The PR adds injectTransloco, centralizes signal translation resolution, adds signal-based playground examples for scopes and languages, and expands unit and Playwright coverage for injected, lazy, scoped, and inline translations.

Translation API and playground integration

Layer / File(s) Summary
Shared signal translation resolution
libs/transloco/src/lib/transloco.signal.ts, libs/transloco/src/lib/tests/signal.spec.ts
Signal APIs share scope, language, dependency-loading, and initial-value resolution, with coverage for key prefixing and arrays of keys.
Injectable translation reference
libs/transloco/src/lib/transloco.inject.ts, libs/transloco/src/index.ts, libs/transloco/src/lib/tests/inject.spec.ts
Adds the public injectTransloco API with callable and object translation, active-language signals, prefixed references, injector overrides, and lifecycle handling.
Playground translation examples
apps/transloco-playground/src/app/...
Adds injected and signal-based rendering examples covering lazy scopes, provider languages, inline languages, shared scopes, and inline loaders.
End-to-end translation coverage
apps/transloco-playground-e2e/src/*
Extends full-cycle helpers and tests to validate injected translations, signal translations, lazy content, multiple languages, scope sharing, and inline loaders.

Possibly related PRs

Suggested reviewers: shaharkazaz

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding the injectTransloco composable API.
Description check ✅ Passed The description follows the template and includes the checklist, PR type, current/new behavior, breaking change note, and other information.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

@jsverse/transloco

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

@jsverse/transloco-locale

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

@jsverse/transloco-messageformat

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

@jsverse/transloco-optimize

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

@jsverse/transloco-persist-lang

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

@jsverse/transloco-persist-translations

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

@jsverse/transloco-preload-langs

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

@jsverse/transloco-schematics

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

@jsverse/transloco-scoped-libs

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

@jsverse/transloco-utils

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

@jsverse/transloco-validator

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

commit: eda83c1

@Rodrigo54
Rodrigo54 marked this pull request as ready for review July 21, 2026 06:09
@shaharkazaz

Copy link
Copy Markdown
Collaborator

Hey @Rodrigo54!

The only signal-based translation option today is the toSignal(this.translocoService.selectTranslate('hello')) boilerplate, with no lean, purpose-built signal API.

There is a translateSignal so can you clarify what you mean?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/transloco-playground/src/app/lazy/lazy.component.ts (1)

14-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Mark 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 the readonly modifier to adminTitle and lazyPageTitle.
  • apps/transloco-playground/src/app/multi-langs/multi-langs.component.ts#L14-L17: Add the readonly modifier to global and withScope.
  • apps/transloco-playground/src/app/multi-langs/provider-lang/provider-lang.component.ts#L18-L20: Add the readonly modifier to inProvider and inline.
  • apps/transloco-playground/src/app/scope-sharing/scope-sharing.component.ts#L21-L21: Add the readonly modifier to todosTitle.

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 win

Document that read(prefix) is meant to be cached, not re-invoked per render.

createTranslocoRef allocates fresh translateMemo/translateObjectMemo maps and registers a new ctx.destroyRef.onDestroy(...) callback every time it runs — including every call to read(). All examples/tests correctly cache the result in a field (nested = this.t.read('nested')), so this is fine in practice, but nothing in the read doc 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 read doc comment (e.g. "cache the result — don't call this from inside a template binding or computed") 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

📥 Commits

Reviewing files that changed from the base of the PR and between 04f9c11 and 22b5771.

📒 Files selected for processing (22)
  • apps/transloco-playground-e2e/src/full-cycle.spec.ts
  • apps/transloco-playground-e2e/src/home.ts
  • apps/transloco-playground-e2e/src/inline-loaders.ts
  • apps/transloco-playground-e2e/src/lazy.ts
  • apps/transloco-playground-e2e/src/multi-lang.ts
  • apps/transloco-playground-e2e/src/scope-sharing.ts
  • apps/transloco-playground/src/app/home/home.component.html
  • apps/transloco-playground/src/app/home/home.component.ts
  • apps/transloco-playground/src/app/inline-loaders/inline-loaders.component.html
  • apps/transloco-playground/src/app/lazy/lazy.component.html
  • apps/transloco-playground/src/app/lazy/lazy.component.ts
  • apps/transloco-playground/src/app/multi-langs/multi-langs.component.html
  • apps/transloco-playground/src/app/multi-langs/multi-langs.component.ts
  • apps/transloco-playground/src/app/multi-langs/provider-lang/provider-lang.component.html
  • apps/transloco-playground/src/app/multi-langs/provider-lang/provider-lang.component.ts
  • apps/transloco-playground/src/app/scope-sharing/scope-sharing.component.html
  • apps/transloco-playground/src/app/scope-sharing/scope-sharing.component.ts
  • libs/transloco/src/index.ts
  • libs/transloco/src/lib/tests/inject.spec.ts
  • libs/transloco/src/lib/tests/signal.spec.ts
  • libs/transloco/src/lib/transloco.inject.ts
  • libs/transloco/src/lib/transloco.signal.ts

Comment thread apps/transloco-playground-e2e/src/full-cycle.spec.ts
Comment thread libs/transloco/src/lib/transloco.signal.ts
@Rodrigo54

Copy link
Copy Markdown
Contributor Author

Fair catch, and sorry for the confusion - that line in the PR description is leftover phrasing from #781's original body, which predates translateSignal existing. I should've updated it. Let me clarify what injectTransloco actually adds on top of translateSignal, since they're not really alternatives to each other.

translateSignal(key, params?) gives you a signal for one, pre-declared key - each call becomes its own class field. If you need several keys, or a key that's only known dynamically in the template, you end up needing one translateSignal call per key ahead of time.

*transloco (the structural directive), on the other hand, gives you t(key) callable with any key, inline in the template, no pre-declaration needed - but that ergonomic comes from it being a structural directive, which is exactly what #846 is about (delayed view init, breaks static ViewChild).

injectTransloco is meant to close that specific gap: it gives you the same callable t(key) ergonomic the directive has, but as a plain composable, not a structural directive - so no view-init delay, ViewChild works fine. Under the hood it's built directly on top of translateSignal/translateObjectSignal (memoizing a signal per key on demand, reusing the same engine), not a competing implementation.

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

Copy link
Copy Markdown
Collaborator

@Rodrigo54 Thanks for the clarification.
I'm in the middle of a major upgrade to the lib so this will defer after v9 is released. but I'll read what you wrote on autoPrefixing and consider riding on this next major for it.
An important note is that this will break the TKM, so we'll need a matching PR for it as well.
I'm transferring it to this monorepo so you'll soon be able to mix all in the same PR

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
libs/transloco/src/lib/tests/signal.spec.ts (1)

42-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding an explicit return type.

While TypeScript correctly infers Signal<string[]> from translateSignal, adding an explicit type annotation can improve consistency with the outsideInjectionContext... 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

📥 Commits

Reviewing files that changed from the base of the PR and between 22b5771 and eda83c1.

📒 Files selected for processing (2)
  • libs/transloco/src/lib/tests/signal.spec.ts
  • libs/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

@medbenmakhlouf medbenmakhlouf added enhancement New feature or request transloco Related to the @jsverse/transloco core package labels Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request transloco Related to the @jsverse/transloco core package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature(scope): control-flow style declaration

3 participants