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 standalone translate() function returns a reactive Signal β no subscriptions, no cleanup.
*translateBlock directive β 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(), and getParsedResult() accept an optional lang parameter 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 β fallbackLang signal
setDefaultLang(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
InterpolatableTranslationObject type extracted from translate.service.ts into translate.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.
What's new in @ngx-translate/core v18-rc.0
v18 rebuilds ngx-translate on Angular Signals. The library is now fully standalone β
TranslateModuleis 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
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.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.translate(),get(),instant(),stream(), andgetParsedResult()accept an optionallangparameter to bypass the current/fallback chain.BREAKING CHANGES
TranslateModuleremoved β Impact: HighTranslateModule,TranslateModule.forRoot(), andTranslateModule.forChild()have been removed. Use the standalone provider functions and import the pipe/directive directly:currentLangis now a Signal β Impact: HighTranslateService.currentLangwas a plain string getter in v17. In v18 it returns aSignal<Language | null>. Any code that reads it as a string must call the signal:The upside:
currentLangnow works directly incomputed()andeffect(), and Angular's template change detection tracks it automatically β no manual subscriptions needed.getCurrentLang()can returnnullβ Impact: MediumThe return type changed from
LanguagetoLanguage | null. Before any language is set, it returnsnull.Deprecated
defaultLangaliases removed β Impact: MediumdefaultLang,setDefaultLang(),getDefaultLang(), andonDefaultLangChangehave been removed. These were deprecated v17 aliases for the fallback equivalents:defaultLangβfallbackLangsignalsetDefaultLang(lang)βsetFallbackLang(lang)getDefaultLang()βgetFallbackLang()onDefaultLangChangeβonFallbackLangChangeDeprecated config options
useDefaultLanganddefaultLanguageremoved β Impact: MediumThese v16-era compatibility options have been removed from
RootTranslateServiceConfig. UsefallbackLanginstead.extendconfig replaced by root/child hierarchy β Impact: MediumThe
extend: trueconfig option has been removed. UseprovideChildTranslateService()instead β it automatically creates a non-root service. Child services delegate language selection to the root and maintain their own translation loader.Child services now have their own store β Impact: Medium
provideChildTranslateService()now creates its ownTranslateStoreinstance 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), useprovideTranslateService()β which creates a root-level service with its own language state and store.Mixed child content in
[translate]elements β Impact: MediumThe directive now sets
el.textContentdirectly 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:setValue()removed β Impact: LowThe deprecated
setValue()utility function has been removed. UseinsertValue()instead. Note thatinsertValue()returns a new object rather than mutating in place:TranslateStorerewritten with Angular Signals β Impact: LowThe store's internal state is now held in
WritableSignals with public read-onlySignalaccessors: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()ordeleteTranslations()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 fromTranslateStore. These responsibilities now live inTranslateService.getTranslation(key)removed from store β Impact: LowThe 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)orstore.getTranslationValue(language, key).getValue()βgetTranslationValue()β Impact: LowThe protected
getValue(language, key)method is now public and renamed togetTranslationValue(language, key).onTranslationChangeβtranslationChange$β Impact: LowThe observable getter
onTranslationChangehas been replaced by thetranslationChange$property. For template-reactive reads, use thelastTranslationChangesignal instead.langsgetter removed β Impact: LowReplace
translateService.langswithtranslateService.getLangs().currentLoaderandcompilerare now protected β Impact: LowThese properties are no longer part of the public API. Use the service's public methods instead.
Empty keys no longer throw β Impact: Low
get("")andinstant("")now return empty strings instead of throwing anError. Code that relied on catching those exceptions needs adjustment.setTranslation()no longer auto-merges withextendβ Impact: LowIn v17,
setTranslation()would force-merge when the service hadextend: true. In v18, merging only happens whenshouldMergeis explicitlytrue.DefaultLangChangeEventtype removed β Impact: LowThe deprecated type alias has been removed. Use
FallbackLangChangeEventdirectly.TranslatePiperewritten with signals β Impact: LowThe pipe is now powered by
TranslateService.translate()signals internally. All manual RxJS subscriptions,ChangeDetectorRef.markForCheck(), andOnDestroycleanup are gone. Template usage is unchanged β{{ 'KEY' | translate }}works exactly as before. Previously-public internals (lastKey,lastParams,updateValue(), subscription fields) are removed.TranslateDirectiverewritten with signals β Impact: LowThe directive now uses
WritableSignals for key/params and aneffect()for DOM writes when using explicit key binding ([translate]="'KEY'"). The content-as-key path is delegated toContentKeyHandler(deprecated). Manual subscriptions replaced byonTranslationRefresh+takeUntilDestroyed. Previously-public methods (checkNodes(),updateValue(),getContent(),setContent()) are removed.Internal
TranslateLoader,TranslateCompiler,TranslateParser,MissingTranslationHandler) are unchanged in API and behaviorInterpolatableTranslationObjecttype extracted fromtranslate.service.tsintotranslate.service.interface.ts(no consumer impact)New Features
Hierarchical services with isolation
provideChildTranslateService()creates a child service with its ownTranslateStoreand 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.
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()functionA standalone function that returns a
Signal<Translation>, usable in component class code within an injection context:Accepts plain values or
Signalinputs 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.*translateBlockstructural directiveExposes a typed translation function to a template block, ideal when multiple keys are needed:
Delegates to
TranslateService.instant()internally, so Angular's signal tracking keeps translations up to date.Optional
langparameter on lookup methodstranslate(),get(),instant(),stream(),getStreamOnTranslationChange(), andgetParsedResult()now accept an optionallangparameter 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.onTranslationRefreshA 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.
ITranslateServiceabstract classThe
ITranslateServiceabstract class has been moved fromtranslate.service.tsinto 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(), andprovideMissingTranslationHandler()now accept factory functions in addition to classes:The existing class-based usage continues to work unchanged.
Improvements
getValue()β stricter array index validationArray index segments in key paths are now validated with
/^\d+$/before parsing. Previously,parseIntcoercion meant strings like"1abc"would silently resolve to index1. In v18 they correctly returnundefined.getValue()β arraylengthsupportgetValue()now supports reading array length via key paths:Translation load errors are now logged
Failed translation loads now emit
console.warninstead 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
privatemembers are nowprotectedparser,missingTranslationHandler,store, and several internal methods (loadOrExtendLanguage,changeLang,loadAndCompileTranslations,getParsedResultForKey, interpolation helpers) are nowprotectedinstead ofprivate, making subclassing easier.mergeDeeputility is now a public exportThe
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-loaderpackage.Built-in multi-resource loading
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.warnfor each failure. The remaining resources still contribute their keys. In v17, a single 404 would fail the entire language load.prefixandsuffixnow optionalTranslateHttpLoaderConfig.prefixand.suffixare 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*translateBlockinstead.