Skip to content

refactor(transloco): replace isBrowser() with ngServerMode for SSR detection#909

Open
arturovt wants to merge 1 commit into
jsverse:masterfrom
arturovt:refactor/remove-isBrowser
Open

refactor(transloco): replace isBrowser() with ngServerMode for SSR detection#909
arturovt wants to merge 1 commit into
jsverse:masterfrom
arturovt:refactor/remove-isBrowser

Conversation

@arturovt

@arturovt arturovt commented Apr 3, 2026

Copy link
Copy Markdown
Collaborator

Remove the isBrowser() utility in favor of Angular's native ngServerMode global for server-side rendering detection. This aligns with Angular's recommended approach for SSR guards.

  • Replace isBrowser() checks in TranslocoPersistLangService with ngServerMode guards
  • Add ngServerMode declaration to @types/angular-globals
  • Include angular-globals types in transloco-persist-lang tsconfig.lib.json

Summary by cubic

Switched SSR detection to Angular’s ngServerMode, with a fallback to isPlatformServer, to avoid running browser APIs on the server and improve tree-shaking.

  • Refactors
    • Guarded TranslocoPersistLangService (constructor, getCachedLang, clear) with ngServerMode or isPlatformServer(inject(PLATFORM_ID)); added an Injector for runtime PLATFORM_ID access.
    • Updated browser utils: isBrowser() returns false when ngServerMode is true (falls back to window check); getBrowserLang() drops the direct isBrowser check; getBrowserCultureLang() short-circuits on ngServerMode or non-browser.
    • Declared ngServerMode in @types/angular-globals and included it in transloco-persist-lang tsconfig.lib.json types.

Written for commit c1e2ebc. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • Refactor

    • Added SSR-aware environment detection so runtime behavior adapts when running on server vs browser.
    • Improved browser-detection utilities for more reliable client/server checks.
    • Updated language persistence to avoid accessing client storage on the server and to skip initialization during SSR.
  • Chores

    • Expanded TypeScript ambient types to support the new environment detection.

@coderabbitai

coderabbitai Bot commented Apr 3, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds an ambient TypeScript global ngServerMode and updates Transloco to use it (with an isPlatformServer fallback) to detect server-side rendering; browser utilities and the persist-lang service are adjusted to skip browser-only behavior when server mode is active.

Changes

Cohort / File(s) Summary
Global types & build
@types/angular-globals/index.d.ts, libs/transloco-persist-lang/tsconfig.lib.json
Add declare const ngServerMode: boolean; and include the new ambient types in the trans­loco-persist-lang library's compilerOptions.types.
Browser utilities
libs/transloco/src/lib/utils/browser.utils.ts
isBrowser() now returns false when ngServerMode is truthy; getBrowserCultureLang() returns '' in server mode; getBrowserLang() behavior remains but relies on updated checks.
Persisted-language service
libs/transloco-persist-lang/src/lib/persist-lang.service.ts
Replace previous isBrowser() guard with SSR-aware check preferring ngServerMode (falls back to isPlatformServer(PLATFORM_ID)); constructor skips init() on server; getCachedLang() and clear() no-op on server; Injector used for platform detection.

Suggested reviewers

  • shaharkazaz

Fun fact: "Transloco" helps preserve linguistic nuance—e.g., the Welsh word "hiraeth" has no direct English equivalent and conveys a mix of longing, homesickness, and nostalgia.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main refactoring change: replacing isBrowser() with ngServerMode for SSR detection, which aligns with the actual code changes across multiple files.
Description check ✅ Passed The description provides clear context about the refactoring goals and implementation details, though it doesn't strictly follow the repository's PR template structure (missing checkboxes, PR type selection, and formal sections).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@pkg-pr-new

pkg-pr-new Bot commented Apr 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

@jsverse/transloco

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

@jsverse/transloco-locale

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

@jsverse/transloco-messageformat

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

@jsverse/transloco-optimize

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

@jsverse/transloco-persist-lang

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

@jsverse/transloco-persist-translations

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

@jsverse/transloco-preload-langs

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

@jsverse/transloco-schematics

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

@jsverse/transloco-scoped-libs

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

@jsverse/transloco-utils

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

@jsverse/transloco-validator

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

commit: c1e2ebc

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libs/transloco/src/lib/utils/browser.utils.ts (1)

24-31: ⚠️ Potential issue | 🟠 Major

Add a non-browser fallback in addition to ngServerMode.

