@rerune/angular
v1.6.0
Published
Native Angular OTA translation adapters for ngx-translate and Transloco.
Readme
@rerune/angular
@rerune/angular is the Angular binding layer for ReRune's OTA runtime, with native ngx-translate and Transloco integrations.
This guide targets SDK 1.5.0. Combined native providers are available since 1.4.0.
Requirements
- Angular 18 or newer and RxJS 7.
- An application using ngx-translate 18 or Transloco 8, configured normally.
- Bundled translations and any native loader/compiler plugins your app needs.
- A publishable/read-only OTA publish ID for browser runtime fetches.
Install only the engine your app uses. Both adapters ship in the same package, but neither requires the other engine:
npm install @rerune/[email protected] @ngx-translate/core@18Or, for a Transloco application:
npm install @rerune/[email protected] @jsverse/transloco@8Use @rerune/angular/ngx-translate or @rerune/angular/transloco for setup and service imports. The package root does not export a setup API. Older engine majors require an upgrade; see Compatibility Evidence for tested Angular versions.
Primary Integration Path
For most apps, the intended public path is:
- Keep your existing translation engine configuration and loaders.
- Replace its root provider call with
ReRune.provide({ otaPublishId }, nativeOptions), passing the same native options as the second argument. - Keep using native pipes, directives, services, language switching, and bundled loaders.
ReRune handles OTA fetch, cache, merge, and update notification. It does not add a ReRune-specific translation pipe or t() wrapper. Register exactly one adapter, once, at the application root. Do not add it to lazy routes or child translation providers.
Browser Setup
ReRune.provide(...) registers the selected native engine internally when you
pass its configuration as the second argument. You do not also call
provideTranslateService(...) or provideTransloco(...). Keep your loader and
language settings once in that native configuration.
Existing applications keep their loaders, parsers, compilers, plugins, and fallback configuration. The second argument has the native provider's own TypeScript type. Replace only the root translation provider call, keeping your zone/zoneless, routing, hydration, and separate plugin providers in place.
Each comparison starts with an app using its native engine without ReRune, not an older ReRune SDK. Choose your engine, then replace its "Before" setup with "After". Do not combine both alternatives.
Bundled Translations
This in-memory loader works with either example. It stands in for your existing JSON/HTTP loader; you do not need to move translations into TypeScript to use ReRune.
If your base resources require HTTP, their offline availability remains your loader's responsibility. ReRune does not cache those loader responses.
// translations.ts
import { Injectable } from '@angular/core'
import { of } from 'rxjs'
const resources: Record<string, Record<string, string>> = {
en: {
headline: 'Bundled headline',
greeting: 'Hello, {{name}}',
},
de: {
headline: 'Gebuendelte Ueberschrift',
greeting: 'Hallo, {{name}}',
},
}
@Injectable()
export class BundledLoader {
getTranslation(language: string) {
return of(resources[language] ?? {})
}
}ngx-translate
Before ReRune
// app.config.ts (ngx-translate)
import { ApplicationConfig } from '@angular/core'
import { provideTranslateService, TranslateLoader } from '@ngx-translate/core'
import { BundledLoader } from './translations'
export const appConfig: ApplicationConfig = {
providers: [
provideTranslateService({
lang: 'en',
fallbackLang: 'en',
loader: { provide: TranslateLoader, useClass: BundledLoader },
}),
],
}After ReRune
// app.config.ts (ngx-translate)
import { ApplicationConfig } from '@angular/core'
import { TranslateLoader } from '@ngx-translate/core'
import { ReRune } from '@rerune/angular/ngx-translate'
import { BundledLoader } from './translations'
export const appConfig: ApplicationConfig = {
providers: [
ReRune.provide({
otaPublishId: '<READ_ONLY_OTA_PUBLISH_ID>',
supportedLocales: ['en', 'de'],
}, {
lang: 'en',
fallbackLang: 'en',
loader: { provide: TranslateLoader, useClass: BundledLoader },
}),
],
}Replace the provideTranslateService import and call with ReRune. The native
options object moves unchanged to the second argument. Keep TranslateLoader
and your loader class. Do not register provideTranslateService(...) separately
when passing native options to ReRune.
supportedLocales is needed here because the loader knows about German, but
ngx-translate has not registered that language yet. Supply this hint for
unregistered lazy-loaded root languages; it is not a second language-selection
setting. Transloco exposes its language list natively, as shown below.
ngx-translate 18 uses provider functions;
older TranslateModule.forRoot(...) examples do not apply to this supported
engine version.
Transloco
Before ReRune
// app.config.ts (Transloco)
import { ApplicationConfig } from '@angular/core'
import { provideTransloco } from '@jsverse/transloco'
import { BundledLoader } from './translations'
export const appConfig: ApplicationConfig = {
providers: [
provideTransloco({
config: {
availableLangs: ['en', 'de'],
defaultLang: 'en',
fallbackLang: 'en',
reRenderOnLangChange: true,
},
loader: BundledLoader,
}),
],
}After ReRune
// app.config.ts (Transloco)
import { ApplicationConfig } from '@angular/core'
import { ReRune } from '@rerune/angular/transloco'
import { BundledLoader } from './translations'
export const appConfig: ApplicationConfig = {
providers: [
ReRune.provide({
otaPublishId: '<READ_ONLY_OTA_PUBLISH_ID>',
}, {
config: {
availableLangs: ['en', 'de'],
defaultLang: 'en',
fallbackLang: 'en',
reRenderOnLangChange: true,
},
loader: BundledLoader,
}),
],
}Replace the provideTransloco import and call with ReRune. Its native
config and loader move unchanged to the second argument, with no extra
ReRune language list. Do not also register provideTransloco(...).
Keep separate plugins, such as
provideTranslocoMessageformat(), after the combined provider just as they
followed the native provider. ReRune does not install or replace those plugins.
The engine owns the active language. ReRune derives configured languages from the engine. For ngx-translate, list additional lazy-loaded root languages in supportedLocales if the engine has not registered them yet. This lets ReRune distinguish app-owned loader languages from dashboard-only languages. Transloco normally provides this information through availableLangs.
Browser startup does not wait for OTA network delivery. The app can render bundled translations while ReRune restores its cache and checks for updates. The default browser store uses localStorage and falls back to memory when storage is unavailable or a write fails. No custom cache is needed for normal setup.
No custom fetch, interceptor, or response decoder is required for the hosted service. The manifest base is fixed internally to https://rerune.io/api; there is no public backend URL override. Requests use X-OTA-Publish-Id for ReRune identity.
Variants And Update Options
Add options to the same provider, not a second registration:
ReRune.provide({
otaPublishId: 'publishable-read-id',
variant: 'vip',
updatePolicy: { checkOnStart: true, periodicIntervalInHours: 24 },
logLevel: 'info',
}, nativeOptions)variant defaults to ReRune.Main. Use a lowercase published slug for audience-specific wording of the same key and language. Missing overrides fall back to Main content. Variants are separate from language selection and plural grammar. See the translation variants guide for the dashboard workflow.
The browser store restores a persisted selection before applying cached translations. That value wins over the provider's variant option, including a persisted Main. Use ReRuneService.setVariant(...) when the selection becomes known later.
Console logging defaults to off; structured errors and warnings remain available. Levels error, info, and verbose are cumulative. verbose can expose the OTA publish ID, translation payloads, and error details; enable it only when that disclosure is acceptable.
Angular Usage
Translation code stays the same before and after setup: keep existing pipes,
directives, services, and language-switch calls. The examples below add optional
status and refresh controls; those additions are not required for translations
to update. Inject ReRuneService only when a component needs those controls.
ngx-translate
// app.component.ts (ngx-translate)
import { Component, inject } from '@angular/core'
import { TranslatePipe, TranslateService } from '@ngx-translate/core'
import { ReRuneService } from '@rerune/angular/ngx-translate'
@Component({
selector: 'app-root',
standalone: true,
imports: [TranslatePipe],
template: `
<h1>{{ 'headline' | translate }}</h1>
<p>{{ 'greeting' | translate: { name: 'Ada' } }}</p>
<button (click)="translations.use('de').subscribe()">Deutsch</button>
<button (click)="rerune.checkForUpdates()">
Refresh ({{ rerune.state().status }})
</button>
`,
})
export class AppComponent {
readonly translations = inject(TranslateService)
readonly rerune = inject(ReRuneService)
}Transloco
// app.component.ts (Transloco)
import { Component, inject } from '@angular/core'
import { TranslocoPipe, TranslocoService } from '@jsverse/transloco'
import { ReRuneService } from '@rerune/angular/transloco'
@Component({
selector: 'app-root',
standalone: true,
imports: [TranslocoPipe],
template: `
<h1>{{ 'headline' | transloco }}</h1>
<p>{{ 'greeting' | transloco: { name: 'Ada' } }}</p>
<button (click)="translations.setActiveLang('de')">Deutsch</button>
<button (click)="rerune.checkForUpdates()">
Refresh ({{ rerune.state().status }})
</button>
`,
})
export class AppComponent {
readonly translations = inject(TranslocoService)
readonly rerune = inject(ReRuneService)
}Native directives work too. ReRune enables Transloco's reRenderOnLangChange while the adapter is active so existing views update after same-language OTA changes.
For a published cardinal plural such as cartItems, pass { count: 3 } through the normal pipe or service arguments. OTA plurals do not require a MessageFormat plugin. Bundled ICU strings still need your engine's native compiler/plugin; ReRune does not install one.
Within each locale, the merge order is:
- Application translations, including late root-loader results and native catalog writes.
- Remote OTA values for matching keys.
An active-language application value wins over a fallback-language OTA value. Missing keys can fall back through the parent language, manifest main language, and the engine's configured fallback behavior. Removing an OTA override restores the latest application value. An explicit empty published string remains an override; an untranslated record contributes no override.
rerune.state() is a read-only Angular signal. Its availableLocales and localeNames expose configured plus dashboard-published languages and their display names. Every published locale is synchronized, not just the current language. Keep using the engine's normal language-switch method.
Optional Runtime Controls
Inside an async method of a component or service that injects ReRuneService as rerune:
await this.rerune.ready()
const result = await this.rerune.checkForUpdates()
if (result.hasErrors) {
console.error('Translation refresh failed', result.errors)
}
await this.rerune.setVariant('vip')
await this.rerune.setVariant({ variant: 'vip', persist: true })
await this.rerune.setVariant()
await this.rerune.resetVariant()ready() waits for initial cache hydration and the configured startup check. It is not a guarantee that every app loader or OTA request succeeded. setVariant() selects Main for the current runtime without changing the stored choice. resetVariant() selects and persists Main. Effective changes rebuild cached overlays immediately and update rerune.state().variant.
Removing ReRune
Replace ReRune.provide(otaOptions, nativeOptions) with your engine's original
provideTranslateService(nativeOptions) or provideTransloco(nativeOptions).
Restore its native import and keep the same root configuration, loader,
compiler/parser, and separately registered plugins. Pipes, directives, and
native language-switch calls stay unchanged.
Remove optional ReRuneService status/refresh/variant controls if you added
them, then uninstall @rerune/angular. OTA delivery stops and ReRune's cache
is no longer consumed, so keep the messages you need in native bundles/loaders.
This is a code migration followed by an app restart, not a runtime provider toggle.
Startup, Offline, And Partial Updates
Offline startup is supported through the normal root ReRune.provide(...) setup:
- Browser bootstrap does not wait for OTA. The app can render its native bundled translations as they become available.
- ReRune reads browser storage asynchronously and overlays matching cached translations. The first render may show bundled copy while this read finishes.
- After cache restoration, the default startup check fetches OTA updates without blocking app startup. Network failures leave cached or bundled copy available.
- Valid locale updates are written through the cache store before becoming active in memory. Changed resources notify the native engine, so its reactive pipes/subscriptions can refresh displayed text.
You do not need to await ReRuneService.ready() to enable this browser flow.
SSR uses transferred data first and defers cache/network work until hydration
readiness. Server rendering intentionally waits for initialization, as described
below.
The default browser store persists data across reloads when storage works.
Its write-failure fallback lasts only in memory. Cache writes and
{ persist: true } are best effort, with no background persistence retry.
Dashboard-only languages need an earlier successful manifest and locale fetch
to be available offline; bundled languages do not. An app-owned HTTP loader
still needs its own offline strategy if its base resources are not bundled or
otherwise cached; ReRune does not cache those loader responses.
Updates are independent per language. If English succeeds while German fails,
English can use the new copy and German keeps its previously committed copy
(or native fallback if none exists). This is intentional. Inspect
rerune.state().lastResult or the result of checkForUpdates() for errors.
SSR And SEO Preload
Browser-only OTA cannot update HTML already sent to a crawler. Keep Angular's normal server rendering and hydration configuration, with the same root ReRune provider and native translation setup on server and browser. Unlike React, Angular does not need a separate ReRune.preload(...) call.
On the server, the provider waits for its configured initial OTA check and writes canonical translation payloads, the selected variant, and main language into Angular TransferState. The browser consumes this state synchronously before asynchronous cache restoration and synchronization. Keep startup checks enabled when server-rendered OTA copy is required.
The browser waits for the first root bootstrap before starting cache restoration or network work, so persisted preferences cannot replace the transferred copy before that first render. A stored variant may change the text afterward. Normal eager hydration needs no extra setup.
For incremental/deferred hydration, pass hydrationReady: Promise<void> to ReRune.provide(...) and resolve it after all server-rendered translation consumers hydrate. First root bootstrap alone does not establish that. The promise is browser-only and ignored during server rendering. An unresolved promise holds asynchronous cache reads and checks; rejection reports an error without starting them. Keep explicit language/variant changes after hydration as well.
Translate SEO-visible values after initialization in your server rendering path. A title or description computed once before translations load is not recalculated automatically. Root translation loaders must finish for rendering to settle. If you customize server bootstrap, pass Angular's supplied bootstrap context to bootstrapApplication(...) as required by your Angular version.
Server caches default to request-local memory. If you supply a custom store, own its request/project isolation and never share mutable user-specific state across unrelated requests. The built-in browser cache is scoped by publish ID and cache schema; custom stores must implement their own isolation and optional variant persistence methods.
Previewing Draft Translations
Pass staging: true to ReRune.provide(...) from either engine entry point to
preview enabled draft translations while project staging is enabled in the
dashboard. Every synchronization then fetches the complete draft snapshot of
each manifest locale and replaces the stored document, so deleted or disabled
keys disappear and never-published keys appear. Manifest versions do not track
draft edits, so staging mode checks every locale on every sync, including after
a manifest 304. Staging data uses a separate cache scope, and returning to
production through await ReRuneService.setStaging(false) restores the
production cache before synchronizing published content. Use
ReRuneService.setStaging(true) to switch back and
ReRuneService.isStaging() to report the current mode. The staging provider
option selects only the initial mode.
Server and browser configuration must use the same mode. TransferState records the server mode, and the browser discards a mismatched snapshot before it can supply translations.
Known Limitations
ReRuneService exposes structured omission diagnostics, including Angular path
conflicts. state().warnings describes the current selection; lastResult
and manual results retain the last check's snapshot. hasWarnings does not
mean hasErrors, and console logging is not required.
const result = await this.rerune.checkForUpdates()
for (const warning of result.warnings) {
console.warn(warning.locale, warning.key, warning.reasons)
}The advanced client property exposes core catalog diagnostics only. Use the
service state/results when investigating Angular-specific projection omissions.
- Root catalog only. OTA does not target ngx-translate child catalogs, Transloco named scopes, or backend namespaces. Those remain application-owned. Do not publish scoped keys expecting scoped delivery.
- No Angular localize adapter.
@angular/localize, extracted Angular message IDs, XLIFF delivery, and structural placeholders such as{$START_TAG_SPAN}are unsupported. No compatibility layer is included. - Limited OTA message grammar. Published messages support plain text, declared named interpolation, and one
countcardinal plural with an optional exact-zero form. General ICUselect, ordinal or nested plurals, rich-template structures, and runtime argument-type validation are not provided. Existing bundled ICU still uses the native engine plugin. Custom compilers/parsers/transpilers do not extend ReRune's OTA grammar. - Reactive consumers only. Native reactive pipes/signals can update. Strings saved from
TranslateService.instant(...),TranslocoService.translate(...), or one-shot subscriptions do not update themselves. Re-read them or use the engine's reactive API. Static HTML, generated metadata, and already-sent SSR responses need another render/build to change. - Dotted-key conflicts. OTA keys use dot paths. Empty path segments, reserved segments (
__proto__,constructor,prototype), and conflicting keys such as bothaccountandaccount.titleare omitted from the Angular overlay. There is no configurable key separator or literal-dot mode. These omissions appear instate().warningsand update-resultwarnings. - Check-based delivery. Startup, relevant language changes, manual refresh, and configured timers trigger asynchronous checks. These requests do not block browser bootstrap, but they are not push delivery or an OS background task. A closed page cannot keep checking. All manifest languages are synchronized;
supportedLocalesis not a remote-download filter. - Invalid updates retain fallback. Structural payload errors or rejected custom locale writes preserve that locale's previous copy. Placeholder-conflicting keys are omitted instead of invalidating the locale. See startup, offline, and partial updates for the supported update policy.
- Variants share project resource access. Anyone able to read delivered project resources can inspect their included variants, including those in cache or hydration data. Selecting a variant changes wording, not read permissions.
Advanced APIs
Existing Root Registration
If a shared module must keep its existing native root provider, register
ReRune.provide({ otaPublishId }) once alongside it, without a second argument.
ReRune uses that configured engine rather than registering another one. This is
an alternative for existing integrations, not an extra step in the Quick Start.
Most apps only need ReRune.provide(...). ReRuneService.client exposes the lower-level catalog client for custom integrations, and ReRuneAngularOptions describes provider options. Import ReRuneService and ReRuneAngularOptions from the selected engine entry point.
For custom persistence, createReRuneBrowserCacheStore(...) and the ReRuneCacheStore contract are available from @rerune/core. A rejected custom locale write keeps that locale's previous committed translations active.
Runnable Examples
Choose the public example for your existing translation engine:
Both install published npm packages, use native pipes and a MessageFormat plugin for bundled ICU, and match the React web example's look and feel. Each guide covers startup, ports, and a manual checklist. Use the public demo project or your own publish ID. No access to the SDK source repository is required.
Compatibility Evidence
The 1.5.0 candidate passed packed runtime/zoneless SSR and
consumer type checks on Angular 18 through 22, including single-engine installs
on 22. Declaration checking uses Angular's moduleResolution: "Bundler" with
skipLibCheck: false. NodeNext consumer checks use skipLibCheck: true because
Transloco 8.4.0's @jsverse/utils declarations contain extensionless ESM
imports. The same strict NodeNext errors reproduce when importing only native
Transloco, without ReRune. Native provider types are preserved rather than
copied into a separate SDK schema.
The 1.5.0 candidate checks tested packed imports, runtime behavior with mocked OTA, zoneless SSR, native pipes, and TypeScript resolution on Angular 18.2.14, 19.2.25, 20.3.30, 21.2.22, and 22.1.5. Browser-startup regression tests used Angular 18 with jsdom; production example builds also used Angular 18. Public example browser checks were not rerun for this candidate. Permitting future Angular versions to install is not a compatibility guarantee.
Safari/Firefox, full production SSR-to-browser DOM hydration, and every third-party loader/compiler/parser/interceptor or NgModule arrangement have not been exercised.
License
The ReRune SDK is available under the MIT License.
You may use, copy, modify,
and redistribute it, including commercially, subject to the license notice
requirement. The license covers the published SDK packages; separately
operated ReRune backend and hosted services are governed by their own terms.
The complete license text and copyright notice are included in each package's
LICENSE file.
