├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── lib ├── index.ts ├── pipe.spec.ts ├── pipe.ts ├── service.spec.ts └── service.ts ├── package.json ├── tsconfig.json └── typings.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules/ 3 | dist/ 4 | typings/ 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | lib/ 3 | .DS_Store 4 | typings/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - 5 10 | after_success: 11 | - npm run semantic-release 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Viktor Somodi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular2 translate 2 | 3 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 4 | 5 | This is a simple translate service solution for angular2. You should provide a dictionary and the service will translate it with sprintf interpolation. 6 | 7 | Install 8 | --------- 9 | 10 | ```bash 11 | npm install --save angular2-translate 12 | ``` 13 | Setup 14 | --------- 15 | 16 | ```javascript 17 | 18 | import { TranslateModule } from 'angular2-translate'; 19 | 20 | const translations = { 21 | useValue: { 22 | main: { 23 | text: 'I am: %s you are: %s' 24 | }, 25 | other: { 26 | withoutInterpolation: 'Star Wars' 27 | } 28 | } 29 | }; 30 | 31 | @NgModule({ 32 | imports: [ 33 | BrowserModule, 34 | TranslateModule.create(translations) 35 | ], 36 | declarations: [AppComponent], 37 | bootstrap: [AppComponent] 38 | }) 39 | export class AppModule { } 40 | 41 | ``` 42 | 43 | Usage in template 44 | --------- 45 | 46 | ```javascript 47 | 48 | import { TranslatePipe } from 'angular2-translate'; 49 | 50 | @Component({ 51 | selector: '', 52 | template: ` 53 |

{{ 'main.text' | translate:'Luke':controllerVariable }}

54 |

{{ 'other.withoutInterpolation' | translate }}

