Skip to content

Migration guide v17 → v18

Migrating from v15 or v16? Follow the v15/v16 → v17 guide first, then return here.

v18 rebuilds ngx-translate on Angular Signals. TranslateModule is gone, language state is reactive, and defaultLang aliases have been removed. Most upgrades come down to find-and-replace plus a small number of structural changes.

Most v18 upgrades are mechanical replacements. Open a global search across your project for each string below.

Search forWhat it means
"@ngx-translate/core": "^17Pre-flight: Angular peer dep is now >=18. See Angular 16 → 18 peer dependency bump.
TranslateModuleReplace module imports with providers. The module is removed in v18.
TranslateModule.forRootReplace with provideTranslateService() in your bootstrap providers.
TranslateModule.forChildReplace with provideTranslateService() (isolated) or provideChildTranslateService() (connected, parent fallback).
.currentLangNow a Signal. Read sites need (): translate.currentLang().
useDefaultLangRemoved. Use fallbackLang only.
defaultLanguageRenamed to fallbackLang in the config object.
setDefaultLangRenamed to setFallbackLang.
getDefaultLangRenamed to getFallbackLang.
defaultLang (property access)Replaced by the fallbackLang signal: translate.fallbackLang().
onDefaultLangChangeRenamed to onFallbackLangChange.
DefaultLangChangeEventRenamed to FallbackLangChangeEvent.
setValue(Removed. Use insertValue(), which returns a new object instead of mutating.
extend: trueRemoved from the root config. Use provideChildTranslateService() for connected children.
translate.langsReplaced by translate.getLangs().
<el translate>KEY</el> (content as key)Deprecated; will be removed in v19. Use [translate]="'KEY'" or *translateBlock.
onTranslationChange (on TranslateStore)Renamed to translationChange$.
store.getTranslation(Removed. Use service.instant(key) or store.getTranslationValue(lang, key).
store.getValue(Renamed to store.getTranslationValue() and now public.

If your build or runtime breaks, find the message below.

Error or warningFix
npm ERR! ERESOLVE could not resolvepeer @angular/core@">=18"Upgrade Angular first. See Angular 16 → 18 peer dependency bump.
Cannot read currentLang of undefined / unexpected null from currentLangcurrentLang is now a Signal<Language | null>. Call it: translate.currentLang().
TS: Property 'defaultLang' does not exist on type 'TranslateService'Replace with translate.fallbackLang() (Signal) or translate.getFallbackLang().
TS: Property 'setDefaultLang' does not exist on type 'TranslateService'Replace with translate.setFallbackLang(lang).
TS: Property 'onDefaultLangChange' does not exist on type 'TranslateService'Replace with translate.onFallbackLangChange.
TS: Module '"@ngx-translate/core"' has no exported member 'TranslateModule'.Remove module imports. Use provideTranslateService() and import TranslatePipe / TranslateDirective directly in component imports.
TS: 'defaultLanguage' does not exist in type 'RootTranslateServiceConfig'Rename the config key to fallbackLang.
TS: 'useDefaultLang' does not exist in type 'RootTranslateServiceConfig'Remove it. fallbackLang is enough.
TS: 'extend' does not exist in type 'ChildTranslateServiceConfig'Move to provideChildTranslateService() (where it is no longer needed, since connected children are the default).
TS: 'langs' does not exist on type 'TranslateService'Use translate.getLangs().
TS: Argument of type 'string' is not assignable to parameter of type 'string | null' on getCurrentLang() returngetCurrentLang() now returns Language | null. Add a null check or default.
TS: Cannot find name 'DefaultLangChangeEvent'Use FallbackLangChangeEvent.
TS: Cannot find name 'setValue'Use insertValue() and assign the return value back.
Console warn: @ngx-translate/core: "<field>" received a bare class (<ClassName>); auto-wrapping with <helperName>(). For clarity, prefer <field>: <helperName>(<ClassName>).Wrap your class with the matching provider helper, e.g. loader: provideTranslateLoader(MyLoader).
Console warn: @ngx-translate/core: failed to load "<lang>". currentLang was NOT changed… or child failed to load "<lang>". Cause:Your loader threw. v18 logs failures instead of swallowing them. Inspect the loader.
Console warn: @ngx-translate/http-loader: error loading translation for <lang>HTTP request failed. The language load now returns {} for that resource by default; opt back into v17 fail-fast with failOnError: true.
Console warn: deprecation warning on <el translate>KEY</el>Element-text-as-key is deprecated. Use [translate]="'KEY'" or *translateBlock.

v18 raises the minimum Angular peer dependency from ^16.0.0 to >=18 (Angular 18 through 22 are all supported). Projects on Angular 16 or 17 must upgrade Angular first (ng update @angular/core@18) before installing @ngx-translate/core@18. The library uses signal-based reactivity and Angular DI features that landed in Angular 18.

If you’re on Angular 18 or newer already, no action needed.

Old code (v17):

app.module.ts
@NgModule({
imports: [
TranslateModule.forRoot({
loader: { provide: TranslateLoader, useClass: MyLoader },
fallbackLang: "en",
}),
],
})
export class AppModule {}

TypeScript error: Module '"@ngx-translate/core"' has no exported member 'TranslateModule'.

Fix (v18):

main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import {
provideTranslateService,
provideTranslateLoader,
} from "@ngx-translate/core";
bootstrapApplication(AppComponent, {
providers: [
provideTranslateService({
loader: provideTranslateLoader(MyLoader),
fallbackLang: "en",
}),
],
});
my.component.ts
import { TranslatePipe, TranslateDirective } from "@ngx-translate/core";
@Component({
imports: [TranslatePipe, TranslateDirective],
template: `{{ 'HELLO' | translate }}`,
})
export class MyComponent {}

A child with extend: true was a connected child sharing the parent’s translation chain. In v18 this is what provideChildTranslateService() does by default.

Old code (v17):

@NgModule({
imports: [
TranslateModule.forChild({
extend: true,
loader: { provide: TranslateLoader, useClass: FeatureLoader },
}),
],
})
export class FeatureModule {}

Symptom: The TranslateModule import itself fails to resolve (Module '"@ngx-translate/core"' has no exported member 'TranslateModule'.), so there is no specific error for extend: true. The whole forChild call is gone. Any code still importing TranslateModule won’t compile.

Fix (v18), route-level providers:

app.routes.ts
import { provideChildTranslateService, provideTranslateLoader } from "@ngx-translate/core";
export const routes: Routes = [
{
path: "feature",
providers: [
provideChildTranslateService({
loader: provideTranslateLoader(FeatureLoader),
}),
],
component: FeatureComponent,
},
];

Translation lookup checks the child store first, then walks up to the parent. Child writes never reach the parent store.

An isolated child was a fully independent service with its own language state. In v18, provideTranslateService() creates exactly that: a root-level service with its own store, language, and fallback.

Old code (v17):

@NgModule({
imports: [
TranslateModule.forChild({
isolate: true,
loader: { provide: TranslateLoader, useClass: FeatureLoader },
}),
],
})
export class FeatureModule {}

Symptom: As above, the TranslateModule import fails to resolve, so isolate: true has no dedicated error. The whole forChild call needs to be replaced.

Fix (v18):

app.routes.ts
import { provideTranslateService, provideTranslateLoader } from "@ngx-translate/core";
export const routes: Routes = [
{
path: "feature",
providers: [
provideTranslateService({
loader: provideTranslateLoader(FeatureLoader),
}),
],
component: FeatureComponent,
},
];

Both config keys have been removed. Use fallbackLang only.

Old code (v17 module config, or v17 provider after the deprecation alias):

TranslateModule.forRoot({
defaultLanguage: "en",
useDefaultLang: true,
});

TypeScript error after upgrading: Object literal may only specify known properties, and 'defaultLanguage' does not exist in type 'RootTranslateServiceConfig' — appears when v17 config keys are passed to v18’s provideTranslateService.

Fix (v18):

provideTranslateService({
fallbackLang: "en",
});

In v17, currentLang was a string getter. In v18 it is a Signal<Language | null>.

Old code (v17):

if (translate.currentLang === "en") {
// ...
}
const lang: string = translate.currentLang;

Runtime symptom: Comparisons silently return false because the signal function is compared to a string. TypeScript may not catch this on its own.

Fix (v18):

if (translate.currentLang() === "en") {
// ...
}
const lang: string | null = translate.currentLang();

The upside: currentLang now plugs straight into computed() and effect(), and Angular template change detection tracks it automatically.

The return type changed from Language to Language | null. Before any language has been set, it returns null.

Old code (v17):

const lang: string = translate.getCurrentLang();

TypeScript error: Type 'string | null' is not assignable to type 'string'.

Fix (v18):

const lang: string | null = translate.getCurrentLang();
// or guard:
const lang = translate.getCurrentLang() ?? "en";

defaultLang, setDefaultLang(), getDefaultLang(), and onDefaultLangChange have been removed.

Old code (v17):

translate.setDefaultLang("en");
const lang = translate.getDefaultLang();
const lang2 = translate.defaultLang;
translate.onDefaultLangChange.subscribe(({ lang }) => console.log(lang));

TypeScript error: Property 'setDefaultLang' does not exist on type 'TranslateService'.

Fix (v18):

translate.setFallbackLang("en");
const lang = translate.getFallbackLang();
const lang2 = translate.fallbackLang(); // Signal
translate.onFallbackLangChange.subscribe(({ lang }) => console.log(lang));

Old code (v17):

import { DefaultLangChangeEvent } from "@ngx-translate/core";
translate.onDefaultLangChange.subscribe((event: DefaultLangChangeEvent) => {
console.log(event.lang);
});

TypeScript error: Module '"@ngx-translate/core"' has no exported member 'DefaultLangChangeEvent'.

Fix (v18):

import { FallbackLangChangeEvent } from "@ngx-translate/core";
translate.onFallbackLangChange.subscribe((event: FallbackLangChangeEvent) => {
console.log(event.lang);
});

The mutating setValue() utility is gone. insertValue() returns a new object instead of mutating in place, so you need to assign the result.

Old code (v17):

import { setValue } from "@ngx-translate/core";
setValue(translations, "nested.key", "value"); // mutates translations

TypeScript error: Module '"@ngx-translate/core"' has no exported member 'setValue'.

Fix (v18):

import { insertValue } from "@ngx-translate/core";
translations = insertValue(translations, "nested.key", "value");

In v17, an extend: true child shared the parent’s TranslateStore. In v18, provideChildTranslateService() creates its own TranslateStore and the lookup walks the parent chain: local translations first, parent translations as fallback. Child writes stay in the child store.

Silent behavior change:

  • Sibling lazy modules no longer accidentally see each other’s translations.
  • A child that intentionally wrote into the shared store to surface keys at the root no longer does so. Use the parent injection context if you need to write to the root.
  • Code that read store.translations after a write must re-read, because state updates are now immutable.

If your child needs to be fully independent (no parent fallback at all), use provideTranslateService() at the child level instead.

Loading state inherits downward. v18 introduces isLoading: Signal<boolean> on every service in the hierarchy. A load triggered at the root marks the root and all descendants as loading; a load triggered at a child marks only that child’s subtree. See isLoading and the loading-indicator recipe for details.

setTranslation(lang, translations) no longer silently merges based on the v17 extend: true config (which is also gone). Pass shouldMerge explicitly.

Old code (v17):

// merged automatically because extend: true was set on the service
translate.setTranslation("en", newTranslations);

Silent behavior change: If your service was previously configured with extend: true, setTranslation(lang, translations) auto-merged. In v18, extend: true is gone and the call always replaces by default. Pass true as the third argument to keep the merge behavior.

Fix (v18):

translate.setTranslation("en", newTranslations, true);

[translate]="'KEY'" now sets el.textContent directly. Any child HTML inside the element is overwritten.

Old code (v17), may have rendered the <b>:

<span [translate]="'GREETING'"><b>Bold child</b></span>

Runtime symptom: Child HTML disappears after first render.

Fix (v18): Pull the markup out of the translated element.

<span [translate]="'GREETING'"></span>

Using element text content as the key still works in v18, but emits a console.warn and will be removed in v19.

Old code (v17), still works in v18 with a warning:

<span translate>HELLO</span>

Console warning: A deprecation message with the offending element as a second argument so DevTools can highlight it.

Fix (v18):

<span [translate]="'HELLO'"></span>
<!-- or, for multiple keys in one block -->
<ng-container *translateBlock="let t">
<span>{{ t('HELLO') }}</span>
</ng-container>

Old code (v17):

const languages = translate.langs;

TypeScript error: Property 'langs' does not exist on type 'TranslateService'.

Fix (v18):

const languages = translate.getLangs();

get("") and instant("") now return an empty string instead of throwing. If you wrapped these calls in try/catch to detect an empty key, switch to an explicit check.

Old code (v17):

try {
const value = translate.instant(key);
} catch {
// handle empty key
}

Fix (v18):

if (!key) {
// handle empty key explicitly
}
const value = translate.instant(key);

A 404 on a translation file no longer fails the whole language load. v18 catches the error per resource, logs a console.warn, and treats the missing resource as {}.

Old code (v17): A 404 propagated, failing the language load.

Fix (v18): No code change needed if the new behavior is what you want. To restore v17 fail-fast (useful for catching missing translation files during deploy), pass failOnError:

provideTranslateHttpLoader({
prefix: "/assets/i18n/",
failOnError: true,
});

Most users do not touch TranslateStore. If you inject it directly:

  • store.getCurrentLang(), store.setCurrentLang(), store.onLangChange → use TranslateService instead.
  • store.onTranslationChange → renamed to store.translationChange$, plus a store.lastTranslationChange signal for template/computed reads.
  • store.getTranslation(key) → removed. Use service.instant(key) or store.getTranslationValue(lang, key).
  • store.getValue(lang, key) → renamed to public store.getTranslationValue(lang, key).
  • State updates are immutable: re-read store.translations() or store.getTranslations(lang) after a write.

TranslatePipe.lastKey, lastParams, updateValue(), and TranslateDirective.checkNodes(), updateValue(), getContent(), setContent() have been removed. These were never part of the public API.

v18 finishes the migration to Angular’s standalone provider model. TranslateModule.forRoot/forChild are removed; provideTranslateService() and provideChildTranslateService() configure the same things with less boilerplate and better tree-shaking. Pipe and directive are imported directly into a component’s imports array.

Language state is now reactive. currentLang and fallbackLang are Signals, which means they compose naturally with computed(), effect(), and Angular’s template change detection, no manual onLangChange subscriptions for simple reactivity. The Observable API (onLangChange, onFallbackLangChange, onTranslationChange) is still there for push-style flows.

Naming and types match the Angular team’s inject()-first style: provider helpers, factory function support across all plugins, and stricter return types (getCurrentLang(): Language | null) that surface bugs at compile time instead of at runtime.

  • Skim the v18 release notes for new features (signal-driven pipe/directive, *translateBlock, per-call lang, translate() standalone function, hierarchical services, setCompiledTranslation() for precompiled payloads, getRoot()/getParent() for chain introspection).
  • For NgModule users, the NgModules support page covers how to bridge provideTranslateService() into a legacy module.
  • File anything missing or wrong on the ngx-translate/core issue tracker.
Imprint Privacy