diff --git a/.gitignore b/.gitignore index 2678302..cfb6227 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ coverage/ # overrides for js !config/* !demo/cli/*js -!copy.js !karma.conf.js !webpack.config.js diff --git a/.npmignore b/.npmignore index e20cd16..2424658 100644 --- a/.npmignore +++ b/.npmignore @@ -18,7 +18,6 @@ coverage config karma.conf.js webpack.config.js -copy.js tsconfig.json tslint.json !*.metadata.json diff --git a/.travis.yml b/.travis.yml index d9761ee..0fd5418 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,11 +12,9 @@ before_install: - npm i -g npm@^3 before_script: - npm prune -after_success: - - npm run semantic-release branches: except: - "/^v\\d+\\.\\d+\\.\\d+$/" before_script: - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start \ No newline at end of file + - sh -e /etc/init.d/xvfb start diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 19b96b1..4d13069 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -17,37 +17,53 @@ In some cases your local `node_modules` might get in broken state in which case 3. The module initialization `forRoot` method has changed a bit. Instead of loader, it expects an object of parameters. - ```typescript + ```ts + import { Http } from '@angular/http'; + import { TranslateService } from '@ngx-translate/core'; + import { Location } from '@angular/common'; + import { LocalizeRouterModule, LocalizeParser, StaticParserLoader } from 'localize-router'; + LocalizeRouterModule.forRoot(routes, { provide: LocalizeParser, useFactory: (translate, location, http) => - new StaticParserLoader(translate, location, http, 'your/path/to/config.json'), + new StaticParserLoader(translate, location, http, 'your/path/to/config.json'), deps: [TranslateService, Location, Http] }) ``` Is now: - ```typescript + ```ts + import { Http } from '@angular/http'; + import { TranslateService } from '@ngx-translate/core'; + import { Location } from '@angular/common'; + import { LocalizeRouterModule, LocalizeParser, StaticParserLoader, LocalizeRouterSettings } from 'localize-router'; + LocalizeRouterModule.forRoot(routes, { parser: { - provide: LocalizeParser, - useFactory: (translate, location, settings, http) => - new StaticParserLoader(translate, location, settings, http, 'your/path/to/config.json'), - deps: [TranslateService, Location, LocalizeRouterSettings, Http] + provide: LocalizeParser, + useFactory: (translate, location, settings, http) => + new StaticParserLoader(translate, location, settings, http, 'your/path/to/config.json'), + deps: [TranslateService, Location, LocalizeRouterSettings, Http] } }) ``` You can now also provide additional settings regarding url prefixes and default language cache mechanisms: - ```typescript - LocalizeRouterModule.forRoot({ - useCachedLang: { provide: USE_CACHED_LANG, useValue: booleanValue }, - alwaysSetPrefix: { provide: ALWAYS_SET_PREFIX, useValue: booleanValue }, - cacheName: { provide: CACHE_NAME, useValue: stringValue }, - cacheMechanism: { provide: CACHE_MECHANISM, useValue: typeOfCacheMechanism }, - defaultLangFunction: { provide: DEFAULT_LANG_FUNCTION, useValue: typeOfDefaultLanguageFunction }, + ```ts + LocalizeRouterModule.forRoot(routes, { + parser: { + provide: LocalizeParser, + useFactory: (translate, location, settings, http) => + new StaticParserLoader(translate, location, settings, http, 'your/path/to/config.json'), + deps: [TranslateService, Location, LocalizeRouterSettings, Http] + }, + useCachedLang: { provide: USE_CACHED_LANG, useValue: booleanValue }, + alwaysSetPrefix: { provide: ALWAYS_SET_PREFIX, useValue: booleanValue }, + cacheName: { provide: CACHE_NAME, useValue: stringValue }, + cacheMechanism: { provide: CACHE_MECHANISM, useValue: typeOfCacheMechanism }, + defaultLangFunction: { provide: DEFAULT_LANG_FUNCTION, useValue: typeOfDefaultLanguageFunction }, }) ``` diff --git a/README.md b/README.md index b2a11d2..9db2e11 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,9 @@ export function localizeLoaderFactory(translate: TranslateService, location: Loc #### Properties - `parser`: Provider for loading of LocalizeParser. Default value is `StaticParserLoader`. - `useCachedLang`: boolean. Flag whether default language should be cached. Default value is `true`. -- `alwaysSetPrefix`: boolean. Flag whether language should always prefix the url if only one language is present. Default value is `true`. +- `alwaysSetPrefix`: boolean. Flag whether language should always prefix the url. Default value is `true`. + When value is `false`, prefix will not be used for for default language (this includes the situation when there is only one language). +- `prefixDefaultLanguage`: boolean. Flag whether default language should be prefixed or not. In order to maintain constant default language, specifying`defaultLangFunction` is recommended. Default value is `true`. - `cacheMechanism`: CacheMechanism.LocalStorage || CacheMechanism.Cookie. Default value is `CacheMechanism.LocalStorage`. - `cacheName`: string. Name of cookie/local store. Default value is `LOCALIZE_DEFAULT_LANGUAGE`. - `defaultLangFunction`: (languages: string[], cachedLang?: string, browserLang?: string) => string. Override method for custom logic for picking default language, when no language is provided via url. Default value is `undefined`. @@ -325,6 +327,7 @@ yoursite.com/en/users/John%20Doe/profile -> yoursite.com/de/benutzer/John%20Doe/ - `locales`: Array of used language codes - `currentLang`: Currently selected language - `routes`: Active translated routes +- `urlPrefix`: Language prefix for current language. Empty string if `alwaysSetPrefix=false` and `currentLang` is same as default language. #### Methods: - `translateRoutes(language: string): Observable`: Translates all the routes and sets language and current diff --git a/copy.js b/copy.js deleted file mode 100644 index 7cc196b..0000000 --- a/copy.js +++ /dev/null @@ -1,3 +0,0 @@ -var fs = require('fs'); - -fs.createReadStream('bundles/localize-router.umd.js').pipe(fs.createWriteStream('bundles/index.js')); \ No newline at end of file diff --git a/demo/cli/package.json b/demo/cli/package.json index cd67441..4091a3c 100644 --- a/demo/cli/package.json +++ b/demo/cli/package.json @@ -23,7 +23,8 @@ "@ngx-translate/core": "^7.0.0", "@ngx-translate/http-loader": "^0.1.0", "core-js": "^2.4.1", - "localize-router": "^0.7.1", + "localize-router": "1.0.0-alpha.1", + "localize-router-http-loader": "0.0.1", "rxjs": "^5.1.0", "zone.js": "^0.8.4" }, diff --git a/demo/cli/src/app/app-routing.module.ts b/demo/cli/src/app/app-routing.module.ts index 2d8ba57..069f408 100644 --- a/demo/cli/src/app/app-routing.module.ts +++ b/demo/cli/src/app/app-routing.module.ts @@ -1,6 +1,14 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { LocalizeRouterModule } from 'localize-router'; +import { LocalizeRouterModule, LocalizeRouterSettings, LocalizeParser } from 'localize-router'; +import { LocalizeRouterHttpLoader } from 'localize-router-http-loader'; +import { Http } from '@angular/http'; +import { TranslateService } from '@ngx-translate/core'; +import { Location } from '@angular/common'; + +export function HttpLoaderFactory(translate: TranslateService, location: Location, settings: LocalizeRouterSettings, http: Http) { + return new LocalizeRouterHttpLoader(translate, location, settings, http); +} const routes = [ { path: 'lazy', loadChildren: './+lazy/lazy.module#LazyModule' } @@ -10,7 +18,11 @@ const routes = [ imports: [ RouterModule.forRoot(routes), LocalizeRouterModule.forRoot(routes, { - useCachedLang: false + parser: { + provide: LocalizeParser, + useFactory: HttpLoaderFactory, + deps: [TranslateService, Location, LocalizeRouterSettings, Http] + } }) ], exports: [ RouterModule, LocalizeRouterModule ] diff --git a/demo/cli/tslint.json b/demo/cli/tslint.json index 9113f13..4377add 100644 --- a/demo/cli/tslint.json +++ b/demo/cli/tslint.json @@ -25,11 +25,6 @@ 140 ], "member-access": false, - "member-ordering": [ - true, - "static-before-instance", - "variables-before-functions" - ], "no-arg": true, "no-bitwise": true, "no-console": [ @@ -69,9 +64,7 @@ "single" ], "radix": true, - "semicolon": [ - "always" - ], + "semicolon": [ true, "always" ], "triple-equals": [ true, "allow-null-check" diff --git a/package.json b/package.json index 6c7ed7a..54fab77 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "localize-router", - "version": "1.0.0-alpha.1", + "version": "1.0.0-alpha.2", "description": "An implementation of routes localization for Angular 2", "scripts": { "test": "karma start", "test-watch": "karma start --singleRun=false --autoWatch=true", "commit": "npm run prepublish && npm test", "prepublish": "ngc && npm run build", - "build": "webpack && node copy.js" + "build": "webpack" }, "repository": { "type": "git", diff --git a/src/localize-router.parser.ts b/src/localize-router.parser.ts index 5fc938b..c431269 100644 --- a/src/localize-router.parser.ts +++ b/src/localize-router.parser.ts @@ -79,9 +79,10 @@ export abstract class LocalizeParser { selectedLanguage = locationLang || this.defaultLang; this.translate.setDefaultLang(this.defaultLang); - // don't add prefix if only single language and not enforced via options - if (this.locales.length > 1 || this.settings.alwaysSetPrefix) { - /** set base route */ + + let children: Routes = []; + /** if set prefix is enforced */ + if (this.settings.alwaysSetPrefix) { const baseRoute = { path: '', redirectTo: this.defaultLang, pathMatch: 'full' }; /** extract potential wildcard route */ @@ -89,29 +90,33 @@ export abstract class LocalizeParser { if (wildcardIndex !== -1) { this._wildcardRoute = routes.splice(wildcardIndex, 1)[0]; } + children = this.routes.splice(0, this.routes.length, baseRoute); + } else { + children = [...this.routes]; // shallow copy of routes + } - /** mutable operation on routes */ - let children = this.routes.splice(0, this.routes.length, baseRoute); - - /** exclude certain routes */ - for (let i = children.length - 1; i >= 0; i--) { - if (children[i].data && children[i].data.skipRouteLocalization) { + /** exclude certain routes */ + for (let i = children.length - 1; i >= 0; i--) { + if (children[i].data && children[i].data.skipRouteLocalization) { + if (this.settings.alwaysSetPrefix) { // add directly to routes this.routes.push(children[i]); - children.splice(i, 1); } + children.splice(i, 1); } + } - /** append children routes... */ - if (children && children.length) { + /** append children routes */ + if (children && children.length) { + if (this.locales.length > 1 || this.settings.alwaysSetPrefix) { this._languageRoute = { children: children }; - this.routes.push(this._languageRoute); + this.routes.unshift(this._languageRoute); } + } - /** ...and potential wildcard route */ - if (this._wildcardRoute) { - this.routes.push(this._wildcardRoute); - } + /** ...and potential wildcard route */ + if (this._wildcardRoute && this.settings.alwaysSetPrefix) { + this.routes.push(this._wildcardRoute); } /** translate routes */ @@ -137,7 +142,7 @@ export abstract class LocalizeParser { translateRoutes(language: string): Observable { return new Observable((observer: Observer) => { this._cachedLang = language; - if (this._languageRoute && (this.locales.length > 1 || this.settings.alwaysSetPrefix)) { + if (this._languageRoute) { this._languageRoute.path = language; } @@ -145,10 +150,7 @@ export abstract class LocalizeParser { this._translationObject = translations; this.currentLang = language; - // if no prefixes used - if (this.locales.length === 1 && !this.settings.alwaysSetPrefix) { - this._translateRouteTree(this.routes); - } else { + if (this._languageRoute) { if (this._languageRoute) { this._translateRouteTree(this._languageRoute.children); } @@ -156,6 +158,8 @@ export abstract class LocalizeParser { if (this._wildcardRoute && this._wildcardRoute.redirectTo) { this._translateProperty(this._wildcardRoute, 'redirectTo', true); } + } else { + this._translateRouteTree(this.routes); } observer.next(void 0); @@ -209,7 +213,7 @@ export abstract class LocalizeParser { } get urlPrefix() { - return this.locales.length > 1 || this.settings.alwaysSetPrefix ? this.currentLang : ''; + return this.settings.alwaysSetPrefix || this.currentLang !== this.defaultLang ? this.currentLang : ''; } /** diff --git a/tests/localize-router.parser.spec.ts b/tests/localize-router.parser.spec.ts index 8a4b1d3..4b73725 100644 --- a/tests/localize-router.parser.spec.ts +++ b/tests/localize-router.parser.spec.ts @@ -187,8 +187,8 @@ describe('LocalizeParser', () => { routes = [{ path: 'home' }]; loader.load(routes); tick(); - expect(routes[0]).toEqual({ path: '', redirectTo: 'de', pathMatch: 'full' }); - expect(routes[1]).toEqual({ path: 'de', children: [{ path: 'home_de', data: { localizeRouter: { path: 'home' }} }] }); + expect(routes[0]).toEqual({ path: 'de', children: [{ path: 'home_de', data: { localizeRouter: { path: 'home' }} }] }); + expect(routes[1]).toEqual({ path: '', redirectTo: 'de', pathMatch: 'full' }); expect(loader.currentLang).toEqual('de'); expect(translate.currentLang).toEqual('de'); })); @@ -203,8 +203,8 @@ describe('LocalizeParser', () => { routes = [{ path: 'home' }]; loader.load(routes); tick(); - expect(routes[0]).toEqual({ path: '', redirectTo: 'fr', pathMatch: 'full' }); - expect(routes[1]).toEqual({ path: 'fr', children: [{ path: 'home_fr', data: { localizeRouter: { path: 'home' }} }] }); + expect(routes[0]).toEqual({ path: 'fr', children: [{ path: 'home_fr', data: { localizeRouter: { path: 'home' }} }] }); + expect(routes[1]).toEqual({ path: '', redirectTo: 'fr', pathMatch: 'full' }); expect(loader.currentLang).toEqual('fr', 'loader currentLang should equal'); expect(translate.currentLang).toEqual('fr', 'translate currentLang should equal'); })); @@ -215,10 +215,11 @@ describe('LocalizeParser', () => { localStorage.setItem('LOCALIZE_DEFAULT_LANGUAGE', 'en'); - routes = []; + routes = [{ path: 'home' }]; loader.load(routes); tick(); - expect(routes[0].redirectTo).toEqual('en'); + expect(routes[0].path).toEqual('en'); + expect(routes[1].redirectTo).toEqual('en'); expect(loader.currentLang).toEqual('en', 'loader currentLang should equal'); expect(translate.currentLang).toEqual('en', 'translate currentLang should equal'); })); @@ -232,7 +233,7 @@ describe('LocalizeParser', () => { routes = [{ path: 'home', component: DummyComponent }, { path: '**', component: DummyComponent }]; loader.load(routes); tick(); - expect(routes[1].children[0].path).toEqual('home_en'); + expect(routes[0].children[0].path).toEqual('home_en'); expect(routes.length).toEqual(3); })); @@ -245,9 +246,9 @@ describe('LocalizeParser', () => { routes = [{ path: 'home', component: DummyComponent }]; loader.load(routes); tick(); - expect(routes[1].children[0].path).toEqual('home_en'); + expect(routes[0].children[0].path).toEqual('home_en'); loader.translateRoutes('de').subscribe(() => { - expect(routes[1].children[0].path).toEqual('home_de'); + expect(routes[0].children[0].path).toEqual('home_de'); }); tick(); })); @@ -261,7 +262,7 @@ describe('LocalizeParser', () => { routes = [{ path: 'abc', component: DummyComponent }]; loader.load(routes); tick(); - expect(routes[1].children[0].path).toEqual('abc'); + expect(routes[0].children[0].path).toEqual('abc'); })); it('should not translate if prefix does not match', fakeAsync(() => { @@ -273,7 +274,7 @@ describe('LocalizeParser', () => { routes = [{ path: 'home', component: DummyComponent }]; loader.load(routes); tick(); - expect(routes[1].children[0].path).toEqual('home'); + expect(routes[0].children[0].path).toEqual('home'); })); it('should not translate if prefix does not match', fakeAsync(() => { @@ -285,7 +286,7 @@ describe('LocalizeParser', () => { routes = [{ path: 'home', component: DummyComponent }]; loader.load(routes); tick(); - expect(routes[1].children[0].path).toEqual('home'); + expect(routes[0].children[0].path).toEqual('home'); })); it('should translate redirectTo', fakeAsync(() => { @@ -297,7 +298,7 @@ describe('LocalizeParser', () => { routes = [{ redirectTo: 'home' }]; loader.load(routes); tick(); - expect(routes[1].children[0].redirectTo).toEqual('home_en'); + expect(routes[0].children[0].redirectTo).toEqual('home_en'); })); it('should translate wildcard redirectTo', fakeAsync(() => { @@ -321,7 +322,7 @@ describe('LocalizeParser', () => { routes = [{ path: '/home/about', component: DummyComponent }]; loader.load(routes); tick(); - expect(routes[1].children[0].path).toEqual('/home_en/about_en'); + expect(routes[0].children[0].path).toEqual('/home_en/about_en'); })); it('should translate children', fakeAsync(() => { @@ -344,8 +345,8 @@ describe('LocalizeParser', () => { ]; loader.load(routes); tick(); - expect(routes[1].children[0].path).toEqual('home_en'); - expect(routes[1].children[0].children[0].path).toEqual('about_en'); + expect(routes[0].children[0].path).toEqual('home_en'); + expect(routes[0].children[0].children[0].path).toEqual('about_en'); })); it('should translate lazy loaded children', fakeAsync(() => { @@ -354,24 +355,23 @@ describe('LocalizeParser', () => { localStorage.setItem('LOCALIZE_DEFAULT_LANGUAGE', 'en'); - routes = [ + routes = [ { path: 'home', children: [ - { path: 'about', component: DummyComponent } - ] + { path: 'about', component: DummyComponent } + ] }, { - path: 'contact', loadChildren: '#pathToSomeModule' + path: 'contact', loadChildren: '#pathToSomeModule', _loadedConfig: { routes: [{ path: 'info', component: DummyComponent }] } } ]; - ( routes[1])['_loadedConfig'] = { routes: [{ path: 'info', component: DummyComponent }] }; loader.load(routes); tick(); - expect(routes[1].children[0].path).toEqual('home_en'); - expect(routes[1].children[0].children[0].path).toEqual('about_en'); - expect(routes[1].children[1].path).toEqual('contact_en'); - expect((routes[1].children[1])._loadedConfig.routes[0].path).toEqual('info_en'); + expect(routes[0].children[0].path).toEqual('home_en'); + expect(routes[0].children[0].children[0].path).toEqual('about_en'); + expect(routes[0].children[1].path).toEqual('contact_en'); + expect((routes[0].children[1])._loadedConfig.routes[0].path).toEqual('info_en'); })); /** @@ -388,8 +388,8 @@ describe('LocalizeParser', () => { loader.load(routes); tick(); - expect(routes[0]).toEqual({ path: '', redirectTo: 'de', pathMatch: 'full' }); - expect(routes[1]).toEqual({ path: 'de', children: [{ path: 'home_de', data: { localizeRouter: { path: 'home' }} }] }); + expect(routes[0]).toEqual({ path: 'de', children: [{ path: 'home_de', data: { localizeRouter: { path: 'home' }} }] }); + expect(routes[1]).toEqual({ path: '', redirectTo: 'de', pathMatch: 'full' }); expect(loader.currentLang).toEqual('de', 'loader currentLang should equal'); expect(translate.currentLang).toEqual('de', 'translate currentLang should equal'); })); @@ -435,11 +435,11 @@ describe('LocalizeParser', () => { loader.load(routes); tick(); - expect(routes[0]).toEqual({ path: '', redirectTo: 'de', pathMatch: 'full' }); - expect(routes[1]).toEqual({ + expect(routes[0]).toEqual({ path: 'de', children: [{ path: 'home_de', component: DummyComponent, data: { localizeRouter: { path: 'home' }} }] }); + expect(routes[1]).toEqual({ path: '', redirectTo: 'de', pathMatch: 'full' }); expect(loader.currentLang).toEqual('de', 'loader currentLang should equal'); expect(translate.currentLang).toEqual('de', 'translate currentLang should equal'); })); @@ -459,6 +459,22 @@ describe('LocalizeParser', () => { expect(loader.currentLang).toEqual('de', 'loader currentLang should equal'); expect(translate.currentLang).toEqual('de', 'translate currentLang should equal'); })); + it('should add prefix if not enforced and multi lang', fakeAsync(() => { + settings.alwaysSetPrefix = false; + + loader = new ManualParserLoader(translate, location, settings, ['de', 'en'], prefix); + + localStorage.setItem('LOCALIZE_DEFAULT_LANGUAGE', 'de'); + + routes = [{ path: 'home', component: DummyComponent }]; + loader.load(routes); + tick(); + + expect(routes[0]).toEqual({ path: 'de', children: [{ path: 'home_de', component: DummyComponent, data: { localizeRouter: { path: 'home' }} }] }); + expect(routes[1]).toEqual({ path: 'home_de', component: DummyComponent, data: { localizeRouter: { path: 'home' }} }); + expect(loader.currentLang).toEqual('de', 'loader currentLang should equal'); + expect(translate.currentLang).toEqual('de', 'translate currentLang should equal'); + })); it('should exclude certain routes', fakeAsync(() => { settings.useCachedLang = false; loader = new ManualParserLoader(translate, location, settings, locales, prefix); @@ -469,9 +485,9 @@ describe('LocalizeParser', () => { tick(); expect(routes.length).toEqual(3); - expect(routes[0]).toEqual({ path: '', redirectTo: 'de', pathMatch: 'full' }); - expect(routes[1]).toEqual({ path: 'about', data: { skipRouteLocalization: true } }); - expect(routes[2]).toEqual({ path: 'de', children: [{ path: 'home_de', data: { localizeRouter: { path: 'home' }} }] }); + expect(routes[0]).toEqual({ path: 'de', children: [{ path: 'home_de', data: { localizeRouter: { path: 'home' }} }] }); + expect(routes[1]).toEqual({ path: '', redirectTo: 'de', pathMatch: 'full' }); + expect(routes[2]).toEqual({ path: 'about', data: { skipRouteLocalization: true } }); })); });