Facade
Configuration
Language
Language Discovery
Helpers
Translation Provider
Storage
Managers
Loader
Pipes
Directives
namespace
translations table
key description
app.
main.
xxx.yyy.zzz
- translations/
- app/
- main/
en.json
pl.json
- admin/
en.json
pl.json
<p translate="app.main.xxx.yyy.zzz"> ... </p>
<p translate="app.main.xxx.yyy.zzz || app.main.globals.xxx.yyy.zzz"> ... </p>
fallback notation
export function appInitializerFactory( facadeService: FacadeService) {
// eager translation loading
const defaultTranslationTable = require('../../translations/app/main/en.json');
// bootstrap engine
return () => facadeService.bootstrap( defaultTranslationTable );
};
...
@NgModule({
...
providers: [FacadeService,
{ provide: APP_INITIALIZER, useFactory: appInitializerFactory, deps [...]. multi: true}]
}
})
1
2
loadTranslationsUsingWebpack(namespace, table, lang) {
return import(/* webpackMode: "lazy" */
`../../../../translations/${namespace}/${table}/${lang}.json`);
}
loadTranslationsUsingHttpClient(namespace, table, lang) {
return this.httpClient.get(`{$apiUrl}/translations/${namespace}/${table}/${lang}`);
}
@Injectable()
export class TranslationService {
// ...
translate(
translationKey: string, contextValues: object = {},
languageId: string = this.languageService.getCurrentLanguage(),
fallbackTranslationKey?: string) {
// key validation
if (!this.isTranslationKeyValid(translationKey)) {
return translationKey;
}
// caching
const cachedValue = this.getFromCache(translationKey, languageId, contextValues);
if (cachedValue) return cachedValue;
// get namespace, table and key from string
const destructurizedTranslationKey = this.destructurizeTranslationKey(translationKey);
// identify translationTable - current language or default language
const translationTableToUse = this.identifyTranslationTableToUse(destructurizedTranslationKey);
...
1
2
3
4
...
// do the magic!
if (translationTableToUse.hasOwnProperty(translationKey)) {
// handle simple interpolations
result = Mustache.render(translationTableToUse[translationKey], contextValues);
} else {
// handle fallback
if (typeof fallbackTranslationKey === 'string') {
return this.translate(fallbackTranslationKey, contextValues, languageId);
}
// log missing key
this.logMissingKeys() && console.warn(`Missing translation for '${translationKey}'`);
result = translationKey;
}
// store in cache in proper context
this.storeInCache(translationKey, languageId, contextValues, result);
return result;
}
}
5
6
8
7
linkedin.com/in/lukasz-max-kokoszka
twitter.com/rafal_brzoska
linkedin.com/in/rafalbrzoska