Skip to content

Providing runtime translations

@flyze/lib-core-angular 1.0.0-alpha.34· latest

How to feed a transloco scope from your own backend, so scoped translation configs resolve against translations that were not in the bundle.

Mechanics: features/runtime-scoped-translations.

import {
AbstractFyzTranslateConfigDataService,
FyzScopedTranslocoConfig
} from '@flyze/lib-core-angular';
@Injectable({ providedIn: 'root' })
export class MyTranslateConfigDataService implements AbstractFyzTranslateConfigDataService {
constructor(private http: HttpClient) {}
loadConfig(scope: string): Observable<FyzScopedTranslocoConfig> {
return this.http.get<FyzScopedTranslocoConfig>(`/api/translations/${scope}`);
}
saveConfig(config: FyzScopedTranslocoConfig): Observable<FyzScopedTranslocoConfig> {
return this.http.put<FyzScopedTranslocoConfig>(`/api/translations/${config.scope}`, config);
}
}

The payload is keyed by translation key, with every language of that key together:

{
"scope": "orders",
"translations": {
"GREETING": [
{ "language": "de", "text": "Hallo {{name}}" },
{ "language": "en", "text": "Hello {{name}}" }
]
}
}

If you never save, still implement saveConfig — the abstract class requires it. Returning throwError(() => new Error('read only')) is honest; the test app throws synchronously (custom-transloco-config-data.service.ts).

@NgModule({
providers: [
{
provide: FYZ_TRANSLATE_CONFIG_DATA_SERVICE,
useClass: MyTranslateConfigDataService
}
]
})
export class AppModule {}

Provide it in the root injector. The lookup happens once, in the constructor of a root-provided service, so a provider registered inside a lazy route’s injector is never found — and the failure is silent: you get the built-in no-op provider that returns an empty config, with a console.info note.

constructor(private configService: FyzTranslateTranslocoConfigService) {}
ngOnInit() {
this.configService
.loadConfig('orders')
.pipe(takeUntil(this.destroy$))
.subscribe();
}

loadConfig applies the result to transloco by default; the subscription is what triggers the request. Pass false as the second argument to fetch without applying.

Do this before anything renders the scope — a scoped config for a key that is not loaded yet renders the bare key and does not retry. If the scope is needed by the first view, resolve it during bootstrap (APP_INITIALIZER) or in a route resolver.

this.configService.applyConfigLocal({
scope: 'orders',
translations: { GREETING: [{ language: 'de', text: 'Hallo' }] }
});

Synchronous, no request. Use it for translations that arrived over a websocket or came from a local edit.

Applying a config is additive. Keys the new config omits keep their old values, even for a scope you already loaded — removeConfigLocal does not actually remove anything (see the limitation in features/runtime-scoped-translations).

To genuinely clear a scope, go through transloco directly:

constructor(private transloco: TranslocoService) {}
clearScope(scope: string) {
for (const lang of this.transloco.getAvailableLangs() as string[]) {
this.transloco.setTranslation({}, `${scope}/${lang}`, { merge: false });
}
}

The /transloco-test route of the test app runs the whole flow against a fake data service: it loads the playground2 scope, then applies a second config three seconds later so you can watch already rendered text change.