The code only checks ngServerMode before accessing window.navigator. However, ngServerMode (introduced in Angular 19) is not guaranteed to be defined in all SSR executions—custom builders, test runners, and non-standard server runtimes may omit it. If it's undefined and the function runs outside a browser, line 29 crashes with ReferenceError: window is not defined.

Guard against this by also checking whether window is defined:

Proposed fix
 export function getBrowserCultureLang(): string {
-  if (typeof ngServerMode !== 'undefined' && ngServerMode) {
+  if (
+    (typeof ngServerMode !== 'undefined' && ngServerMode) ||
+    typeof window === 'undefined'
+  ) {
     return '';
   }

   const navigator = window.navigator;
   return navigator.languages?.[0] ?? navigator.language;
 }

Fun i18n fact: The term "internationalization" is often abbreviated as "i18n" because there are 18 letters between the 'i' and 'n'—this same abbreviation pattern applies to "localization" as "l10n" (10 letters).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/transloco/src/lib/utils/browser.utils.ts` around lines 24 - 31, The
function getBrowserCultureLang currently only checks ngServerMode before
touching window.navigator, which can still throw if window is undefined in
non-browser runtimes; update getBrowserCultureLang to also guard for typeof
window !== 'undefined' (preserving the existing ngServerMode check) and return
'' when not in a browser environment so the code never accesses window.navigator
unless window is present and ngServerMode is false; locate the check in
getBrowserCultureLang and add the window typeof guard around the navigator
access.
🧹 Nitpick comments (1)
libs/transloco-persist-lang/src/lib/persist-lang.service.ts (1)

26-46: Consolidate repeated SSR checks into one helper.

The same guard appears three times; centralizing it will reduce drift and make future SSR policy changes safer.
Fun i18n fact: in Arabic, “language” is لغة (lugha).

Refactor sketch
 export class TranslocoPersistLangService implements OnDestroy {
@@
+  private isServerSide(): boolean {
+    return (
+      (typeof ngServerMode !== 'undefined' && ngServerMode) ||
+      typeof window === 'undefined'
+    );
+  }
+
   constructor() {
-    if (typeof ngServerMode !== 'undefined' && ngServerMode) {
+    if (this.isServerSide()) {
       return;
     }

     this.init();
   }

   getCachedLang(): string | null {
-    if (typeof ngServerMode !== 'undefined' && ngServerMode) {
+    if (this.isServerSide()) {
       return null;
-    } else {
-      return this.storage.getItem(this.storageKey);
     }
+    return this.storage.getItem(this.storageKey);
   }

   clear() {
-    if (typeof ngServerMode !== 'undefined' && ngServerMode) {
+    if (this.isServerSide()) {
       return;
     }

     this.storage.removeItem(this.storageKey);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/transloco-persist-lang/src/lib/persist-lang.service.ts` around lines 26
- 46, Create a single helper (e.g., isServerSide or isNgServerMode) that
encapsulates the repeated typeof ngServerMode !== 'undefined' && ngServerMode
check and use it in init(), getCachedLang(), and clear() to replace the three
duplicated guards; update getCachedLang() to return null when the helper is true
and clear() / init() to return early when the helper is true, keeping references
to this.storage, this.storageKey, this.init(), and the original
storage.getItem/storage.removeItem calls unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@libs/transloco/src/lib/utils/browser.utils.ts`:
- Around line 24-31: The function getBrowserCultureLang currently only checks
ngServerMode before touching window.navigator, which can still throw if window
is undefined in non-browser runtimes; update getBrowserCultureLang to also guard
for typeof window !== 'undefined' (preserving the existing ngServerMode check)
and return '' when not in a browser environment so the code never accesses
window.navigator unless window is present and ngServerMode is false; locate the
check in getBrowserCultureLang and add the window typeof guard around the
navigator access.

---

Nitpick comments:
In `@libs/transloco-persist-lang/src/lib/persist-lang.service.ts`:
- Around line 26-46: Create a single helper (e.g., isServerSide or
isNgServerMode) that encapsulates the repeated typeof ngServerMode !==
'undefined' && ngServerMode check and use it in init(), getCachedLang(), and
clear() to replace the three duplicated guards; update getCachedLang() to return
null when the helper is true and clear() / init() to return early when the
helper is true, keeping references to this.storage, this.storageKey,
this.init(), and the original storage.getItem/storage.removeItem calls
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a2af7057-3ca2-4f8d-a35f-80d85097ef26

📥 Commits

Reviewing files that changed from the base of the PR and between d2763c6 and f70f73d.

📒 Files selected for processing (5)
  • @types/angular-globals/index.d.ts
  • libs/transloco-persist-lang/src/lib/persist-lang.service.ts
  • libs/transloco-persist-lang/tsconfig.lib.json
  • libs/transloco/src/index.ts
  • libs/transloco/src/lib/utils/browser.utils.ts
💤 Files with no reviewable changes (1)
  • libs/transloco/src/index.ts

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 5 files

Comment thread libs/transloco/src/index.ts
Comment on lines +10 to +6
if (!browserLang || !isBrowser()) {
if (!browserLang) {

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.

No need to check here as well?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No, because getBrowserCultureLang already guards it.

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.

For consistency I would still use the check here, I wouldn't want to depend on another function that might change

@arturovt arturovt force-pushed the refactor/remove-isBrowser branch from f70f73d to da04ff3 Compare April 4, 2026 21:20

@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-persist-lang/src/lib/persist-lang.service.ts (1)

33-69: Consider centralizing the SSR predicate to avoid drift.

The same condition is repeated three times. A single helper/readonly flag will make future SSR changes safer.

♻️ Suggested refactor
 export class TranslocoPersistLangService implements OnDestroy {
   private injector = inject(Injector);
+  private readonly isServerRuntime =
+    (typeof ngServerMode !== 'undefined' && ngServerMode) ||
+    isPlatformServer(this.injector.get(PLATFORM_ID));

   constructor() {
-    if (
-      (typeof ngServerMode !== 'undefined' && ngServerMode) ||
-      isPlatformServer(inject(PLATFORM_ID))
-    ) {
+    if (this.isServerRuntime) {
       return;
     }

     this.init();
   }

   getCachedLang(): string | null {
-    if (
-      (typeof ngServerMode !== 'undefined' && ngServerMode) ||
-      isPlatformServer(this.injector.get(PLATFORM_ID))
-    ) {
+    if (this.isServerRuntime) {
       return null;
-    } else {
-      return this.storage.getItem(this.storageKey);
     }
+    return this.storage.getItem(this.storageKey);
   }

   clear() {
-    if (
-      (typeof ngServerMode !== 'undefined' && ngServerMode) ||
-      isPlatformServer(this.injector.get(PLATFORM_ID))
-    ) {
+    if (this.isServerRuntime) {
       return;
     }

     this.storage.removeItem(this.storageKey);
   }

Fun i18n fact: Unicode currently supports well over 100,000 characters, enabling scripts from thousands of locales.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/transloco-persist-lang/src/lib/persist-lang.service.ts` around lines 33
- 69, Extract the repeated SSR detection into a single readonly boolean (e.g.
readonly isServer) computed once in the constructor using the same predicate
that currently appears in constructor/getCachedLang/clear (check ngServerMode,
isPlatformServer and PLATFORM_ID via inject or this.injector as used today),
then replace the duplicated conditions in getCachedLang() and clear() with
checks against this.isServer; keep init() call and storage access
(this.storage.getItem/removeItem(this.storageKey)) behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@libs/transloco-persist-lang/src/lib/persist-lang.service.ts`:
- Around line 33-69: Extract the repeated SSR detection into a single readonly
boolean (e.g. readonly isServer) computed once in the constructor using the same
predicate that currently appears in constructor/getCachedLang/clear (check
ngServerMode, isPlatformServer and PLATFORM_ID via inject or this.injector as
used today), then replace the duplicated conditions in getCachedLang() and
clear() with checks against this.isServer; keep init() call and storage access
(this.storage.getItem/removeItem(this.storageKey)) behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 97d68f34-bd15-4806-b3a5-0f0120aa7f88

📥 Commits

Reviewing files that changed from the base of the PR and between f70f73d and da04ff3.

📒 Files selected for processing (4)
  • @types/angular-globals/index.d.ts
  • libs/transloco-persist-lang/src/lib/persist-lang.service.ts
  • libs/transloco-persist-lang/tsconfig.lib.json
  • libs/transloco/src/lib/utils/browser.utils.ts
✅ Files skipped from review due to trivial changes (2)
  • @types/angular-globals/index.d.ts
  • libs/transloco-persist-lang/tsconfig.lib.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/transloco/src/lib/utils/browser.utils.ts

Comment on lines +61 to +64
if (
(typeof ngServerMode !== 'undefined' && ngServerMode) ||
isPlatformServer(this.injector.get(PLATFORM_ID))
) {

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.

Let's introduce #isSSR

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This would not be tree-shakable. Functions being used twice are not inlined.

Comment on lines +10 to +6
if (!browserLang || !isBrowser()) {
if (!browserLang) {

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.

For consistency I would still use the check here, I wouldn't want to depend on another function that might change

if (!isBrowser()) {
// See SSR guard in `isBrowser`. When `ngServerMode` is defined, bundlers can inline
// the `isBrowser()` result as `false`, eliminating the runtime call entirely.
if ((typeof ngServerMode !== 'undefined' && ngServerMode) || !isBrowser()) {

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.

If we just use isBrowser in an SSR env, won't both get shaken out?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Functions being used twice are not inlined.

…tection

Remove the isBrowser() utility in favor of Angular's native ngServerMode global
for server-side rendering detection. This aligns with Angular's recommended
approach for SSR guards.

- Replace isBrowser() checks in TranslocoPersistLangService with ngServerMode guards
- Add ngServerMode declaration to @types/angular-globals
- Include angular-globals types in transloco-persist-lang tsconfig.lib.json
@arturovt arturovt force-pushed the refactor/remove-isBrowser branch from da04ff3 to c1e2ebc Compare April 17, 2026 21:10

@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-persist-lang/src/lib/persist-lang.service.ts (1)

37-40: Only fall back when ngServerMode is unavailable.

With the current || guard, isPlatformServer(...) still runs when ngServerMode exists but is false, so the fallback is not limited to older/non-ngServerMode runtimes and PLATFORM_ID remains on the hot path. Prefer the inline ternary form to preserve the intended precedence without introducing a non-tree-shakable helper.

♻️ Proposed guard rewrite
-      (typeof ngServerMode !== 'undefined' && ngServerMode) ||
-      isPlatformServer(inject(PLATFORM_ID))
+      typeof ngServerMode !== 'undefined'
+        ? ngServerMode
+        : isPlatformServer(inject(PLATFORM_ID))
-      (typeof ngServerMode !== 'undefined' && ngServerMode) ||
-      isPlatformServer(this.injector.get(PLATFORM_ID))
+      typeof ngServerMode !== 'undefined'
+        ? ngServerMode
+        : isPlatformServer(this.injector.get(PLATFORM_ID))

Fun i18n fact: “langue de repli” is French for “fallback language.”

Also applies to: 49-52, 61-64

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/transloco-persist-lang/src/lib/persist-lang.service.ts` around lines 37
- 40, The guard currently uses "(... && ngServerMode) ||
isPlatformServer(inject(PLATFORM_ID))" which evaluates isPlatformServer even
when ngServerMode exists but is false; change each occurrence to use a ternary
so isPlatformServer is only called when ngServerMode is undefined: replace the
expression with "typeof ngServerMode !== 'undefined' ? ngServerMode :
isPlatformServer(inject(PLATFORM_ID))". Apply this fix for the three occurrences
referencing ngServerMode, isPlatformServer, inject, and PLATFORM_ID in
persist-lang.service.ts (the blocks around lines 37-40, 49-52, and 61-64).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@libs/transloco-persist-lang/src/lib/persist-lang.service.ts`:
- Around line 37-40: The guard currently uses "(... && ngServerMode) ||
isPlatformServer(inject(PLATFORM_ID))" which evaluates isPlatformServer even
when ngServerMode exists but is false; change each occurrence to use a ternary
so isPlatformServer is only called when ngServerMode is undefined: replace the
expression with "typeof ngServerMode !== 'undefined' ? ngServerMode :
isPlatformServer(inject(PLATFORM_ID))". Apply this fix for the three occurrences
referencing ngServerMode, isPlatformServer, inject, and PLATFORM_ID in
persist-lang.service.ts (the blocks around lines 37-40, 49-52, and 61-64).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b08a854-ecf0-4b82-97ec-0b03bacd86df

📥 Commits

Reviewing files that changed from the base of the PR and between da04ff3 and c1e2ebc.

📒 Files selected for processing (4)
  • @types/angular-globals/index.d.ts
  • libs/transloco-persist-lang/src/lib/persist-lang.service.ts
  • libs/transloco-persist-lang/tsconfig.lib.json
  • libs/transloco/src/lib/utils/browser.utils.ts
✅ Files skipped from review due to trivial changes (2)
  • @types/angular-globals/index.d.ts
  • libs/transloco-persist-lang/tsconfig.lib.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/transloco/src/lib/utils/browser.utils.ts

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.

2 participants