When you switch languages with ngx-translate, your UI updates — but the html[lang="..."] attribute does not update automatically.
That's a small detail, but it matters for things like accessibility tooling and browser behavior.
The official ngx-translate documentation has a recipe for this: Synchronize lang attribute with current language.
That approach works, but it's based on older Angular patterns: subscribing to observables manually, using component lifecycle hooks, and handling subscriptions explicitly. Also, the example puts all logic in the root component, which makes it grow complex and do too many things at once.
So in this article we'll instead use a clean and modern Angular approach: signal-based reactivity, host directives attached via the component decorator, and no lifecycle hooks.
Step 1 — Make the current language reactive
Turn TranslateService.onLangChange into a signal.
Provide an initialValue so you have a correct value immediately.
import { inject, Injectable } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { TranslateService } from "@ngx-translate/core";
import { map } from "rxjs";
@Injectable({ providedIn: "root" })
export class Language {
private readonly translate = inject(TranslateService);
readonly currentLang = toSignal(this.translate.onLangChange.pipe(map(({ lang }) => lang)), {
initialValue: this.getCurrentLang(),
});
private getCurrentLang(): string {
return this.translate.getCurrentLang() ?? this.translate.getFallbackLang()!;
}
}
Now currentLang() always reflects the current language, reactively.
Step 2 — Sync html[lang] with an effect
Create a directive that writes to document.documentElement.lang whenever the signal changes:
import { DOCUMENT } from "@angular/common";
import { Directive, effect, inject } from "@angular/core";
import { Language } from "@services/language";
@Directive({
selector: "[appHtmlLangSync]",
})
export class HtmlLangSyncDirective {
private document = inject(DOCUMENT);
private language = inject(Language);
readonly _syncEffect = effect(() => {
this.document.documentElement.lang = this.language.currentLang();
});
}
Add this directive once near the top of your app, and you're done.
Step 3 — attach it as a host directive in App
import { Component } from "@angular/core";
import { RouterOutlet } from "@angular/router";
import { HtmlLangSyncDirective } from "@directives/html-lang-sync.directive";
@Component({
selector: "app-root",
imports: [RouterOutlet],
templateUrl: "./app.html",
styleUrl: "./app.css",
hostDirectives: [HtmlLangSyncDirective],
})
export class App {}