I'm developing an Angular app and I'm looking for something similar to Android Resource available in Android development.
Here is the way to get a string in Android:
String mystring = getResources().getString(R.string.mystring);
I would like to have the same in Angular.
For example if I have few HTML templates in which there are the same message about the wrong email provided...
<div class="alert alert-danger">
<strong>Error!</strong>Invalid e-mail
</div>
I would like to have the following:
<div class="alert alert-danger">
<strong>Error!</strong>{{myStrings.INVALID_EMAIL}}
</div>
...or something like this...
<div class="alert alert-danger">
<strong>Error!</strong>{{'INVALID_EMAIL' | stringGenerator}}
</div>
Do you know a way or addon I can install to reach that?
Having configuration, translations and resources separated from application's logic is very useful. Configuration would also be very helpful in other context like, for example, getting api_url
useful for any rest call.
You can set up such thing using @angular/cli. Having the following application structure:
|- app
|- assets
|- i18n
- en.json
- it.json
|- json-config
- development.json
- env.json
- production.json
|- resources
- en.json
- it.json
|- environment
- environment.prod.ts
- environment.ts
|- config
- app.config.ts
Where:
E.G. en.json
:
{
"TEST": {
"WELCOME" : "Welcome"
}
E.G it.json
:
{
"TEST": {
"WELCOME" : "Benvenuto"
}
env.json
which is a json that says which is the current development mode:E.G. env.json
:
{
"env" : "development"
}
E.G. development.json
:
{
"API_URL" : "someurl",
"MYTOKEN" : "sometoken",
"DEBUGGING" : true
}
assets/resources: contains jsons files of resources per each language we want to cover. For instance, it may contain jsons initialization's for application models. It's useful if, for example, you want to fill an array of a model to be passed to an *ngFor
personalized based on enviroment and/or language. Such initialization should be done inside each component which want to access a precise resource via AppConfig.getResourceByKey
that will be shown later.
app.config.ts: Configuration Service that loads resources based on development mode. I will show a snippet below.
Basic Configuration:
In order to load basic configuration files as the application starts we need to do a few things.
app.module.ts:
import { NgModule, APP_INITIALIZER } from '@angular/core';
/** App Services **/
import { AppConfig } from '../config/app.config';
import { TranslationConfigModule } from './shared/modules/translation.config.module';
// Calling load to get configuration + translation
export function initResources(config: AppConfig, translate: TranslationConfigModule) {
return () => config.load(translate);
}
// Initializing Resources and Translation as soon as possible
@NgModule({
. . .
imports: [
. . .
TranslationConfigModule
],
providers: [
AppConfig, {
provide: APP_INITIALIZER,
useFactory: initResources,
deps: [AppConfig, TranslationConfigModule],
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
app.config.ts:
As said above, this service loads configuration files based on development mode and, in this case, browser language. Loading resources based on language can be very useful if you want to customize your application. For example, your italian distribution would have different routes, different behavior or simple different texts.
Every Resources, Configuration and Enviroment entry is available trough AppConfig
service's methods such as getEnvByKey
, getEntryByKey
and getResourceByKey
.
import { Inject, Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { get } from 'lodash';
import 'rxjs/add/operator/catch';
import { TranslationConfigModule } from '../app/shared/modules/translation.config.module';
@Injectable()
export class AppConfig {
private _configurations: any = new Object();
private _config_path = './assets/json-config/';
private _resources_path = './assets/resources/';
constructor( private http: Http) { }
// Get an Environment Entry by Key
public getEnvByKey(key: any): any {
return this._configurations.env[key];
}
// Get a Configuration Entryby Key
public getEntryByKey(key: any): any {
return this._configurations.config[key];
}
// Get a Resource Entry by Key
public getResourceByKey(key: any): any {
return get(this._configurations.resource, key);
}
// Should be self-explanatory
public load(translate: TranslationConfigModule){
return new Promise((resolve, reject) => {
// Given env.json
this.loadFile(this._config_path + 'env.json').then((envData: any) => {
this._configurations.env = envData;
// Load production or development configuration file based on before
this.loadFile(this._config_path + envData.env + '.json').then((conf: any) => {
this._configurations.config = conf;
// Load resources files based on browser language
this.loadFile(this._resources_path + translate.getBrowserLang() +'.json').then((resource: any) => {
this._configurations.resource = resource;
return resolve(true);
});
});
});
});
}
private loadFile(path: string){
return new Promise((resolve, reject) => {
this.http.get(path)
.map(res => res.json())
.catch((error: any) => {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
})
.subscribe((res_data) => {
return resolve(res_data);
})
});
}
}
translation.config.module.ts
This module sets up translation built using ngx-translate. Sets up translation depending on the browser language.
import { HttpModule, Http } from '@angular/http';
import { NgModule, ModuleWithProviders } from '@angular/core';
import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { isNull, isUndefined } from 'lodash';
export function HttpLoaderFactory(http: Http) {
return new TranslateHttpLoader(http, '../../../assets/i18n/', '.json');
}
const translationOptions = {
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [Http]
}
};
@NgModule({
imports: [TranslateModule.forRoot(translationOptions)],
exports: [TranslateModule],
providers: [TranslateService]
})
export class TranslationConfigModule {
private browserLang;
/**
* @param translate {TranslateService}
*/
constructor(private translate: TranslateService) {
// Setting up Translations
translate.addLangs(['en', 'it']);
translate.setDefaultLang('en');
this.browserLang = translate.getBrowserLang();
translate.use(this.browserLang.match(/en|it/) ? this.browserLang : 'en');
}
public getBrowserLang(){
if(isUndefined(this.browserLang) || isNull(this.browserLang)){
this.browserLang = 'en';
}
return this.browserLang;
}
}
Ok, and now? How can I use such configuration?
Any Module/Component imported into app.module.ts
or any of them that is imported into another custom module that is importing translation.config.module
can now automatically translate any interpolated entry based on browser language. For instance using the following snipped will generate Welcome or Benvenuto based on explained behavior:
{{ 'TEST.WELCOME' | translate }}
What If I want to get a resource to initialize a certain array that will be passed to an *ngFor
?
In any component, just do that inside the constructor:
. . .
// Just some model
public navigationLinks: NavigationLinkModel[];
constructor(private _config: AppConfig) {
// PAGES.HOMEPAGE.SIDENAV.NAVIGATION contains such model data
this.navigationLinks =
this._config.getResourceByKey('PAGES.HOMEPAGE.SIDENAV.NAVIGATION');
}
Of course you can also combinate resources and configuration.
AndreaM16's answer is definitely thorough, but you sould also consider using an existing package for this instead of writing your own custom code that you need to maintain. To that end, I would recommend checking out ngx-translate (which is used somewhat under the hood in that answer), transloco and Angular's built-in i18n capabilities.
Most of those approaches are based on the magic strings, where your HTML templates and typescript code need to contain strings that (hopefully) match the keys in a JSON or XML file. This presents a serious code maintenance issue which won't be revealed at compile time, only at runtime. I would also advocate for checking out some guides on creating a type-safe translation system:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With