What's new in @ngx-translate/core v18-rc.0
v18 rebuilds ngx-translate on Angular Signals. The library is now fully standalone — TranslateModule is gone — and gains hierarchical service isolation, a block directive for templates, and per-call language selection. Existing template syntax (| translate, [translate]) works as before.
Requires Angular 18 or later. The library uses effect(), takeUntilDestroyed(), and stable signal APIs that are only available from Angular 18+.
Highlights
- Signals everywhere —
currentLang,fallbackLang, internal state management, the pipe, and the directive are all signal-driven. The new standalonetranslate()function returns a reactiveSignal— no subscriptions, no cleanup. *translateBlockdirective — Exposes a typed translation function to a template block. Cleaner than chaining pipes when you need multiple keys in one place.- Hierarchical services —
provideChildTranslateService()creates an isolated child with its own store and loader. Translations resolve child-first, then walk up to the parent.provideTranslateService()creates a fully independent root. - Per-call language override —
translate(),get(),instant(),stream(), andgetParsedResult()accept an optionallangparameter to bypass the current/fallback chain.
BREAKING CHANGES
TranslateModule removed — Impact: High
TranslateModule, TranslateModule.forRoot(), and TranslateModule.forChild() have been removed. Use the standalone provider functions and import the pipe/directive directly:
// v17
@NgModule({
imports: [
TranslateModule.forRoot({
loader: { provide: TranslateLoader, useClass: MyLoader },
compiler: { provide: TranslateCompiler, useClass: MyCompiler },
parser: { provide: TranslateParser, useClass: MyParser },
missingTranslationHandler: { provide: MissingTranslationHandler, useClass: MyHandler },
defaultLanguage: "en",
}),
],
})
// v18 — in bootstrapApplication() or route config
providers: [
provideTranslateService({
loader: provideTranslateLoader(MyLoader),
compiler: provideTranslateCompiler(MyCompiler),
parser: provideTranslateParser(MyParser),
missingTranslationHandler: provideMissingTranslationHandler(MyHandler),
fallbackLang: "en",
}),
]
// Component imports (replace TranslateModule in component imports)
@Component({
imports: [TranslatePipe, TranslateDirective],
})currentLang is now a Signal — Impact: High
TranslateService.currentLang was a plain string getter in v17. In v18 it returns a Signal<Language | null>. Any code that reads it as a string must call the signal:
// v17
if (service.currentLang === "en") { ... }
// v18
if (service.currentLang() === "en") { ... }The upside: currentLang now works directly in computed() and effect(), and Angular's template change detection tracks it automatically — no manual subscriptions needed.
getCurrentLang() can return null — Impact: Medium
The return type changed from Language to Language | null. Before any language is set, it returns null.
Deprecated defaultLang aliases removed — Impact: Medium
defaultLang, setDefaultLang(), getDefaultLang(), and onDefaultLangChange have been removed. These were deprecated v17 aliases for the fallback equivalents:
defaultLang→fallbackLangsignalsetDefaultLang(lang)→setFallbackLang(lang)getDefaultLang()→getFallbackLang()onDefaultLangChange→onFallbackLangChange
Deprecated config options useDefaultLang and defaultLanguage removed — Impact: Medium
These v16-era compatibility options have been removed from RootTranslateServiceConfig. Use fallbackLang instead.
extend config replaced by root/child hierarchy — Impact: Medium
The extend: true config option has been removed. Use provideChildTranslateService() instead — it automatically creates a non-root service. Child services delegate language selection to the root and maintain their own translation loader.
// v17
TranslateModule.forChild({
loader: { provide: TranslateLoader, useClass: FeatureLoader },
extend: true,
})
// v18
provideChildTranslateService({
loader: provideTranslateLoader(FeatureLoader),
})Child services now have their own store — Impact: Medium
provideChildTranslateService() now creates its own TranslateStore instance with a full set of default plugin providers. Child services look up translations in their own store first, then walk up the parent chain — so lazy-loaded modules can still access translations provided by the parent, while local translations take precedence. Child translations never write to the parent store, keeping scopes cleanly separated. To create a fully independent service (no parent fallback), use provideTranslateService() — which creates a root-level service with its own language state and store.
Mixed child content in [translate] elements — Impact: Medium
The directive now sets el.textContent directly for explicit-key bindings, replacing all child content. In v17, individual text nodes were updated, potentially preserving sibling HTML. If you have mixed content inside translated elements, restructure:
<!-- This will lose the <b> in v18 -->
<span [translate]="'GREETING'"><b>Bold</b></span>
<!-- Instead, restructure -->
<span [translate]="'GREETING'"></span>setValue() removed — Impact: Low
The deprecated setValue() utility function has been removed. Use insertValue() instead. Note that insertValue() returns a new object rather than mutating in place:
// v17 (mutation)
setValue(obj, "a.b", 42);
// v18 (immutable — assign the return value)
obj = insertValue(obj, "a.b", 42);TranslateStore rewritten with Angular Signals — Impact: Low
The store's internal state is now held in WritableSignals with public read-only Signal accessors:
translations: Signal<Record<Language, InterpolatableTranslationObject>>languages: Signal<Language[]>lastTranslationChange: Signal<TranslationChangeEvent | null>
The translationChange$ observable is still available for push-based subscribers.
All state updates are now immutable — each setTranslations() or deleteTranslations() call produces a new object reference, enabling proper signal change detection.
Language management moved from store to service — Impact: Low
currentLang, fallbackLang, and their associated getters, setters, and observables (onLangChange, onFallbackLangChange) have been removed from TranslateStore. These responsibilities now live in TranslateService.
getTranslation(key) removed from store — Impact: Low
The convenience method that looked up a key against current + fallback language has been removed because the store no longer tracks language selection. Use TranslateService.instant(key) or store.getTranslationValue(language, key).
getValue() → getTranslationValue() — Impact: Low
The protected getValue(language, key) method is now public and renamed to getTranslationValue(language, key).
onTranslationChange → translationChange$ — Impact: Low
The observable getter onTranslationChange has been replaced by the translationChange$ property. For template-reactive reads, use the lastTranslationChange signal instead.
langs getter removed — Impact: Low
Replace translateService.langs with translateService.getLangs().
currentLoader and compiler are now protected — Impact: Low
These properties are no longer part of the public API. Use the service's public methods instead.
Empty keys no longer throw — Impact: Low
get("") and instant("") now return empty strings instead of throwing an Error. Code that relied on catching those exceptions needs adjustment.
setTranslation() no longer auto-merges with extend — Impact: Low
In v17, setTranslation() would force-merge when the service had extend: true. In v18, merging only happens when shouldMerge is explicitly true.
DefaultLangChangeEvent type removed — Impact: Low
The deprecated type alias has been removed. Use FallbackLangChangeEvent directly.
TranslatePipe rewritten with signals — Impact: Low
The pipe is now powered by TranslateService.translate() signals internally. All manual RxJS subscriptions, ChangeDetectorRef.markForCheck(), and OnDestroy cleanup are gone. Template usage is unchanged — {{ 'KEY' | translate }} works exactly as before. Previously-public internals (lastKey, lastParams, updateValue(), subscription fields) are removed.
TranslateDirective rewritten with signals — Impact: Low
The directive now uses WritableSignals for key/params and an effect() for DOM writes when using explicit key binding ([translate]="'KEY'"). The content-as-key path is delegated to ContentKeyHandler (deprecated). Manual subscriptions replaced by onTranslationRefresh + takeUntilDestroyed. Previously-public methods (checkNodes(), updateValue(), getContent(), setContent()) are removed.
Internal
- Plugin base classes (
TranslateLoader,TranslateCompiler,TranslateParser,MissingTranslationHandler) are unchanged in API and behavior InterpolatableTranslationObjecttype extracted fromtranslate.service.tsintotranslate.service.interface.ts(no consumer impact)
New Features
Hierarchical services with isolation
provideChildTranslateService() creates a child service with its own TranslateStore and loader, scoped to a lazy-loaded module or component subtree. Child services delegate language selection (current/fallback lang) to the root, but load and store translations independently — child translations never write to the parent store.
A child can read translations from its parent (and up the chain), but siblings cannot see each other's translations. This gives each feature module a clean, isolated scope while still inheriting shared keys from the root.
// Root — in bootstrapApplication() or app config
providers: [
provideTranslateService({
loader: provideTranslateHttpLoader(),
}),
]
// Lazy route or component — child service with its own translations
providers: [
provideChildTranslateService({
loader: provideTranslateHttpLoader({ prefix: "/assets/i18n/feature/" }),
}),
]Translation lookup resolves child-first, then walks up to the parent. Local keys take precedence; shared keys from the root remain accessible.
To create a fully independent service with no parent fallback, use provideTranslateService() instead — it creates a separate root with its own language state and store.
Standalone translate() function
A standalone function that returns a Signal<Translation>, usable in component class code within an injection context:
@Component({ ... })
export class MyComponent {
greeting = translate('HELLO');
dynamic = translate(this.keySignal, this.paramsSignal);
}Accepts plain values or Signal inputs for key, params, and lang. Updates automatically on language changes, translation reloads, or input signal changes. No subscriptions or cleanup needed.
The same API is available as TranslateService.translate(key, params?, lang?) for use outside injection context.
*translateBlock structural directive
Exposes a typed translation function to a template block, ideal when multiple keys are needed:
<ng-container *translateBlock="let t">
<h1>{{ t('TITLE') }}</h1>
<p>{{ t('GREETING', { name: userName }) }}</p>
</ng-container>Delegates to TranslateService.instant() internally, so Angular's signal tracking keeps translations up to date.
Optional lang parameter on lookup methods
translate(), get(), instant(), stream(), getStreamOnTranslationChange(), and getParsedResult() now accept an optional lang parameter to look up translations in a specific language, bypassing the current/fallback chain.
setCompiledTranslation()
Stores pre-compiled translations without running them through the TranslateCompiler. Useful for build-time pre-compilation.
getTranslations()
Public read access to stored translations for a given language, returned as DeepReadonly.
onTranslationRefresh
A convenience observable that merges all events that could affect displayed translations (lang changes, fallback changes, translation updates for active languages). Use this when you need a single stream that fires whenever displayed translations might have changed.
ITranslateService abstract class
The ITranslateService abstract class has been moved from translate.service.ts into its own file, translate.service.interface.ts. Consumers can type against the interface for testing and library authoring. All shared type aliases (Language, Translation, InterpolationParameters, etc.) are also centralized here.
Factory function support for all plugin providers
provideTranslateLoader(), provideTranslateCompiler(), provideTranslateParser(), and provideMissingTranslationHandler() now accept factory functions in addition to classes:
provideTranslateLoader(() => new MyLoader(someConfig))
provideTranslateCompiler(() => new MyCompiler())
provideTranslateParser(() => new MyParser())
provideMissingTranslationHandler(() => new MyHandler(someConfig))The existing class-based usage continues to work unchanged.
Improvements
getValue() — stricter array index validation
Array index segments in key paths are now validated with /^\d+$/ before parsing. Previously, parseInt coercion meant strings like "1abc" would silently resolve to index 1. In v18 they correctly return undefined.
getValue() — array length support
getValue() now supports reading array length via key paths:
getValue({ items: [1, 2, 3] }, "items.length"); // 3 (was undefined in v17)Translation load errors are now logged
Failed translation loads now emit console.warn instead of being silently swallowed. The core service logs @ngx-translate/core: error loading translations for <lang> and the HTTP loader logs @ngx-translate/http-loader: error loading translation for <lang>. This applies to all loaders, not just the HTTP loader.
Translation loading is now per-language cached
Each language load is tracked independently with shareReplay({ bufferSize: 1, refCount: true }). Concurrent requests for the same language are deduped, and completed loads are replayed without re-fetching.
Several private members are now protected
parser, missingTranslationHandler, store, and several internal methods (loadOrExtendLanguage, changeLang, loadAndCompileTranslations, getParsedResultForKey, interpolation helpers) are now protected instead of private, making subclassing easier.
mergeDeep utility is now a public export
The mergeDeep(target, source) deep-merge utility is now exported from @ngx-translate/core. The HTTP loader uses it internally for multi-resource merging; custom loaders can use it too.
HTTP Loader
The HTTP loader gains built-in multi-resource support and better error handling, replacing the need for the separate @codeandweb/ngx-translate-multi-http-loader package.
Built-in multi-resource loading
provideTranslateHttpLoader({
resources: [
"/assets/i18n/common/",
{ prefix: "/assets/i18n/feature/", suffix: ".json" },
],
})Or use provideTranslateMultiHttpLoader() explicitly. Results are loaded in parallel and deep-merged.
Per-resource error resilience
Failed HTTP requests (e.g. 404s) are now caught per-resource and replaced with an empty object, with a console.warn for each failure. The remaining resources still contribute their keys. In v17, a single 404 would fail the entire language load.
prefix and suffix now optional
TranslateHttpLoaderConfig.prefix and .suffix are now optional, defaulting to "/assets/i18n/" and ".json".
Deprecations
Element text as translation key
Using <span translate>HELLO</span> (where the element's text content is the key) now produces a console deprecation warning. The behavior still works but will be removed in the next major version. Use [translate]="'HELLO'" or *translateBlock instead.