55 | ` 56 | }) 57 | export class App { 58 | 59 | constructor() { 60 | this.controllerVariable = 'Darth Vader'; 61 | } 62 | ``` 63 | 64 | Usage in Controller 65 | --------- 66 | 67 | ```javascript 68 | 69 | import { TranslateService } from 'angular2-translate'; 70 | 71 | @Component({ 72 | selector: '', 73 | template: `Some content` 74 | }) 75 | export class App { 76 | 77 | constructor(translateService: TranslateService) { 78 | this.translated = translateService.translate('main.text', ['first', 'second']); 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './service'; 2 | export * from './pipe'; 3 | import { NgModule, ModuleWithProviders } from '@angular/core'; 4 | import { TranslatePipe } from './pipe'; 5 | import { TranslateService } from './service'; 6 | 7 | export type Translations = { 8 | [name: string]: string 9 | }; 10 | 11 | @NgModule({ 12 | declarations: [TranslatePipe], 13 | exports: [TranslatePipe] 14 | }) 15 | export class TranslateModule { 16 | static create(translations: Translations): ModuleWithProviders { 17 | return { 18 | ngModule: TranslateModule, 19 | providers: [ 20 | { provide: TranslateService, useValue: new TranslateService(translations) } 21 | ] 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { TranslatePipe, TranslateService } from '../lib'; 3 | 4 | describe('pipe', () => { 5 | 6 | describe('#transform', () => { 7 | 8 | it('#transform should delegate the translation to the service', () => { 9 | const service = new TranslateService({ nested: { key: 'value%s' } }); 10 | const pipe = new TranslatePipe(service); 11 | expect(pipe.transform('nested.key', [5])).to.eql('value5'); 12 | }); 13 | 14 | 15 | it('#transform should delegate the translation to the service with empty arguments if there is no', () => { 16 | const service = new TranslateService({ nested: { key: 'value' } }); 17 | const pipe = new TranslatePipe(service); 18 | expect(pipe.transform('nested.key')).to.eql('value'); 19 | }); 20 | 21 | }); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /lib/pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe } from '@angular/core'; 2 | import { TranslateService } from './service'; 3 | 4 | @Pipe({ name: 'translate' }) 5 | export class TranslatePipe { 6 | 7 | constructor(private _translateService: TranslateService) {} 8 | 9 | transform(value: string, options: any[] = []) { 10 | return this._translateService.translate(value, options); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /lib/service.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { TranslateService } from '../lib'; 3 | 4 | describe('service', () => { 5 | 6 | const createService = function(translations: any) { 7 | return new TranslateService(translations); 8 | }; 9 | 10 | describe('#hasTranslation', () => { 11 | 12 | it('should give back false if there is no translation for it', () => { 13 | const service = createService({ key: 'value' }); 14 | expect(service.hasTranslation('notExists')).to.eql(false); 15 | }); 16 | 17 | 18 | it('should give back true if there is translation for it', () => { 19 | const service = createService({ what: 'val' }); 20 | expect(service.hasTranslation('what')).to.eql(true); 21 | }); 22 | 23 | }); 24 | 25 | describe('#translate', () => { 26 | 27 | it('should translate the given simple text', () => { 28 | const service = createService({ key: 'value' }); 29 | expect(service.translate('key')).to.eql('value'); 30 | }); 31 | 32 | 33 | it('should give back the given text if there is no translation for it', () => { 34 | const service = createService({ key: 'value' }); 35 | expect(service.translate('wrongKey')).to.eql('wrongKey'); 36 | }); 37 | 38 | 39 | it('should give back the given text with interpolation', () => { 40 | const service = createService({ key: '%s value %s' }); 41 | expect(service.translate('key', ['a', 'b'])).to.eql('a value b'); 42 | }); 43 | 44 | 45 | it('should handle nested dictionary', () => { 46 | const service = createService({ root: { nested: 'value' } }); 47 | expect(service.translate('root.nested')).to.eql('value'); 48 | }); 49 | 50 | 51 | it('should give back the given text with interpolation if there is no translation for it', () => { 52 | const service = createService({ key: 'value' }); 53 | expect(service.translate('%s wrongKey %s', ['a', 'b'])).to.eql('a wrongKey b'); 54 | }); 55 | 56 | 57 | it('should remove the interpolation places if they are not match for the expectations', () => { 58 | const service = createService({ key: '%s value %d' }); 59 | expect(service.translate('key', ['string', 'expects for decimal'])).to.eql(' value '); 60 | }); 61 | 62 | }); 63 | 64 | 65 | describe('#setTranslations', () => { 66 | 67 | it('should override the initial translations', () => { 68 | const service = createService({ key: 'value' }); 69 | service.setTranslations({ otherKey: 'value' }); 70 | 71 | expect(service.translate('key')).to.eql('key'); 72 | expect(service.translate('otherKey')).to.eql('value'); 73 | }); 74 | 75 | }); 76 | 77 | }); 78 | 79 | 80 | -------------------------------------------------------------------------------- /lib/service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | const { flatten } = require('flat'); 3 | const { vsprintf } = require('sprintf-js'); 4 | 5 | @Injectable() 6 | export class TranslateService { 7 | 8 | constructor(@Inject('translations') private _translations: any) { 9 | this._translations = flatten(_translations); 10 | } 11 | 12 | 13 | setTranslations(value: any) { 14 | this._translations = flatten(value); 15 | } 16 | 17 | 18 | hasTranslation(value: string): boolean { 19 | return !!this._translations[value]; 20 | } 21 | 22 | 23 | translate(value: string, parameters: any[] = []): string { 24 | let translated = (this._translations[value]) ? this._translations[value] : value; 25 | try { 26 | return vsprintf(translated, parameters); 27 | } catch (e) { 28 | return translated.replace(/%s|%d/gi, ''); 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-translate", 3 | "description": "Simple translate for angular2", 4 | "keywords": [ 5 | "angular", 6 | "angular2", 7 | "translate", 8 | "i18n" 9 | ], 10 | "main": "dist/index.js", 11 | "typings": "./dist/index.d.ts", 12 | "scripts": { 13 | "test": "mocha --require core-js/shim --require reflect-metadata --require ts-node/register lib/**/*.spec.ts", 14 | "prepublish": "typings install && rimraf dist && tsc", 15 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/Valetudox/angular2-translate.git" 20 | }, 21 | "author": "Viktor Somodi ", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/Valetudox/angular2-translate/issues" 25 | }, 26 | "homepage": "https://github.com/Valetudox/angular2-translate#readme", 27 | "dependencies": { 28 | "sprintf-js": "^1.0.3", 29 | "flat": "^1.6.0" 30 | }, 31 | "peerDependencies": { 32 | "@angular/core": "^2.0.0" 33 | }, 34 | "devDependencies": { 35 | "@angular/core": "^2.0.0", 36 | "chai": "3.5.0", 37 | "core-js": "2.4.1", 38 | "mocha": "2.4.5", 39 | "reflect-metadata": "0.1.8", 40 | "rimraf": "2.5.2", 41 | "rxjs": "5.0.0-rc.3", 42 | "semantic-release": "6.2.1", 43 | "ts-node": "0.7.0", 44 | "typescript": "2.0.10", 45 | "typings": "2.0.0", 46 | "zone.js": "0.6.26" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "module": "commonjs", 5 | "target": "ES5", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "sourceMap": true, 9 | "declaration": true, 10 | "outDir": "dist/" 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | "./test/**/*.spec.ts" 15 | ], 16 | "filesGlob": [ 17 | "./lib/**/*.ts", 18 | "!./node_modules/**/*.ts", 19 | "typings/index.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDevDependencies": { 3 | "core-js": "registry:dt/core-js#0.0.0+20160914114559", 4 | "mocha": "registry:env/mocha#2.2.5+20160926180742", 5 | "node": "registry:env/node#6.0.0+20161105011511" 6 | }, 7 | "devDependencies": { 8 | "chai": "registry:npm/chai#3.5.0+20160723033700" 9 | } 10 | } 11 | --------------------------------------------------------------------------------