SSR and Angular Universal
ngx-translate v18 works under Angular Universal / SSR with a few things to know.
The v18 pipe and directive are signal-driven. TranslateService.translate()
returns a computed() signal that resolves to a synchronous instant()
lookup against the currently loaded translations. The pipe is declared
pure: false and reads that signal on every change-detection cycle; the
directive wraps the same signal in an effect() that writes the value to
the DOM.
Because the underlying read is synchronous, the server render produces a fully translated DOM in one pass — there is no async boundary to await. If the same language is loaded on both server and client, the rendered text matches and hydration succeeds without flicker.
The HTTP loader makes real HttpClient requests during prerender. Without
intervention, the same translation files are fetched again on the client
right after hydration. There are two common patterns to avoid this.
Ship your translations as a bundled JSON and call setTranslation() (or
setCompiledTranslation() if you precompile) in an APP_INITIALIZER. The
HTTP loader is then never invoked for the languages you pre-load.
import { APP_INITIALIZER, ApplicationConfig, inject } from "@angular/core";import { provideTranslateService, TranslateService } from "@ngx-translate/core";import enTranslations from "../public/i18n/en.json";
export const appConfig: ApplicationConfig = { providers: [ provideTranslateService({ fallbackLang: "en" }), { provide: APP_INITIALIZER, multi: true, useFactory: () => { const translate = inject(TranslateService); return () => { translate.setTranslation("en", enTranslations); return translate.use("en"); }; }, }, ],};Wire Angular’s TransferState between server and client. The server-side
HTTP loader writes the response into TransferState; the client-side loader
reads from TransferState before falling back to HTTP. This pattern is the
same as the standard Angular SSR data-transfer recipe; the ngx-translate
HTTP loader is a HttpClient consumer so the standard
provideClientHydration({ withHttpTransferCacheOptions: ... }) setup
already covers it for most projects.
onTranslationChange,onLangChange, andonFallbackLangChangeare Subject-backed Observables. v18 wires aDestroyRef.onDestroyhook that completes those Subjects when the owning injector tears down, so child services on lazy routes (and the server-side injector after a render) release their subscribers cleanly — no special handling needed in component code.onTranslationRefreshis a derived Observable (mergeof the three above) and completes transitively when its sources complete.- Language switching between server and client (e.g. a language cookie read on the client after hydration) will produce a visible flash from the server-rendered language to the client-rendered language. Use a Universal request-token to read the cookie on the server and pre-load the matching language via Pattern 1 if you need to avoid the flash.