Runtime scoped translations
@flyze/lib-core-angular 1.0.0-alpha.34· latestTranslations for a flyze platform scope are not shipped in the bundle — they are configured per
installation and fetched at runtime. FyzTranslateTranslocoConfigService loads such a configuration
and pushes it into transloco, so a scoped FyzTranslationConfig resolves afterwards exactly like
a compiled-in translation file.
Two pieces make that work: a data provider the app supplies, and a pivot from the flyze shape into transloco’s shape.
The shape
Section titled “The shape”interface FyzScopedTranslocoConfig { scope: string; translations: { [key: string]: FyzMultiLanguage[] }; // key -> per-language texts}Keyed by translation key, with all languages of that key together
(fyz-scoped-transloco-config.model.ts).
Transloco wants the opposite: one flat { key: text } object per scope/lang. applyConfigLocal
does the transpose.
Where the data comes from
Section titled “Where the data comes from”The app implements AbstractFyzTranslateConfigDataService — two methods, loadConfig(scope) and
saveConfig(config), both observable
(fyz-translate-config-data.service.abstract.ts:7)
— and provides it under the FYZ_TRANSLATE_CONFIG_DATA_SERVICE token.
FyzTranslateProviderService resolves it in its constructor with a try/catch around
Injector.get, falling back to a private no-op implementation that returns an empty config
(fyz-translate-provider.service.ts:23).
That is why nothing has to be provided: an app that never configures runtime translations still gets
a working FyzTranslateTranslocoConfigService whose loadConfig succeeds with nothing in it,
instead of an injection error at bootstrap. The missing provider is reported with console.info,
not a warning — it is an expected state, not a misconfiguration.
The lookup happens once, in the constructor of a providedIn: 'root' service. A provider
registered later — for example inside a lazy-loaded route’s injector — is never seen.
Applying a configuration
Section titled “Applying a configuration”applyConfigLocal
(fyz-translate-transloco-config.service.ts:54):
- If the scope is already known,
removeConfigLocalruns first (see the limitation below). - The
key -> languagesmap is pivoted intolanguage -> { key: text }. - For each language,
TranslocoService.setTranslation(translations,scope/lang, { merge: true }). - The config is remembered in an internal
Mapkeyed by scope.
merge: true is what makes the result additive: an existing scope/lang namespace keeps the keys
the new config does not mention. A key present in some languages but not others simply does not
exist for the missing ones — nothing is filled in, and the scoped-key fallback in
translation-config then renders the bare key.
loadConfig(scope, apply = true) and saveConfig(config, apply = true) both delegate to the data
service and apply the result via tap on the way through
(fyz-translate-transloco-config.service.ts:29).
Pass apply: false to fetch or persist without touching the active translations. Note that both are
cold: nothing happens until the returned observable is subscribed.
Limitation — removeConfigLocal removes nothing
Section titled “Limitation — removeConfigLocal removes nothing”fyz-translate-transloco-config.service.ts:82:
const all = structuredClone(this.translocoService.getTranslation());all.forEach((translations, key) => { Object.keys(translations).forEach((key) => { // inner `key` shadows the language if (key.startsWith(scope)) delete translations[key]; }); this.translocoService.setTranslation(translations, `${key}`, { merge: false });});getTranslation() returns a Map<lang, Translation>, so the outer key is a language namespace
like playground2/de while the inner key is a translation key such as GREETING. Translation
keys inside a scoped namespace do not carry the scope prefix, so key.startsWith(scope) is false
for all of them and nothing is deleted. What does happen is a setTranslation(..., merge: false)
for every language in the store, rewriting each namespace with its own unchanged content.
Practical effect: re-applying a config for a scope that already exists never drops keys the new
config omits — despite step 1 above, the result is still purely additive. To genuinely clear a
scope, an app has to call TranslocoService.setTranslation({}, 'scope/lang', { merge: false })
itself.
Related
Section titled “Related”- translation-config — how a
scopedconfig resolves once applied - guides/providing-runtime-translations — how to wire the data provider