Skip to content

Commit

Permalink
Refactoring + documentation, renammed setDefault to setDefaultLang
Browse files Browse the repository at this point in the history
  • Loading branch information
ocombe committed Oct 24, 2015
1 parent 0841111 commit 24f60b4
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 91 deletions.
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ System.config({
});
```

Finally, you can use ng2-translate in your Angular 2 project (be sure that you've loaded the angular2/http bundle as well).
Finally, you can use ng2-translate in your Angular 2 project (make sure that you've loaded the angular2/http bundle as well).
It is recommended to instantiate `TranslateService` in the bootstrap of your application and to never add it to the "providers" property of your components, this way you will keep it as a singleton.
If you add it to the "providers" property of a component it will instantiate a new instance of the service that won't be initialized.

Expand Down Expand Up @@ -48,7 +48,7 @@ export class AppComponent {
userLang = /(fr|en)/gi.test(userLang) ? userLang : 'en';

// optional, default is "en"
translate.setDefault('en');
translate.setDefaultLang('en');

// the lang to use, if the lang isn't available, it will use the current loader to get them
translate.use(userLang);
Expand Down Expand Up @@ -81,3 +81,45 @@ translate.setTranslation('en', {
"HELLO_WORLD": "hello {{value}}"
});
```

## API
### TranslateService
#### Properties:
- `currentLang`: The lang currently used
- `currentLoader`: An instance of the loader currently used (static loader by default)
- `onLangChange`: An EventEmitter to listen to lang changes events

example:
```js
onLangChange.observer({
next: (params: {lang: string, translations: any}) => {
// do something
}
});
```

#### Methods:
- `useStaticFilesLoader()`: Use a static files loader
- `setDefaultLang(lang: string)`: Sets the default language to use ('en' by default)
- `use(lang: string): Observable<any>`: Changes the lang currently used
- `getTranslation(lang: string): Observable<any>`: Gets an object of translations for a given language with the current loader
- `setTranslation(lang: string, translations: Object)`: Manually sets an object of translations for a given language
- `getLangs()`: Returns an array of currently available langs
- `get(key: string, interpolateParams?: Object): Observable<string>`: Gets the translated value of a key
- `set(key: string, value: string, lang?: string)`:

### TranslatePipe
You can call the TranslatePipe with some optional parameters that will be transpolated into the translation for the given key.

Example:
```html
<p>Say {{ 'HELLO_WORLD' | translate:'{value: "world"}' }}</p>
```

With the given translation: `"HELLO_WORLD": "hello {{value}}"`.

### Parser
#### Methods:
- `interpolate(expr: string, params?: any): string`: Interpolates a string to replace parameters.

`This is a {{ key }}` ==> `This is a value` with `params = { key: "value" }`
3 changes: 2 additions & 1 deletion ng2-translate.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './src/translate.pipe';
export * from './src/translate.service';
export * from './src/translate.service';
export * from './src/translate.parser';
56 changes: 56 additions & 0 deletions src/translate.parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export class Parser {
templateMatcher: RegExp = /{{([^{}]*)}}/g;

/**
* Flattens an object
* { key1: { keyA: 'valueI' }} ==> { 'key1.keyA': 'valueI' }
* @param target
* @returns {any}
*/
private flattenObject(target: Object): Object {
var delimiter = '.';
var maxDepth: number;
var currentDepth = 1;
var output: any = {};

function step(object: any, prev?: string) {
Object.keys(object).forEach(function (key) {
var value = object[key];
var newKey = prev ? prev + delimiter + key : key;

maxDepth = currentDepth + 1;

if(!Array.isArray(value) && typeof value === 'object' && Object.keys(value).length && currentDepth < maxDepth) {
++currentDepth;
return step(value, newKey)
}

output[newKey] = value
})
}

step(target);

return output;
}

/**
* Interpolates a string to replace parameters
* "This is a {{ key }}" ==> "This is a value", with params = { key: "value" }
* @param expr
* @param params
* @returns {string}
*/
interpolate(expr: string, params?: any): string {
if(!params) {
return expr;
} else {
params = this.flattenObject(params);
}

return expr.replace(this.templateMatcher, function (substring: string, b: string): string {
var r = params[b];
return typeof r !== 'undefined' ? r : substring;
});
}
}
2 changes: 1 addition & 1 deletion src/translate.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class TranslatePipe implements PipeTransform {

// subscribe to onLangChange event, in case the language changes
this.translate.onLangChange.observer({
next: () => {
next: (params: {lang: string, translations: any}) => {
this.updateValue(query, interpolateParams);
}
});
Expand Down
170 changes: 83 additions & 87 deletions src/translate.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import {Injectable, EventEmitter} from 'angular2/angular2';
import {Http, Response, Headers, Request} from 'angular2/http';
// doc: https://github.com/ReactiveX/RxJS/blob/master/doc/operator-creation.md
import {Observable} from '@reactivex/rxjs/dist/cjs/Rx';

interface SFLoaderParams {
prefix: string;
suffix: string;
}
import {Parser} from "./translate.parser";

interface TranslateLoader {
getTranslation(lang: string): Observable<any>;
Expand All @@ -15,117 +11,91 @@ interface TranslateLoader {
@Injectable()
class TranslateStaticLoader implements TranslateLoader {
private http: Http;
private sfLoaderParams: SFLoaderParams = {prefix: 'i18n/', suffix: '.json'};
private sfLoaderParams = {prefix: 'i18n/', suffix: '.json'};

constructor(http: Http) {
this.http = http;
}

public useStaticFilesLoader(prefix: string, suffix: string) {
/**
* Defines the prefix & suffix used for getTranslation
* @param prefix
* @param suffix
*/
public configure(prefix: string, suffix: string) {
this.sfLoaderParams.prefix = prefix;
this.sfLoaderParams.suffix = suffix;
}

/**
* Gets the translations from the server
* @param lang
* @returns {any}
*/
public getTranslation(lang: string): Observable<any> {
return this.http.get(`${this.sfLoaderParams.prefix}/${lang}${this.sfLoaderParams.suffix}`)
.map((res: Response) => res.json());
}
}

@Injectable()
export class TranslateParser {
templateMatcher: RegExp = /{{([^{}]*)}}/g;
export class TranslateService {
private pending: any;
private translations: any = {};
private defaultLang: string = 'en';
private parser: Parser = new Parser();

/**
* Flattens an object
* { key1: { keyA: 'valueI' }} ==> { 'key1.keyA': 'valueI' }
* @param target
* @returns {any}
* The lang currently used
*/
private flattenObject(target: Object): Object {
var delimiter = '.';
var maxDepth: number;
var currentDepth = 1;
var output: any = {};

function step(object: any, prev?: string) {
Object.keys(object).forEach(function (key) {
var value = object[key];
var isarray = Array.isArray(value);
var type = Object.prototype.toString.call(value);
var isobject = (
type === "[object Object]" ||
type === "[object Array]"
);

var newKey = prev
? prev + delimiter + key
: key;

maxDepth = currentDepth + 1;

if(!isarray && isobject && Object.keys(value).length && currentDepth < maxDepth) {
++currentDepth;
return step(value, newKey)
}

output[newKey] = value
})
}

step(target);
public currentLang: string;

return output;
}
/**
* An instance of the loader currently used
*/
public currentLoader: any;

/**
* Interpolates a string to replace parameters
* "This is a {{ param }}" ==> "This is a value"
* @param expr
* @param params
* @returns {string}
* An EventEmitter to listen to lang changes events
* onLangChange.observer({
* next: (params: {lang: string, translations: any}) => {
* // do something
* }
* });
* @type {ng.EventEmitter}
*/
interpolate(expr: string, params?: any): string {
if(!params) {
return expr;
} else {
params = this.flattenObject(params);
}
public onLangChange: EventEmitter = new EventEmitter();

return expr.replace(this.templateMatcher, function (substring: string, b: string): string {
var r = params[b];
return typeof r !== 'undefined' ? r : substring;
});
constructor(private http: Http) {
this.useStaticFilesLoader();
}
}

@Injectable()
export class TranslateService {
private pending: any;
private staticLoader: any;
private translations: any = {};
private defaultLang: string = 'en';
private parser: TranslateParser;
public currentLang: string;
public currentLoader: any;
public onLangChange: EventEmitter = new EventEmitter();

constructor(http: Http) {
this.staticLoader = new TranslateStaticLoader(http);
this.currentLoader = this.staticLoader;
this.parser = new TranslateParser();
/**
* Use a static files loader
*/
public useStaticFilesLoader() {
this.currentLoader = new TranslateStaticLoader(this.http);
}

setDefault(lang: string) {
/**
* Sets the default language to use ('en' by default)
* @param lang
*/
public setDefaultLang(lang: string) {
this.defaultLang = lang;
}

private changeLang(lang: string) {
this.currentLang = lang;
this.onLangChange.next(this.translations[lang]);
this.onLangChange.next({lang: lang, translations: this.translations[lang]});
}

use(lang: string): Observable<any> {
/**
* Changes the lang currently used
* @param lang
* @returns {Observable<*>}
*/
public use(lang: string): Observable<any> {
// check if this language is available
if(typeof this.translations[lang] === "undefined") {
// not available, ask for it
Expand All @@ -143,7 +113,12 @@ export class TranslateService {
}
}

getTranslation(lang: string): Observable<any> {
/**
* Gets an object of translations for a given language with the current loader
* @param lang
* @returns {Observable<*>}
*/
public getTranslation(lang: string): Observable<any> {
var observable = this.currentLoader.getTranslation(lang);

observable.subscribe((res: Object) => {
Expand All @@ -154,15 +129,30 @@ export class TranslateService {
return observable;
}

setTranslation(lang: string, translation: Object) {
this.translations[lang] = translation;
/**
* Manually sets an object of translations for a given language
* @param lang
* @param translations
*/
public setTranslation(lang: string, translations: Object) {
this.translations[lang] = translations;
}

getLangs() {
/**
* Returns an array of currently available langs
* @returns {any}
*/
public getLangs() {
return Object.keys(this.translations);
}

get(key: string, interpolateParams?: Object): Observable<string> {
/**
* Gets the translated value of a key
* @param key
* @param interpolateParams
* @returns {any}
*/
public get(key: string, interpolateParams?: Object): Observable<string> {
// check if we are loading a new translation to use
if(this.pending) {
return this.pending.map((res: any) => this.parser.interpolate(res[key], interpolateParams) || key);
Expand All @@ -171,7 +161,13 @@ export class TranslateService {
}
}

set(key: string, value: string, lang: string = this.currentLang) {
/**
* Sets the translated value of a key
* @param key
* @param value
* @param lang
*/
public set(key: string, value: string, lang: string = this.currentLang) {
this.translations[lang][key] = value;
}
}

0 comments on commit 24f60b4

Please sign in to comment.