From 6e6ce77e9fd68c85230a965ca09ec51ea57a5341 Mon Sep 17 00:00:00 2001 From: Burak Tasci Date: Sun, 26 Feb 2017 00:36:38 +0300 Subject: [PATCH] fix: depend on Angular 2.0.0 (#3) * build(npm): update deps * build(gulp): update tasks * build(webpack): update config * refactor: update .gitignore * refactor: update .npmignore * style(core): lint --- .gitignore | 1 + .npmignore | 1 + README.md | 10 +- config/gulp-tasks.js | 276 ++++++++-------- config/helpers.js | 10 +- config/karma.conf.js | 232 ++++++------- config/spec-bundle.js | 12 +- config/webpack.prod.js | 301 +++++++++-------- config/webpack.test.js | 264 +++++++-------- index.ts | 145 +++++---- package.json | 28 +- src/i18n-router.loader.ts | 74 +++-- src/i18n-router.pipe.ts | 75 ++--- src/i18n-router.service.ts | 224 ++++++------- tests/i18n-router.loader.spec.ts | 221 +++++++------ tests/i18n-router.pipe.spec.ts | 225 ++++++------- tests/i18n-router.service.spec.ts | 521 +++++++++++++++--------------- tests/index.spec.ts | 237 +++++++------- tslint.json | 4 - 19 files changed, 1454 insertions(+), 1407 deletions(-) diff --git a/.gitignore b/.gitignore index 235f134..31a5503 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.js *.d.ts *.metadata.json +/.idea /bundles !/config/*.js /coverage diff --git a/.npmignore b/.npmignore index aab49df..54d10a5 100644 --- a/.npmignore +++ b/.npmignore @@ -2,6 +2,7 @@ *.ts !*.d.ts /.github +/.idea /config /coverage /node_modules diff --git a/README.md b/README.md index e1a6854..82ebfa8 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,15 @@ To resolve this issue, it is **highly recommended** to use [ng-router-loader]. H - [License](#license) ## Prerequisites -Verify that you are running at least `@angular v2.4.0` and `@angular/router v3.4.0`. Older versions containing outdated dependencies, might produce errors. +This package depends on `@angular v2.0.0` but it's highly recommended that you are running at least **`@angular v2.4.0`** and **`@angular/router v3.4.0`**. Older versions contain outdated dependencies, might produce errors. -You should also upgrade to a minimum version of `TypeScript 2.1.x`. +Also, please ensure that you are using **`Typescript v2.1.6`** or higher. + +#### WARNING + +The pull request [#14327](https://github.com/angular/angular/pull/14327) on **`@angular v2.4.8`** and **`@angular v4.0.0-rc.1`** introduced a severe error [#14588](https://github.com/angular/angular/issues/14588) which causes the app to fall into an infinite loop before bootstrapping. + +In order to avoid issues, avoid using these versions of **Angular**. ## Getting started ### Installation diff --git a/config/gulp-tasks.js b/config/gulp-tasks.js index e4386e2..f98b518 100644 --- a/config/gulp-tasks.js +++ b/config/gulp-tasks.js @@ -3,49 +3,49 @@ /** * Gulp helpers & dependencies */ -var gulp = require('gulp'), - $ = require('gulp-load-plugins')({ - pattern: [ - 'gulp-*', - 'rimraf', - 'webpack' - ] - }), - $$ = require('./helpers'); +const gulp = require('gulp'), + $ = require('gulp-load-plugins')({ + pattern: [ + 'gulp-*', + 'rimraf', + 'webpack' + ] + }), + $$ = require('./helpers'); /** * Define tasks */ -var tasks = {}; +const tasks = {}; /** * Clean file(s) */ -var clean = { - 'temp': function (done) { - $.rimraf('./temp', done); - }, - 'bundles': function (done) { - $.rimraf('./bundles', done); - }, - 'index.js': function (done) { - $.rimraf('./index.js', done); - }, - 'index.d.ts': function (done) { - $.rimraf('./index.d.ts', done); - }, - 'index.metadata.json': function (done) { - $.rimraf('./index.metadata.json', done); - }, - 'src/*.js': function (done) { - $.rimraf('./src/**/*.js', done); - }, - 'src/*.d.ts': function (done) { - $.rimraf('./src/**/*.d.ts', done); - }, - 'src/*.metadata.json': function (done) { - $.rimraf('./src/**/*.metadata.json', done); - } +const clean = { + 'temp': function (done) { + $.rimraf('./temp', done); + }, + 'bundles': function (done) { + $.rimraf('./bundles', done); + }, + 'index.js': function (done) { + $.rimraf('./index.js', done); + }, + 'index.d.ts': function (done) { + $.rimraf('./index.d.ts', done); + }, + 'index.metadata.json': function (done) { + $.rimraf('./index.metadata.json', done); + }, + 'src/*.js': function (done) { + $.rimraf('./src/**/*.js', done); + }, + 'src/*.d.ts': function (done) { + $.rimraf('./src/**/*.d.ts', done); + }, + 'src/*.metadata.json': function (done) { + $.rimraf('./src/**/*.metadata.json', done); + } }; clean.temp.displayName = 'clean:./temp/**'; @@ -60,34 +60,34 @@ clean['src/*.metadata.json'].displayName = 'clean:./src/*.js'; /** * AoT compilation */ -var ts = { - compile: function(done) { - const options = { - continueOnError: false, - pipeStdout: false, - customTemplatingThing: 'test' - }; - const reportOptions = { - err: true, - stderr: true, - stdout: true - }; - - return gulp.src('./tsconfig.json') - .pipe($.exec('ngc -p "./tsconfig.json"', options)) - .pipe($.exec.reporter(reportOptions)) - .on('end', done); - }, - lint: function(done) { - return gulp.src([ - './index.ts', - './src/**/*.ts', - '!./src/**/*.d.ts' - ]) - .pipe($.tslint({ formatter: 'verbose' })) - .pipe($.tslint.report({ emitError: false })) - .on('end', done); - } +const ts = { + compile: function (done) { + const options = { + continueOnError: false, + pipeStdout: false, + customTemplatingThing: 'test' + }; + const reportOptions = { + err: true, + stderr: true, + stdout: true + }; + + return gulp.src('./tsconfig.json') + .pipe($.exec('"./node_modules/.bin/ngc" -p "./tsconfig.json"', options)) + .pipe($.exec.reporter(reportOptions)) + .on('end', done); + }, + lint: function (done) { + return gulp.src([ + './index.ts', + './src/**/*.ts', + '!./src/**/*.d.ts' + ]) + .pipe($.tslint({formatter: 'verbose'})) + .pipe($.tslint.report({emitError: false})) + .on('end', done); + } }; ts.compile.displayName = 'compile:ngc'; @@ -96,48 +96,48 @@ ts.lint.displayName = 'tslint'; /** * Bundle */ -var bundle = { - webpack: function (done) { - const chalk = require('chalk'), - conf = require('./webpack.prod.js'); - - $.webpack(conf) - .run(function (err, stats) { - if (err) { - console.log(chalk.red(`Error: ${err}`)); - done(); - } else { - const statsJson = stats.toJson(), - warnings = statsJson.warnings, - errors = statsJson.errors; - - Object.keys(warnings) - .forEach(function (key) { - console.log(chalk.gray(`Warning: ${warnings[key]}\n`)); - }); - - if (warnings.length > 0) - console.log(chalk.gray(` (${warnings.length}) warning(s) total.\n`)); - - Object.keys(errors) - .forEach(function (key) { - console.log(chalk.red(`Error: ${errors[key]}\n`)); - }); - - if (errors.length > 0) - console.log(chalk.red(` (${errors.length}) error(s) total.\n`)); - - Object.keys(stats.compilation.assets) - .forEach(function (key) { - console.log(`Webpack: output ${chalk.green(key)}`); - }); - - console.log(`Webpack: ${chalk.blue(`finished`)}`); - - done(); - } +const bundle = { + webpack: function (done) { + const chalk = require('chalk'), + conf = require('./webpack.prod.js'); + + $.webpack(conf) + .run(function (err, stats) { + if (err) { + console.log(chalk.red(`Error: ${err}`)); + done(); + } else { + const statsJson = stats.toJson(), + warnings = statsJson.warnings, + errors = statsJson.errors; + + Object.keys(warnings) + .forEach(function (key) { + console.log(chalk.gray(`Warning: ${warnings[key]}\n`)); }); - } + + if (warnings.length > 0) + console.log(chalk.gray(` (${warnings.length}) warning(s) total.\n`)); + + Object.keys(errors) + .forEach(function (key) { + console.log(chalk.red(`Error: ${errors[key]}\n`)); + }); + + if (errors.length > 0) + console.log(chalk.red(` (${errors.length}) error(s) total.\n`)); + + Object.keys(stats.compilation.assets) + .forEach(function (key) { + console.log(`Webpack: output ${chalk.green(key)}`); + }); + + console.log(`Webpack: ${chalk.blue(`finished`)}`); + + done(); + } + }); + } }; bundle.webpack.displayName = 'bundle:webpack'; @@ -145,19 +145,19 @@ bundle.webpack.displayName = 'bundle:webpack'; /** * Tests */ -var tests = { - run: function(done) { - const server = require('karma').Server; - - new server({ - configFile: $$.root('./karma.conf.js'), - singleRun: true - }, - function() { - done(); - process.exit(0); - }).start(); - } +const tests = { + run: function (done) { + const server = require('karma').Server; + + new server({ + configFile: $$.root('./karma.conf.js'), + singleRun: true + }, + function () { + done(); + process.exit(0); + }).start(); + } }; tests.run.displayName = 'tests:run'; @@ -174,39 +174,39 @@ tasks.tests = tests; * Task: __CLEAN__ */ gulp.task('__CLEAN__', - gulp.parallel( - clean.temp, - clean.bundles, - clean['index.js'], - clean['index.d.ts'], - clean['index.metadata.json'], - clean['src/*.js'], - clean['src/*.d.ts'], - clean['src/*.metadata.json'] - )); + gulp.parallel( + clean.temp, + clean.bundles, + clean['index.js'], + clean['index.d.ts'], + clean['index.metadata.json'], + clean['src/*.js'], + clean['src/*.d.ts'], + clean['src/*.metadata.json'] + )); /** * Task: make */ gulp.task('make', - gulp.series( - '__CLEAN__', - tasks.ts.compile, - tasks.bundle.webpack - )); + gulp.series( + '__CLEAN__', + tasks.ts.compile, + tasks.bundle.webpack + )); /** * Task: _TEST_ */ gulp.task('_TEST_', - gulp.series( - tasks.tests.run - )); + gulp.series( + tasks.tests.run + )); /** * Task: review:ts */ gulp.task('review:ts', - gulp.series( - tasks.ts.lint - )); \ No newline at end of file + gulp.series( + tasks.ts.lint + )); \ No newline at end of file diff --git a/config/helpers.js b/config/helpers.js index 8b8d652..99b09c2 100644 --- a/config/helpers.js +++ b/config/helpers.js @@ -8,12 +8,12 @@ $.path = require('path'); /** * Helper methods */ -function root(args) { - const ROOT = $.path.resolve(__dirname, '..'); - args = Array.prototype.slice.call(arguments, 0); +const root = function (args) { + const ROOT = $.path.resolve(__dirname, '..'); + args = Array.prototype.slice.call(arguments, 0); - return $.path.join.apply($.path, [ROOT].concat(args)); -} + return $.path.join.apply($.path, [ROOT].concat(args)); +}; /** * Exports diff --git a/config/karma.conf.js b/config/karma.conf.js index e3db8a1..f90d108 100644 --- a/config/karma.conf.js +++ b/config/karma.conf.js @@ -3,120 +3,120 @@ */ const webpackConfig = require('./webpack.test.js'); -module.exports = function(config) { - const configuration = { - // base path that will be used to resolve all patterns (e.g. files, exclude) - basePath: '', - - /** - * Frameworks to use - * - * available frameworks: https://npmjs.org/browse/keyword/karma-adapter - */ - frameworks: ['jasmine'], - - // list of files to exclude - exclude: [], - - client: { - captureConsole: false - }, - - /** - * list of files / patterns to load in the browser - * - * we are building the test environment in ./spec-bundle.js - */ - files: [ - { - pattern: './config/spec-bundle.js', - watched: false - } - ], - - /** - * preprocess matching files before serving them to the browser - * available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - */ - preprocessors: { - './config/spec-bundle.js': ['coverage', 'webpack', 'sourcemap'] - }, - - // Webpack config at ./webpack.test.js - webpack: webpackConfig, - - coverageReporter: { - type: 'in-memory' - }, - - remapCoverageReporter: { - 'text-summary': null, - json: './coverage/coverage.json', - html: './coverage/html' - }, - - // Webpack please don't spam the console when running in karma! - webpackMiddleware: { - // webpack-dev-middleware configuration - // i.e. - noInfo: true, - // and use stats to turn off verbose output - stats: { - // options i.e. - chunks: false - } - }, - - /** - * test results reporter to use - * - * possible values: 'dots', 'progress' - * available reporters: https://npmjs.org/browse/keyword/karma-reporter - */ - reporters: ['mocha', 'coverage', 'remap-coverage'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - /** - * level of logging - * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - */ - logLevel: config.LOG_WARN, - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: false, - - /** - * start these browsers - * available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - */ - browsers: [ - 'Chrome' - ], - - customLaunchers: { - ChromeTravisCi: { - base: 'Chrome', - flags: ['--no-sandbox'] - } - }, - - /** - * Continuous Integration mode - * if true, Karma captures browsers, runs the tests and exits - */ - singleRun: true - }; - - if (process.env.TRAVIS) { - configuration.browsers = [ - 'ChromeTravisCi' - ]; - } - - config.set(configuration); +module.exports = function (config) { + const configuration = { + // base path that will be used to resolve all patterns (e.g. files, exclude) + basePath: '', + + /** + * Frameworks to use + * + * available frameworks: https://npmjs.org/browse/keyword/karma-adapter + */ + frameworks: ['jasmine'], + + // list of files to exclude + exclude: [], + + client: { + captureConsole: false + }, + + /** + * list of files / patterns to load in the browser + * + * we are building the test environment in ./spec-bundle.js + */ + files: [ + { + pattern: './config/spec-bundle.js', + watched: false + } + ], + + /** + * preprocess matching files before serving them to the browser + * available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + */ + preprocessors: { + './config/spec-bundle.js': ['coverage', 'webpack', 'sourcemap'] + }, + + // Webpack config at ./webpack.test.js + webpack: webpackConfig, + + coverageReporter: { + type: 'in-memory' + }, + + remapCoverageReporter: { + 'text-summary': null, + json: './coverage/coverage.json', + html: './coverage/html' + }, + + // Webpack please don't spam the console when running in karma! + webpackMiddleware: { + // webpack-dev-middleware configuration + // i.e. + noInfo: true, + // and use stats to turn off verbose output + stats: { + // options i.e. + chunks: false + } + }, + + /** + * test results reporter to use + * + * possible values: 'dots', 'progress' + * available reporters: https://npmjs.org/browse/keyword/karma-reporter + */ + reporters: ['mocha', 'coverage', 'remap-coverage'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + /** + * level of logging + * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + */ + logLevel: config.LOG_WARN, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + /** + * start these browsers + * available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + */ + browsers: [ + 'Chrome' + ], + + customLaunchers: { + ChromeTravisCi: { + base: 'Chrome', + flags: ['--no-sandbox'] + } + }, + + /** + * Continuous Integration mode + * if true, Karma captures browsers, runs the tests and exits + */ + singleRun: true + }; + + if (process.env.TRAVIS) { + configuration.browsers = [ + 'ChromeTravisCi' + ]; + } + + config.set(configuration); }; diff --git a/config/spec-bundle.js b/config/spec-bundle.js index 18a77df..e08f3dd 100644 --- a/config/spec-bundle.js +++ b/config/spec-bundle.js @@ -24,11 +24,11 @@ require('zone.js/dist/fake-async-test'); require('rxjs/Rx'); const testing = require('@angular/core/testing'), - browser = require('@angular/platform-browser-dynamic/testing'); + browser = require('@angular/platform-browser-dynamic/testing'); testing.TestBed.initTestEnvironment( - browser.BrowserDynamicTestingModule, - browser.platformBrowserDynamicTesting() + browser.BrowserDynamicTestingModule, + browser.platformBrowserDynamicTesting() ); /** @@ -40,7 +40,7 @@ testing.TestBed.initTestEnvironment( * any file that ends with spec.ts and get its path. By passing in true * we say do this recursively */ -var testContext = require.context('../tests', true, /\.spec\.ts/); +const testContext = require.context('../tests', true, /\.spec\.ts/); /** * get all the files, for each file, call the context function @@ -48,8 +48,8 @@ var testContext = require.context('../tests', true, /\.spec\.ts/); * loop and require those spec files here */ function requireAll(requireContext) { - return requireContext.keys().map(requireContext); + return requireContext.keys().map(requireContext); } // requires and returns all modules that match -var modules = requireAll(testContext); +requireAll(testContext); diff --git a/config/webpack.prod.js b/config/webpack.prod.js index 899b146..37ae066 100644 --- a/config/webpack.prod.js +++ b/config/webpack.prod.js @@ -4,9 +4,9 @@ const helpers = require('./helpers'); const checkerPlugin = require('awesome-typescript-loader').CheckerPlugin, - contextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin'), - loaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin'), - uglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin'); + contextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin'), + loaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin'), + uglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin'); /** * Webpack configuration @@ -14,178 +14,177 @@ const checkerPlugin = require('awesome-typescript-loader').CheckerPlugin, * See: http://webpack.github.io/docs/configuration.html#cli */ module.exports = { - /** - * Cache generated modules and chunks to improve performance for multiple incremental builds. - * This is enabled by default in watch mode. - * You can pass false to disable it. - * - * See: http://webpack.github.io/docs/configuration.html#cache - */ - //cache: false, + /** + * Cache generated modules and chunks to improve performance for multiple incremental builds. + * This is enabled by default in watch mode. + * You can pass false to disable it. + * + * See: http://webpack.github.io/docs/configuration.html#cache + */ + //cache: false, - /** - * The entry point for the bundle - * Our Angular app - * - * See: http://webpack.github.io/docs/configuration.html#entry - */ - entry: helpers.root('index.ts'), + /** + * The entry point for the bundle + * Our Angular app + * + * See: http://webpack.github.io/docs/configuration.html#entry + */ + entry: helpers.root('index.ts'), + /** + * Options affecting the resolving of modules. + * + * See: http://webpack.github.io/docs/configuration.html#resolve + */ + resolve: { + extensions: ['.ts', '.js'] + }, + + /** + * Options affecting the output of the compilation. + * + * See: http://webpack.github.io/docs/configuration.html#output + */ + output: { /** - * Options affecting the resolving of modules. + * The output directory as absolute path (required). * - * See: http://webpack.github.io/docs/configuration.html#resolve + * See: http://webpack.github.io/docs/configuration.html#output-path */ - resolve: { - extensions: ['.ts', '.js'] - }, + path: helpers.root('bundles'), + publicPath: '/', /** - * Options affecting the output of the compilation. + * Specifies the name of each output file on disk. + * IMPORTANT: You must not specify an absolute path here! * - * See: http://webpack.github.io/docs/configuration.html#output + * See: http://webpack.github.io/docs/configuration.html#output-filename */ - output: { - /** - * The output directory as absolute path (required). - * - * See: http://webpack.github.io/docs/configuration.html#output-path - */ - path: helpers.root('bundles'), - publicPath: '/', + filename: 'i18n-router.umd.min.js', + + libraryTarget: 'umd', + library: 'i18n-router' + }, - /** - * Specifies the name of each output file on disk. - * IMPORTANT: You must not specify an absolute path here! - * - * See: http://webpack.github.io/docs/configuration.html#output-filename - */ - filename: 'i18n-router.umd.min.js', + /** + * Require those dependencies but don't bundle them + */ + externals: [/^@angular\//, /^rxjs\//, /^lodash/], - libraryTarget: 'umd', - library: 'i18n-router' - }, + /** + * Options affecting the normal modules. + * + * See: http://webpack.github.io/docs/configuration.html#module + */ + module: { + rules: [ + /** + * TS linter + */ + { + enforce: 'pre', + test: /\.ts$/, + use: 'tslint-loader', + exclude: [helpers.root('node_modules')] + }, + /** + * Typescript loader support for .ts and Angular 2 async routes via .async.ts + * Replace templateUrl and stylesUrl with require() + * + * See: https://github.com/s-panferov/awesome-typescript-loader + */ + { + test: /\.ts$/, + use: 'awesome-typescript-loader?declaration=false', + exclude: [/\.(spec|e2e)\.ts$/] + } + ] + }, + + /** + * Add additional plugins to the compiler. + * + * See: http://webpack.github.io/docs/configuration.html#plugins + */ + plugins: [ /** - * Require those dependencies but don't bundle them + * Plugin: ForkCheckerPlugin + * Description: Do type checking in a separate process, so webpack don't need to wait. + * + * See: https://github.com/s-panferov/awesome-typescript-loader#forkchecker-boolean-defaultfalse */ - externals: [/^\@angular\//, /^rxjs\//, /^lodash/], + new checkerPlugin(), /** - * Options affecting the normal modules. + * Plugin: ContextReplacementPlugin + * Description: Provides context to Angular's use of System.import * - * See: http://webpack.github.io/docs/configuration.html#module + * See: https://webpack.github.io/docs/list-of-plugins.html#contextreplacementplugin + * See: https://github.com/angular/angular/issues/11580 */ - module: { - rules: [ - /** - * TS linter - */ - { - enforce: 'pre', - test: /\.ts$/, - use: 'tslint-loader', - exclude: [helpers.root('node_modules')] - }, - - /** - * Typescript loader support for .ts and Angular 2 async routes via .async.ts - * Replace templateUrl and stylesUrl with require() - * - * See: https://github.com/s-panferov/awesome-typescript-loader - */ - { - test: /\.ts$/, - use: 'awesome-typescript-loader?declaration=false', - exclude: [/\.(spec|e2e)\.ts$/] - } - ] - }, + new contextReplacementPlugin( + // fix the warning in ./~/@angular/core/src/linker/system_js_ng_module_factory_loader.js + /angular([\\\/])core([\\\/])(esm([\\\/])src|src)([\\\/])linker/, + helpers.root('src') + ), /** - * Add additional plugins to the compiler. + * Plugin LoaderOptionsPlugin (experimental) * - * See: http://webpack.github.io/docs/configuration.html#plugins + * See: https://gist.github.com/sokra/27b24881210b56bbaff7 */ - plugins: [ - /** - * Plugin: ForkCheckerPlugin - * Description: Do type checking in a separate process, so webpack don't need to wait. - * - * See: https://github.com/s-panferov/awesome-typescript-loader#forkchecker-boolean-defaultfalse - */ - new checkerPlugin(), - - /** - * Plugin: ContextReplacementPlugin - * Description: Provides context to Angular's use of System.import - * - * See: https://webpack.github.io/docs/list-of-plugins.html#contextreplacementplugin - * See: https://github.com/angular/angular/issues/11580 - */ - new contextReplacementPlugin( - // fix the warning in ./~/@angular/core/src/linker/system_js_ng_module_factory_loader.js - /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, - helpers.root('src') - ), - - /** - * Plugin LoaderOptionsPlugin (experimental) - * - * See: https://gist.github.com/sokra/27b24881210b56bbaff7 - */ - new loaderOptionsPlugin({ - options: { - tslint: { - emitErrors: false, - failOnHint: false - } - } - }), - - /** - * Plugin: UglifyJsPlugin - * Description: Minimize all JavaScript output of chunks. - * Loaders are switched into minimizing mode. - * - * See: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin - */ - new uglifyJsPlugin({ - beautify: false, - output: { - comments: false - }, - mangle: { - screw_ie8: true - }, - compress: { - screw_ie8: true, - warnings: false, - conditionals: true, - unused: true, - comparisons: true, - sequences: true, - dead_code: true, - evaluate: true, - if_return: true, - join_vars: true, - negate_iife: false // we need this for lazy v8 - } - }) - ], + new loaderOptionsPlugin({ + options: { + tslint: { + failOnHint: false + } + } + }), /** - * Include polyfills or mocks for various node stuff - * Description: Node configuration + * Plugin: UglifyJsPlugin + * Description: Minimize all JavaScript output of chunks. + * Loaders are switched into minimizing mode. * - * See: https://webpack.github.io/docs/configuration.html#node + * See: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin */ - node: { - global: true, - crypto: 'empty', - process: false, - module: false, - clearImmediate: false, - setImmediate: false - } + new uglifyJsPlugin({ + beautify: false, + output: { + comments: false + }, + mangle: { + screw_ie8: true + }, + compress: { + screw_ie8: true, + warnings: false, + conditionals: true, + unused: true, + comparisons: true, + sequences: true, + dead_code: true, + evaluate: true, + if_return: true, + join_vars: true, + negate_iife: false // we need this for lazy v8 + } + }) + ], + + /** + * Include polyfills or mocks for various node stuff + * Description: Node configuration + * + * See: https://webpack.github.io/docs/configuration.html#node + */ + node: { + global: true, + crypto: 'empty', + process: false, + module: false, + clearImmediate: false, + setImmediate: false + } }; diff --git a/config/webpack.test.js b/config/webpack.test.js index cfd667a..364c1ac 100644 --- a/config/webpack.test.js +++ b/config/webpack.test.js @@ -4,7 +4,7 @@ const helpers = require('./helpers'); const contextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin'), - loaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin'); + loaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin'); /** * Webpack configuration @@ -12,155 +12,155 @@ const contextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin') * See: http://webpack.github.io/docs/configuration.html#cli */ module.exports = { - /** - * Source map for Karma from the help of karma-sourcemap-loader & karma-webpack - * - * Do not change, leave as is or it wont work. - * See: https://github.com/webpack/karma-webpack#source-maps - */ - devtool: 'inline-source-map', + /** + * Source map for Karma from the help of karma-sourcemap-loader & karma-webpack + * + * Do not change, leave as is or it wont work. + * See: https://github.com/webpack/karma-webpack#source-maps + */ + devtool: 'inline-source-map', + /** + * Options affecting the resolving of modules. + * + * See: http://webpack.github.io/docs/configuration.html#resolve + */ + resolve: { /** - * Options affecting the resolving of modules. + * An array of extensions that should be used to resolve modules. * - * See: http://webpack.github.io/docs/configuration.html#resolve + * See: http://webpack.github.io/docs/configuration.html#resolve-extensions */ - resolve: { - /** - * An array of extensions that should be used to resolve modules. - * - * See: http://webpack.github.io/docs/configuration.html#resolve-extensions - */ - extensions: ['.ts', '.js'], - - /** - * Make sure root is ./ - */ - modules: [ - helpers.root('src'), - helpers.root('node_modules') - ] - }, + extensions: ['.ts', '.js'], /** - * Options affecting the normal modules. - * - * See: http://webpack.github.io/docs/configuration.html#module + * Make sure root is ./ */ - module: { - rules: [ - /** - * Source map loader support for *.js files - * Extracts SourceMaps for source files that as added as sourceMappingURL comment. - * - * See: https://github.com/webpack/source-map-loader - */ - { - enforce: 'pre', - test: /\.js$/, - use: 'source-map-loader', - exclude: [ - // these packages have problems with their sourcemaps - helpers.root('node_modules/rxjs'), - helpers.root('node_modules/@angular') - ] - }, + modules: [ + helpers.root('src'), + helpers.root('node_modules') + ] + }, - /** - * Typescript loader support for .ts and Angular 2 async routes via .async.ts - * - * See: https://github.com/s-panferov/awesome-typescript-loader - */ - { - test: /\.ts$/, - use: [ - { - loader: 'awesome-typescript-loader', - query: { - // use inline sourcemaps for "karma-remap-coverage" reporter - sourceMap: false, - inlineSourceMap: true, - compilerOptions: { - // Remove TypeScript helpers to be injected - // below by DefinePlugin - removeComments: true - } - } - } - ], - exclude: [/\.e2e\.ts$/] - }, + /** + * Options affecting the normal modules. + * + * See: http://webpack.github.io/docs/configuration.html#module + */ + module: { + rules: [ + /** + * Source map loader support for *.js files + * Extracts SourceMaps for source files that as added as sourceMappingURL comment. + * + * See: https://github.com/webpack/source-map-loader + */ + { + enforce: 'pre', + test: /\.js$/, + use: 'source-map-loader', + exclude: [ + // these packages have problems with their sourcemaps + helpers.root('node_modules/rxjs'), + helpers.root('node_modules/@angular') + ] + }, - /** - * Instruments JS files with Istanbul for subsequent code coverage reporting. - * Instrument only testing sources. - * - * See: https://github.com/deepsweet/istanbul-instrumenter-loader - */ - { - enforce: 'post', - test: /\.(js|ts)$/, - use: 'istanbul-instrumenter-loader?esModules', - include: helpers.root('src'), - exclude: [ - /\.(e2e|spec)\.ts$/, - /node_modules/ - ] + /** + * Typescript loader support for .ts and Angular 2 async routes via .async.ts + * + * See: https://github.com/s-panferov/awesome-typescript-loader + */ + { + test: /\.ts$/, + use: [ + { + loader: 'awesome-typescript-loader', + query: { + // use inline sourcemaps for "karma-remap-coverage" reporter + sourceMap: false, + inlineSourceMap: true, + compilerOptions: { + // Remove TypeScript helpers to be injected + // below by DefinePlugin + removeComments: true + } } + } + ], + exclude: [/\.e2e\.ts$/] + }, + + /** + * Instruments JS files with Istanbul for subsequent code coverage reporting. + * Instrument only testing sources. + * + * See: https://github.com/deepsweet/istanbul-instrumenter-loader + */ + { + enforce: 'post', + test: /\.(js|ts)$/, + use: 'istanbul-instrumenter-loader?esModules', + include: helpers.root('src'), + exclude: [ + /\.(e2e|spec)\.ts$/, + /node_modules/ ] - }, + } + ] + }, + /** + * Add additional plugins to the compiler. + * + * See: http://webpack.github.io/docs/configuration.html#plugins + */ + plugins: [ /** - * Add additional plugins to the compiler. + * Plugin: ContextReplacementPlugin + * Description: Provides context to Angular's use of System.import * - * See: http://webpack.github.io/docs/configuration.html#plugins + * See: https://webpack.github.io/docs/list-of-plugins.html#contextreplacementplugin + * See: https://github.com/angular/angular/issues/11580 */ - plugins: [ - /** - * Plugin: ContextReplacementPlugin - * Description: Provides context to Angular's use of System.import - * - * See: https://webpack.github.io/docs/list-of-plugins.html#contextreplacementplugin - * See: https://github.com/angular/angular/issues/11580 - */ - new contextReplacementPlugin( - // fix the warning in ./~/@angular/core/src/linker/system_js_ng_module_factory_loader.js - /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, - helpers.root('src') - ), - - /** - * Plugin LoaderOptionsPlugin (experimental) - * - * See: https://gist.github.com/sokra/27b24881210b56bbaff7 - */ - new loaderOptionsPlugin({ - debug: true, - options: {} - }) - ], + new contextReplacementPlugin( + // fix the warning in ./~/@angular/core/src/linker/system_js_ng_module_factory_loader.js + /angular([\\\/])core([\\\/])(esm([\\\/])src|src)([\\\/])linker/, + helpers.root('src') + ), /** - * Disable performance hints + * Plugin LoaderOptionsPlugin (experimental) * - * See: https://github.com/a-tarasyuk/rr-boilerplate/blob/master/webpack/dev.config.babel.js#L41 + * See: https://gist.github.com/sokra/27b24881210b56bbaff7 */ - performance: { - hints: false - }, + new loaderOptionsPlugin({ + debug: true, + options: {} + }) + ], - /** - * Include polyfills or mocks for various node stuff - * Description: Node configuration - * - * See: https://webpack.github.io/docs/configuration.html#node - */ - node: { - global: true, - crypto: 'empty', - process: false, - module: false, - clearImmediate: false, - setImmediate: false - } + /** + * Disable performance hints + * + * See: https://github.com/a-tarasyuk/rr-boilerplate/blob/master/webpack/dev.config.babel.js#L41 + */ + performance: { + hints: false + }, + + /** + * Include polyfills or mocks for various node stuff + * Description: Node configuration + * + * See: https://webpack.github.io/docs/configuration.html#node + */ + node: { + global: true, + crypto: 'empty', + process: false, + module: false, + clearImmediate: false, + setImmediate: false + } }; diff --git a/index.ts b/index.ts index e3c1e55..39a7efb 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,8 @@ // angular -import { ANALYZE_FOR_ENTRY_COMPONENTS, APP_INITIALIZER, Inject, NgModule, ModuleWithProviders, OpaqueToken, Optional, - SkipSelf } from '@angular/core'; +import { + ANALYZE_FOR_ENTRY_COMPONENTS, APP_INITIALIZER, Inject, NgModule, ModuleWithProviders, OpaqueToken, Optional, + SkipSelf +} from '@angular/core'; import { provideRoutes, RouterModule, Router, Routes } from '@angular/router'; import { ROUTES } from '@angular/router/src/router_config_loader'; @@ -15,16 +17,16 @@ export * from './src/i18n-router.service'; export const RAW_ROUTES = new OpaqueToken('RAW_ROUTES'); export const I18N_ROUTER_PROVIDERS: any[] = [ - I18NRouterService + I18NRouterService ]; // for AoT compilation export function i18nRouterFactory(routes: Routes): I18NRouterLoader { - return new I18NRouterStaticLoader(routes, {}); + return new I18NRouterStaticLoader(routes, {}); } export function initializerFactory(loader: I18NRouterLoader): any { - return () => loader.loadTranslations(); + return () => loader.loadTranslations(); } export const I18N_ROUTER_FORROOT_GUARD = new OpaqueToken('I18N_ROUTER_FORROOT_GUARD'); @@ -34,78 +36,79 @@ export const MODULE_KEY = new OpaqueToken('MODULE_KEY'); * Do not specify providers for modules that might be imported by a lazy loaded module. */ @NgModule({ - imports: [RouterModule], - declarations: [I18NRouterPipe], - exports: [ - I18NRouterPipe, - RouterModule - ] + imports: [RouterModule], + declarations: [I18NRouterPipe], + exports: [ + I18NRouterPipe, + RouterModule + ] }) export class I18NRouterModule { - static forRoot(routes: Routes, - configuredProviders: Array = [{ - provide: I18NRouterLoader, - useFactory: (i18nRouterFactory), - deps: [RAW_ROUTES] - }]): ModuleWithProviders { - return { - ngModule: I18NRouterModule, - providers: [ - ...configuredProviders, - { - provide: APP_INITIALIZER, - useFactory: (initializerFactory), - deps: [I18NRouterLoader], - multi: true - }, - { - provide: RAW_ROUTES, - useValue: routes - }, - { - provide: I18N_ROUTER_FORROOT_GUARD, - useFactory: (provideForRootGuard), - deps: [[Router, new Optional(), new SkipSelf()]] - } - ] - }; - } + static forRoot(routes: Routes, + configuredProviders: Array = [{ + provide: I18NRouterLoader, + useFactory: (i18nRouterFactory), + deps: [RAW_ROUTES] + }]): ModuleWithProviders { + return { + ngModule: I18NRouterModule, + providers: [ + ...configuredProviders, + { + provide: APP_INITIALIZER, + useFactory: (initializerFactory), + deps: [I18NRouterLoader], + multi: true + }, + { + provide: RAW_ROUTES, + useValue: routes + }, + { + provide: I18N_ROUTER_FORROOT_GUARD, + useFactory: (provideForRootGuard), + deps: [[Router, new Optional(), new SkipSelf()]] + } + ] + }; + } - static forChild(routes: Routes, moduleKey: string): ModuleWithProviders { - return { - ngModule: I18NRouterModule, - providers: [ - provideRoutes([]), - { - provide: RAW_ROUTES, - useValue: routes - }, - { - provide: MODULE_KEY, - useValue: moduleKey - }, - { - provide: ROUTES, - useFactory: (provideChildRoutes), - deps: [I18NRouterService, RAW_ROUTES, MODULE_KEY], - multi: true - }, - { - provide: ANALYZE_FOR_ENTRY_COMPONENTS, - useValue: routes, - multi: true - } - ] - }; - } + static forChild(routes: Routes, moduleKey: string): ModuleWithProviders { + return { + ngModule: I18NRouterModule, + providers: [ + provideRoutes([]), + { + provide: RAW_ROUTES, + useValue: routes + }, + { + provide: MODULE_KEY, + useValue: moduleKey + }, + { + provide: ROUTES, + useFactory: (provideChildRoutes), + deps: [I18NRouterService, RAW_ROUTES, MODULE_KEY], + multi: true + }, + { + provide: ANALYZE_FOR_ENTRY_COMPONENTS, + useValue: routes, + multi: true + } + ] + }; + } - constructor(@Optional() @Inject(I18N_ROUTER_FORROOT_GUARD) guard: any) {} + constructor(@Optional() @Inject(I18N_ROUTER_FORROOT_GUARD) guard: any) { + } } export function provideForRootGuard(router: Router): any { - if (router) - throw new Error( - 'I18NRouterModule.forRoot() called twice. Child modules should use I18NRouterModule.forChild() instead.'); + if (router) + throw new Error( + 'I18NRouterModule.forRoot() called twice. Child modules should use I18NRouterModule.forChild() instead.'); - return 'guarded'; + return 'guarded'; } diff --git a/package.json b/package.json index 4bd91dd..4ca429d 100644 --- a/package.json +++ b/package.json @@ -44,20 +44,20 @@ "@angular/platform-browser-dynamic": "~2.4.0", "@angular/router": "~3.4.0", "core-js": "^2.4.1", - "rxjs": "^5.1.1", - "zone.js": "^0.7.7", + "rxjs": "^5.2.0", + "zone.js": "^0.8.1", "lodash": "^4.17.4", - "@types/node": "^7.0.5", - "@types/jasmine": "^2.5.43", - "@types/lodash": "4.14.44", + "@types/node": "^7.0.8", + "@types/jasmine": "^2.5.45", + "@types/lodash": "4.14.55", "gulp": "gulpjs/gulp#4.0", "gulp-load-plugins": "^1.5.0", - "rimraf": "^2.6.0", + "rimraf": "^2.6.1", "gulp-exec": "^2.1.3", "gulp-tslint": "^7.1.0", "webpack": "^2.2.1", - "tslint-loader": "^3.4.2", - "awesome-typescript-loader": "^3.0.5", + "tslint-loader": "^3.4.3", + "awesome-typescript-loader": "^3.1.2", "jasmine-core": "^2.5.2", "karma": "^1.4.1", "karma-jasmine": "^1.1.0", @@ -67,16 +67,16 @@ "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^2.0.2", "karma-chrome-launcher": "^2.0.0", - "source-map-loader": "^0.1.6", + "source-map-loader": "^0.2.0", "istanbul-instrumenter-loader": "0.2.0", - "codelyzer": "^2.0.1", - "tslint": "^4.4.2", + "codelyzer": "^3.0.0-beta.3", + "tslint": "^4.5.1", "typescript": "~2.1.6" }, "peerDependencies": { - "@angular/core": "^2.4.0 || >=4.0.0-beta.0", - "@angular/router": "^3.4.0 || >=4.0.0-beta.0", - "@angular/http": "^2.4.0 || >=4.0.0-beta.0", + "@angular/core": "^2.0.0 || >=4.0.0-beta.0", + "@angular/router": "^3.0.0 || >=4.0.0-beta.0", + "@angular/http": "^2.0.0 || >=4.0.0-beta.0", "rxjs": "^5.0.3" } } diff --git a/src/i18n-router.loader.ts b/src/i18n-router.loader.ts index 5547e85..6eeb4fc 100644 --- a/src/i18n-router.loader.ts +++ b/src/i18n-router.loader.ts @@ -8,48 +8,52 @@ import 'rxjs/add/operator/toPromise'; import * as _ from 'lodash'; export abstract class I18NRouterLoader { - abstract loadTranslations(): any; - abstract getRoutes(): Routes; - abstract getTranslations(): any; + abstract loadTranslations(): any; + + abstract getRoutes(): Routes; + + abstract getTranslations(): any; } export class I18NRouterStaticLoader implements I18NRouterLoader { - constructor(private readonly routes?: Routes, - private readonly translations?: any) {} + constructor(private readonly routes?: Routes, + private readonly translations?: any) { + } - loadTranslations(): any { - return Promise.resolve(this.translations); - } + loadTranslations(): any { + return Promise.resolve(this.translations); + } - getRoutes(): Routes { - return _.map(this.routes, _.cloneDeep); - } + getRoutes(): Routes { + return _.map(this.routes, _.cloneDeep); + } - getTranslations(): any { - return this.translations; - } + getTranslations(): any { + return this.translations; + } } export class I18NRouterHttpLoader implements I18NRouterLoader { - private translations: any; - - constructor(private readonly http: Http, - private readonly routes?: Routes, - private readonly path: string = '/routes.json') {} - - loadTranslations(): any { - return this.http.get(this.path) - .map((res: any) => res.json()) - .toPromise() - .then((translations: any) => this.translations = translations) - .catch(() => Promise.reject('Endpoint unreachable!')); - } - - getRoutes(): Routes { - return _.map(this.routes, _.cloneDeep); - } - - getTranslations(): any { - return this.translations; - } + private translations: any; + + constructor(private readonly http: Http, + private readonly routes?: Routes, + private readonly path: string = '/routes.json') { + } + + loadTranslations(): any { + return this.http.get(this.path) + .map((res: any) => res.json()) + .toPromise() + .then((translations: any) => this.translations = translations) + .catch(() => Promise.reject('Endpoint unreachable!')); + } + + getRoutes(): Routes { + return _.map(this.routes, _.cloneDeep); + } + + getTranslations(): any { + return this.translations; + } } diff --git a/src/i18n-router.pipe.ts b/src/i18n-router.pipe.ts index b12cfb0..8fd1a91 100644 --- a/src/i18n-router.pipe.ts +++ b/src/i18n-router.pipe.ts @@ -7,56 +7,57 @@ import { I18NRouterService, ROOT_ROUTE_PREFIX } from './i18n-router.service'; @Injectable() @Pipe({ - name: 'i18nRouter', - pure: false // required to update the value when the promise is resolved + name: 'i18nRouter', + pure: false // required to update the value when the promise is resolved }) export class I18NRouterPipe implements PipeTransform { - constructor(private readonly router: Router, - private readonly i18nRouter: I18NRouterService) {} + constructor(private readonly router: Router, + private readonly i18nRouter: I18NRouterService) { + } - transform(query: string | Array): string { - if (typeof query === 'string' && !!query && query.length) - throw new Error('Query must be an empty string or an array!'); + transform(query: string | Array): string { + if (typeof query === 'string' && !!query && query.length) + throw new Error('Query must be an empty string or an array!'); - if (!this.i18nRouter.languageCode || !this.i18nRouter.useLocalizedRoutes) - return `/${typeof query === 'string' ? query : query.join('/')}`; + if (!this.i18nRouter.languageCode || !this.i18nRouter.useLocalizedRoutes) + return `/${typeof query === 'string' ? query : query.join('/')}`; - if (!query || query.length === 0) - return `/${this.i18nRouter.languageCode}`; + if (!query || query.length === 0) + return `/${this.i18nRouter.languageCode}`; - return `/${this.translateQuery(query)}`; - } + return `/${this.translateQuery(query)}`; + } - private translateQuery(key: string | Array): string { - const translateBatch: Array = []; - let batchKey = ''; + private translateQuery(key: string | Array): string { + const translateBatch: Array = []; + let batchKey = ''; - (key as Array).forEach((segment: any, index: number) => { - if (typeof segment === 'string') { - let prefix = ''; + (key as Array).forEach((segment: any, index: number) => { + if (typeof segment === 'string') { + let prefix = ''; - let currentKey = `${ROOT_ROUTE_PREFIX}.${segment}`; + let currentKey = `${ROOT_ROUTE_PREFIX}.${segment}`; - if (index === 0) { - prefix = this.i18nRouter.getTranslation(currentKey); + if (index === 0) { + prefix = this.i18nRouter.getTranslation(currentKey); - if (!!prefix) { - batchKey = currentKey; - translateBatch.push(this.i18nRouter.languageCode); - } - } + if (!!prefix) { + batchKey = currentKey; + translateBatch.push(this.i18nRouter.languageCode); + } + } - currentKey = index === 0 ? (!!prefix ? batchKey : segment) : `${batchKey}.${segment}`; - const translatedSegment = this.i18nRouter.getTranslation(currentKey); + currentKey = index === 0 ? (!!prefix ? batchKey : segment) : `${batchKey}.${segment}`; + const translatedSegment = this.i18nRouter.getTranslation(currentKey); - if (!!translatedSegment) - batchKey = currentKey; + if (!!translatedSegment) + batchKey = currentKey; - translateBatch.push(translatedSegment || segment); - } else - translateBatch.push(segment); - }); + translateBatch.push(translatedSegment || segment); + } else + translateBatch.push(segment); + }); - return translateBatch.join('/'); - } + return translateBatch.join('/'); + } } diff --git a/src/i18n-router.service.ts b/src/i18n-router.service.ts index e835ebe..4323a44 100644 --- a/src/i18n-router.service.ts +++ b/src/i18n-router.service.ts @@ -12,148 +12,150 @@ export const ROOT_ROUTE_PREFIX = 'ROOT'; @Injectable() export class I18NRouterService { - languageCode: string; - useLocalizedRoutes: boolean; - - private readonly routes: Routes; - private translations: any; - - constructor(public loader: I18NRouterLoader, - private readonly router: Router) { - this.routes = _.map(this.loader.getRoutes(), _.cloneDeep); + languageCode: string; + useLocalizedRoutes: boolean; + + private readonly routes: Routes; + private translations: any; + + constructor(public loader: I18NRouterLoader, + private readonly router: Router) { + this.routes = _.map(this.loader.getRoutes(), _.cloneDeep); + } + + init(useLocalizedRoutes: boolean = true): void { + // don't use i18n-router unless allowed + if (!useLocalizedRoutes) + return; + + this.useLocalizedRoutes = true; + this.translations = this.loader.getTranslations(); + } + + changeLanguage(languageCode: string): void { + // don't translate routes unless allowed + if (!this.useLocalizedRoutes) + return; + + // don't translate routes unless got translations + if (!this.translations[languageCode]) + return; + + this.languageCode = languageCode; + + const rawRoutes = this.loader.getRoutes(); + const i18nRoot = _.find(rawRoutes, (route) => (!!route.data && !!(route.data).i18n) && (route.data).i18n.isRoot); + + let routes: Routes = []; + + // translate routes + if (!!i18nRoot) { + const rootPath = this.translateRoute(i18nRoot, 'path').path || i18nRoot.path; + routes = [{ + path: '', + redirectTo: this.interpolateRoute(rootPath), + pathMatch: 'full' + }]; } - init(useLocalizedRoutes: boolean = true): void { - // don't use i18n-router unless allowed - if (!useLocalizedRoutes) - return; - - this.useLocalizedRoutes = true; - this.translations = this.loader.getTranslations(); - } + const translatedRoutes = this.translateRoutes(rawRoutes); + routes = routes.concat(translatedRoutes); - changeLanguage(languageCode: string): void { - // don't translate routes unless allowed - if (!this.useLocalizedRoutes) - return; + this.router.resetConfig(routes); + } - // don't translate routes unless got translations - if (!this.translations[languageCode]) - return; + getTranslation(key: string): string { + key = key.replace(/-/, '_'); - this.languageCode = languageCode; + if (!this.translations[this.languageCode][key.toUpperCase()]) + return undefined; - const rawRoutes = this.loader.getRoutes(); - const i18nRoot = _.find(rawRoutes, (route) => (!!route.data && !!(route.data).i18n) && !!(route.data).i18n.isRoot); + return this.translations[this.languageCode][key.toUpperCase()]; + } - let routes: Routes = []; + translateRoutes(routes: Routes, moduleKey: string = ''): Routes { + const translatedRoutes: Array = []; - // translate routes - if (!!i18nRoot) { - const rootPath = this.translateRoute(i18nRoot, 'path').path || i18nRoot.path; - routes = [{ path: '', redirectTo: this.interpolateRoute(rootPath), pathMatch: 'full' }]; + routes.forEach((route: Route) => { + if (_.isArray(route.children)) { + if ((!!route.data && !!(route.data).i18n) && (route.data).i18n.isRoot) + route.path = this.interpolateRoute(route.path); + else { + if (!!route.path) + route = this.translateRoute(route, 'path', moduleKey); } - const translatedRoutes = this.translateRoutes(rawRoutes); - routes = routes.concat(translatedRoutes); + route.children = this.translateRoutes(route.children, moduleKey); + } + else if (!moduleKey && route.path === '**') + route.redirectTo = this.interpolateRoute(route.redirectTo); + else { + if (!!route.path) + route = this.translateRoute(route, 'path', moduleKey); - this.router.resetConfig(routes); - } - - getTranslation(key: string): string { - key = key.replace(/-/, '_'); + if (!!route.redirectTo) + route = this.translateRoute(route, 'redirectTo', moduleKey); + } - if (!this.translations[this.languageCode][key.toUpperCase()]) - return undefined; - - return this.translations[this.languageCode][key.toUpperCase()]; - } + translatedRoutes.push(route); + }); - translateRoutes(routes: Routes, moduleKey: string = ''): Routes { - const translatedRoutes: Array = []; - - routes.forEach((route: Route) => { - if (_.isArray(route.children)) { - if ((!!route.data && !!(route.data).i18n) && !!(route.data).i18n.isRoot) - route.path = this.interpolateRoute(route.path); - else { - if (!!route.path) - route = this.translateRoute(route, 'path', moduleKey); - } - - route.children = this.translateRoutes(route.children, moduleKey); - } - else if (!moduleKey && route.path === '**') - route.redirectTo = this.interpolateRoute(route.redirectTo); - else { - if (!!route.path) - route = this.translateRoute(route, 'path', moduleKey); - - if (!!route.redirectTo) - route = this.translateRoute(route, 'redirectTo', moduleKey); - } - - translatedRoutes.push(route); - }); - - return translatedRoutes; - } + return translatedRoutes; + } - private interpolateRoute(path: string): string { - if (!path || path.length === 0) - return this.languageCode; + private interpolateRoute(path: string): string { + if (!path || path.length === 0) + return this.languageCode; - path = _.filter(path.split('/'), (segment: string) => !!segment).join('/'); + path = _.filter(path.split('/'), (segment: string) => !!segment).join('/'); - return `${this.languageCode}/${path}`; - } + return `${this.languageCode}/${path}`; + } - private translateRoute(route: Route, property: string, moduleKey: string = ''): Route { - const translateBatch: Array = []; - let batchKey = ''; + private translateRoute(route: Route, property: string, moduleKey: string = ''): Route { + const translateBatch: Array = []; + let batchKey = ''; - const key = _.filter(route[property].split('/'), (segment) => !!segment); - const isRedirection = property === 'redirectTo' && _.startsWith(route[property], '/'); + const key = _.filter(route[property].split('/'), (segment) => !!segment); + const isRedirection = property === 'redirectTo' && _.startsWith(route[property], '/'); - (key as Array).forEach((segment: any, index: number) => { - let prefix = ''; + (key as Array).forEach((segment: any, index: number) => { + let prefix = ''; - let currentKey = `${ROOT_ROUTE_PREFIX}.${!!moduleKey && !isRedirection ? `${moduleKey}.` : ''}${segment}`; + let currentKey = `${ROOT_ROUTE_PREFIX}.${!!moduleKey && !isRedirection ? `${moduleKey}.` : ''}${segment}`; - if (index === 0) { - prefix = this.getTranslation(currentKey); + if (index === 0) { + prefix = this.getTranslation(currentKey); - if (!!prefix) { - if (isRedirection) - translateBatch.push(this.languageCode); + if (!!prefix) { + if (isRedirection) + translateBatch.push(this.languageCode); - batchKey = currentKey; - } - } + batchKey = currentKey; + } + } - currentKey = index === 0 ? (!!prefix ? batchKey : segment) : `${batchKey}.${segment}`; - const translatedSegment = !_.startsWith(segment, ':') ? this.getTranslation(currentKey) : ''; + currentKey = index === 0 ? (!!prefix ? batchKey : segment) : `${batchKey}.${segment}`; + const translatedSegment = !_.startsWith(segment, ':') ? this.getTranslation(currentKey) : ''; - if (!!translatedSegment) - batchKey = currentKey; + if (!!translatedSegment) + batchKey = currentKey; - translateBatch.push(translatedSegment || segment); - }); + translateBatch.push(translatedSegment || segment); + }); - route[property] = translateBatch.join('/'); + route[property] = translateBatch.join('/'); - if (isRedirection) - route[property] = `/${route[property]}`; + if (isRedirection) + route[property] = `/${route[property]}`; - return route; - } + return route; + } } export function provideChildRoutes(i18nRouter: I18NRouterService, routes: Routes, moduleKey: string): Routes { - if (!i18nRouter.useLocalizedRoutes) - return routes; - - const translatedRoutes = i18nRouter.translateRoutes(routes, moduleKey); + if (!i18nRouter.useLocalizedRoutes) + return routes; - return translatedRoutes; + return i18nRouter.translateRoutes(routes, moduleKey); } diff --git a/tests/i18n-router.loader.spec.ts b/tests/i18n-router.loader.spec.ts index 7e4d60e..5e0dc5a 100644 --- a/tests/i18n-router.loader.spec.ts +++ b/tests/i18n-router.loader.spec.ts @@ -12,122 +12,139 @@ import { I18NRouterLoader, I18NRouterStaticLoader, I18NRouterHttpLoader, I18NRou import { testRoutes, testTranslations, testModuleConfig } from './index.spec'; const mockBackendResponse = (connection: MockConnection, response: any) => { - connection.mockRespond(new Response(new ResponseOptions({ body: response }))); + connection.mockRespond(new Response(new ResponseOptions({body: response}))); }; const mockBackendError = (connection: MockConnection, error: string) => { - connection.mockError(new Error(error)); + connection.mockError(new Error(error)); }; describe('@nglibs/i18n-router:', - () => { - beforeEach(() => { - const i18nRouterFactory = () => new I18NRouterStaticLoader(testRoutes, testTranslations); - - testModuleConfig(testRoutes, [{ provide: I18NRouterLoader, useFactory: (i18nRouterFactory) }]); - }); - - describe('I18NRouterLoader', - () => { - it('should not return any routes & translations unless provided', - () => { - const loader = new I18NRouterStaticLoader(); - const loadedRoutes = loader.getRoutes(); - const loadedTranslations = loader.getTranslations(); - - expect(loadedRoutes).toEqual([]); - expect(loadedTranslations).toBeUndefined(); - }); - - it('should be able to provide `I18NRouterStaticLoader`', - () => { - const i18nRouterFactory = () => new I18NRouterStaticLoader(testRoutes, testTranslations); - - testModuleConfig(testRoutes, [{ provide: I18NRouterLoader, useFactory: (i18nRouterFactory) }]); - - const injector = getTestBed(); - const i18nRouter = injector.get(I18NRouterService); - - expect(I18NRouterStaticLoader).toBeDefined(); - expect(i18nRouter.loader).toBeDefined(); - expect(i18nRouter.loader instanceof I18NRouterStaticLoader).toBeTruthy(); - }); - - it('should be able to provide `I18NRouterHttpLoader`', - () => { - const i18nRouterFactory = (http: Http) => new I18NRouterHttpLoader(http); - - testModuleConfig(testRoutes, [{ provide: I18NRouterLoader, useFactory: (i18nRouterFactory), deps: [Http] }]); + () => { + beforeEach(() => { + const i18nRouterFactory = () => new I18NRouterStaticLoader(testRoutes, testTranslations); + + testModuleConfig(testRoutes, [{ + provide: I18NRouterLoader, + useFactory: (i18nRouterFactory) + }]); + }); - const injector = getTestBed(); - const i18nRouter = injector.get(I18NRouterService); + describe('I18NRouterLoader', + () => { + it('should not return any routes & translations unless provided', + () => { + const loader = new I18NRouterStaticLoader(); + const loadedRoutes = loader.getRoutes(); + const loadedTranslations = loader.getTranslations(); - expect(I18NRouterHttpLoader).toBeDefined(); - expect(i18nRouter.loader).toBeDefined(); - expect(i18nRouter.loader instanceof I18NRouterHttpLoader).toBeTruthy(); - }); + expect(loadedRoutes).toEqual([]); + expect(loadedTranslations).toBeUndefined(); + }); - it('should be able to provide any `I18NRouterLoader`', - () => { - class CustomLoader implements I18NRouterLoader { - loadTranslations(): any { - return Promise.resolve({}); - } + it('should be able to provide `I18NRouterStaticLoader`', + () => { + const i18nRouterFactory = () => new I18NRouterStaticLoader(testRoutes, testTranslations); - getRoutes(): Routes { - return _.map(testRoutes, _.cloneDeep); - } + testModuleConfig(testRoutes, [{ + provide: I18NRouterLoader, + useFactory: (i18nRouterFactory) + }]); + + const injector = getTestBed(); + const i18nRouter = injector.get(I18NRouterService); + + expect(I18NRouterStaticLoader).toBeDefined(); + expect(i18nRouter.loader).toBeDefined(); + expect(i18nRouter.loader instanceof I18NRouterStaticLoader).toBeTruthy(); + }); + + it('should be able to provide `I18NRouterHttpLoader`', + () => { + const i18nRouterFactory = (http: Http) => new I18NRouterHttpLoader(http); + + testModuleConfig(testRoutes, [{ + provide: I18NRouterLoader, + useFactory: (i18nRouterFactory), + deps: [Http] + }]); + + const injector = getTestBed(); + const i18nRouter = injector.get(I18NRouterService); + + expect(I18NRouterHttpLoader).toBeDefined(); + expect(i18nRouter.loader).toBeDefined(); + expect(i18nRouter.loader instanceof I18NRouterHttpLoader).toBeTruthy(); + }); + + it('should be able to provide any `I18NRouterLoader`', + () => { + class CustomLoader implements I18NRouterLoader { + loadTranslations(): any { + return Promise.resolve({}); + } + + getRoutes(): Routes { + return _.map(testRoutes, _.cloneDeep); + } + + getTranslations(): any { + return testTranslations; + } + } + + testModuleConfig(testRoutes, [{ + provide: I18NRouterLoader, + useClass: CustomLoader + }]); + + const injector = getTestBed(); + const i18nRouter = injector.get(I18NRouterService); + + expect(CustomLoader).toBeDefined(); + expect(i18nRouter.loader).toBeDefined(); + expect(i18nRouter.loader instanceof CustomLoader).toBeTruthy(); + }); + }); + + describe('I18NRouterHttpLoader', + () => { + beforeEach(() => { + const i18nRouterFactory = (http: Http) => new I18NRouterHttpLoader(http, testRoutes, '/api/get-routes'); - getTranslations(): any { - return testTranslations; - } - } + testModuleConfig(testRoutes, [{ + provide: I18NRouterLoader, + useFactory: (i18nRouterFactory), + deps: [Http] + }]); + }); - testModuleConfig(testRoutes, [{ provide: I18NRouterLoader, useClass: CustomLoader }]); + it('should be able to retrieve route translations from the specified `path`', + async(inject([MockBackend, I18NRouterService], + (backend: MockBackend, i18nRouter: I18NRouterService) => { + // mock response + backend.connections.subscribe((c: MockConnection) => mockBackendResponse(c, testTranslations)); - const injector = getTestBed(); - const i18nRouter = injector.get(I18NRouterService); + i18nRouter.loader.loadTranslations() + .then((res: any) => { + expect(res).toEqual(testTranslations); + }); + }))); - expect(CustomLoader).toBeDefined(); - expect(i18nRouter.loader).toBeDefined(); - expect(i18nRouter.loader instanceof CustomLoader).toBeTruthy(); - }); - }); + it('should throw w/o a valid `path`', + async(inject([MockBackend, I18NRouterService], + (backend: MockBackend, i18nRouter: I18NRouterService) => { + // mock error + backend.connections.subscribe((c: MockConnection) => mockBackendError(c, '500')); - describe('I18NRouterHttpLoader', - () => { - beforeEach(() => { - const i18nRouterFactory = (http: Http) => new I18NRouterHttpLoader(http, testRoutes, '/api/get-routes'); + // this will produce error at the backend + i18nRouter.loader.loadTranslations() + .catch((res: any) => { + expect(res).toEqual('Endpoint unreachable!'); - testModuleConfig(testRoutes, [{ provide: I18NRouterLoader, useFactory: (i18nRouterFactory), deps: [Http] }]); + const loadedTranslations = i18nRouter.loader.getTranslations(); + expect(loadedTranslations).toBeUndefined(); }); - - it('should be able to retrieve route translations from the specified `path`', - async(inject([MockBackend, I18NRouterService], - (backend: MockBackend, i18nRouter: I18NRouterService) => { - // mock response - backend.connections.subscribe((c: MockConnection) => mockBackendResponse(c, testTranslations)); - - i18nRouter.loader.loadTranslations() - .then((res: any) => { - expect(res).toEqual(testTranslations); - }); - }))); - - it('should throw w/o a valid `path`', - async(inject([MockBackend, I18NRouterService], - (backend: MockBackend, i18nRouter: I18NRouterService) => { - // mock error - backend.connections.subscribe((c: MockConnection) => mockBackendError(c, '500')); - - // this will produce error at the backend - i18nRouter.loader.loadTranslations() - .catch((res: any) => { - expect(res).toEqual('Endpoint unreachable!'); - - const loadedTranslations = i18nRouter.loader.getTranslations(); - expect(loadedTranslations).toBeUndefined(); - }); - }))); - }); - }); + }))); + }); + }); diff --git a/tests/i18n-router.pipe.spec.ts b/tests/i18n-router.pipe.spec.ts index 46e6c15..2c12a62 100644 --- a/tests/i18n-router.pipe.spec.ts +++ b/tests/i18n-router.pipe.spec.ts @@ -7,116 +7,119 @@ import { I18NRouterLoader, I18NRouterStaticLoader, I18NRouterPipe, I18NRouterSer import { testRoutes, testTranslations, testModuleConfig } from './index.spec'; describe('@nglibs/i18n-router:', - () => { - beforeEach(() => { - const i18nRouterFactory = () => new I18NRouterStaticLoader(testRoutes, testTranslations); - - testModuleConfig(testRoutes, [{ provide: I18NRouterLoader, useFactory: (i18nRouterFactory) }]); - }); + () => { + beforeEach(() => { + const i18nRouterFactory = () => new I18NRouterStaticLoader(testRoutes, testTranslations); + + testModuleConfig(testRoutes, [{ + provide: I18NRouterLoader, + useFactory: (i18nRouterFactory) + }]); + }); - describe('I18NRouterPipe', - () => { - it('is defined', - inject([Router, I18NRouterService], - (router: Router, i18nRouter: I18NRouterService) => { - const pipe = new I18NRouterPipe(router, i18nRouter); - - expect(I18NRouterPipe).toBeDefined(); - expect(pipe).toBeDefined(); - expect(pipe instanceof I18NRouterPipe).toBeTruthy(); - })); - - it('should not translate routes w/o default initialization', - inject([Router, I18NRouterService], - (router: Router, i18nRouter: I18NRouterService) => { - i18nRouter.init(false); - i18nRouter.changeLanguage('tr'); - - const pipe = new I18NRouterPipe(router, i18nRouter); - - let translatedPath = pipe.transform(''); - expect(translatedPath).toEqual('/'); - - translatedPath = pipe.transform(['about', 'us']); - expect(translatedPath).toEqual('/about/us'); - })); - - it('should not translate routes w/o translations', - inject([Router, I18NRouterService], - (router: Router, i18nRouter: I18NRouterService) => { - i18nRouter.init(); - i18nRouter.changeLanguage('fr'); - - const pipe = new I18NRouterPipe(router, i18nRouter); - - let translatedPath = pipe.transform(''); - expect(translatedPath).toEqual('/'); - - translatedPath = pipe.transform(['about', 'us']); - expect(translatedPath).toEqual('/about/us'); - })); - - it('should throw if you provide `non-empty` string query', - inject([Router, I18NRouterService], - (router: Router, i18nRouter: I18NRouterService) => { - i18nRouter.init(); - i18nRouter.changeLanguage('en'); - - const pipe = new I18NRouterPipe(router, i18nRouter); - - expect(() => pipe.transform('about')) - .toThrowError('Query must be an empty string or an array!'); - })); - - it('should be able to partly translate routes w/missing translations', - inject([Router, I18NRouterService], - (router: Router, i18nRouter: I18NRouterService) => { - i18nRouter.init(); - i18nRouter.changeLanguage('tr'); - - const pipe = new I18NRouterPipe(router, i18nRouter); - - const translatedPath = pipe.transform(['about', 'banana']); - expect(translatedPath).toEqual('/tr/hakkinda/banana'); - })); - - it('should be able to translate the `i18n-root` route', - inject([Router, I18NRouterService], - (router: Router, i18nRouter: I18NRouterService) => { - i18nRouter.init(); - i18nRouter.changeLanguage('en'); - - const pipe = new I18NRouterPipe(router, i18nRouter); - - const translatedPath = pipe.transform(''); - expect(translatedPath).toEqual('/en'); - })); - - it('should be able to translate a route inside the `i18n-root`', - inject([Router, I18NRouterService], - (router: Router, i18nRouter: I18NRouterService) => { - i18nRouter.init(); - i18nRouter.changeLanguage('tr'); - - const pipe = new I18NRouterPipe(router, i18nRouter); - - let translatedPath = pipe.transform(['about', 'us']); - expect(translatedPath).toEqual('/tr/hakkinda/biz'); - - translatedPath = pipe.transform(['about', 'apple', 1, 'pear']); - expect(translatedPath).toEqual('/tr/hakkinda/elma/1/armut'); - })); - - it('should be able to translate a route outside the `i18n-root`', - inject([Router, I18NRouterService], - (router: Router, i18nRouter: I18NRouterService) => { - i18nRouter.init(); - i18nRouter.changeLanguage('tr'); + describe('I18NRouterPipe', + () => { + it('is defined', + inject([Router, I18NRouterService], + (router: Router, i18nRouter: I18NRouterService) => { + const pipe = new I18NRouterPipe(router, i18nRouter); - const pipe = new I18NRouterPipe(router, i18nRouter); - - const translatedPath = pipe.transform(['change-language', 'en']); - expect(translatedPath).toEqual('/dil-secimi/en'); - })); - }); - }); + expect(I18NRouterPipe).toBeDefined(); + expect(pipe).toBeDefined(); + expect(pipe instanceof I18NRouterPipe).toBeTruthy(); + })); + + it('should not translate routes w/o default initialization', + inject([Router, I18NRouterService], + (router: Router, i18nRouter: I18NRouterService) => { + i18nRouter.init(false); + i18nRouter.changeLanguage('tr'); + + const pipe = new I18NRouterPipe(router, i18nRouter); + + let translatedPath = pipe.transform(''); + expect(translatedPath).toEqual('/'); + + translatedPath = pipe.transform(['about', 'us']); + expect(translatedPath).toEqual('/about/us'); + })); + + it('should not translate routes w/o translations', + inject([Router, I18NRouterService], + (router: Router, i18nRouter: I18NRouterService) => { + i18nRouter.init(); + i18nRouter.changeLanguage('fr'); + + const pipe = new I18NRouterPipe(router, i18nRouter); + + let translatedPath = pipe.transform(''); + expect(translatedPath).toEqual('/'); + + translatedPath = pipe.transform(['about', 'us']); + expect(translatedPath).toEqual('/about/us'); + })); + + it('should throw if you provide `non-empty` string query', + inject([Router, I18NRouterService], + (router: Router, i18nRouter: I18NRouterService) => { + i18nRouter.init(); + i18nRouter.changeLanguage('en'); + + const pipe = new I18NRouterPipe(router, i18nRouter); + + expect(() => pipe.transform('about')) + .toThrowError('Query must be an empty string or an array!'); + })); + + it('should be able to partly translate routes w/missing translations', + inject([Router, I18NRouterService], + (router: Router, i18nRouter: I18NRouterService) => { + i18nRouter.init(); + i18nRouter.changeLanguage('tr'); + + const pipe = new I18NRouterPipe(router, i18nRouter); + + const translatedPath = pipe.transform(['about', 'banana']); + expect(translatedPath).toEqual('/tr/hakkinda/banana'); + })); + + it('should be able to translate the `i18n-root` route', + inject([Router, I18NRouterService], + (router: Router, i18nRouter: I18NRouterService) => { + i18nRouter.init(); + i18nRouter.changeLanguage('en'); + + const pipe = new I18NRouterPipe(router, i18nRouter); + + const translatedPath = pipe.transform(''); + expect(translatedPath).toEqual('/en'); + })); + + it('should be able to translate a route inside the `i18n-root`', + inject([Router, I18NRouterService], + (router: Router, i18nRouter: I18NRouterService) => { + i18nRouter.init(); + i18nRouter.changeLanguage('tr'); + + const pipe = new I18NRouterPipe(router, i18nRouter); + + let translatedPath = pipe.transform(['about', 'us']); + expect(translatedPath).toEqual('/tr/hakkinda/biz'); + + translatedPath = pipe.transform(['about', 'apple', 1, 'pear']); + expect(translatedPath).toEqual('/tr/hakkinda/elma/1/armut'); + })); + + it('should be able to translate a route outside the `i18n-root`', + inject([Router, I18NRouterService], + (router: Router, i18nRouter: I18NRouterService) => { + i18nRouter.init(); + i18nRouter.changeLanguage('tr'); + + const pipe = new I18NRouterPipe(router, i18nRouter); + + const translatedPath = pipe.transform(['change-language', 'en']); + expect(translatedPath).toEqual('/dil-secimi/en'); + })); + }); + }); diff --git a/tests/i18n-router.service.spec.ts b/tests/i18n-router.service.spec.ts index b0ac72e..cf9066f 100644 --- a/tests/i18n-router.service.spec.ts +++ b/tests/i18n-router.service.spec.ts @@ -7,259 +7,268 @@ import { I18NRouterLoader, I18NRouterStaticLoader, I18NRouterService } from '../ import { TestBootstrapComponent, TestComponent, testRoutes, testTranslations, testModuleConfig } from './index.spec'; describe('@nglibs/i18n-router:', - () => { - beforeEach(() => { - const i18nRouterFactory = () => new I18NRouterStaticLoader(testRoutes, testTranslations); - - testModuleConfig(testRoutes, [{ provide: I18NRouterLoader, useFactory: (i18nRouterFactory) }]); - }); - - describe('I18NRouterService', - () => { - it('is defined', - inject([I18NRouterService], - (i18nRouter: I18NRouterService) => { - expect(I18NRouterService).toBeDefined(); - expect(i18nRouter).toBeDefined(); - expect(i18nRouter instanceof I18NRouterService).toBeTruthy(); - })); - - it('should not translate routes w/o default initialization', - inject([I18NRouterService], - (i18nRouter: I18NRouterService) => { - const injector = getTestBed(); - const router = injector.get(Router); - - spyOn(router, 'resetConfig').and.callThrough(); - - i18nRouter.init(false); - i18nRouter.changeLanguage('en'); - - expect(router.resetConfig).not.toHaveBeenCalled(); - expect(router.config).toEqual(testRoutes); - - const fixture = TestBed.createComponent(TestBootstrapComponent); - fixture.detectChanges(); - - // initial navigation - router.navigate(['/']) - .then(() => { - expect(router.url).toEqual('/'); - }); - })); - - it('should not translate routes w/o translations', - inject([Router, I18NRouterService], - (router: Router, i18nRouter: I18NRouterService) => { - spyOn(router, 'resetConfig').and.callThrough(); - - i18nRouter.init(); - i18nRouter.changeLanguage('fr'); - - expect(router.resetConfig).not.toHaveBeenCalled(); - expect(router.config).toEqual(testRoutes); - })); - - it('should be able to `partly` translate routes w/missing translations', - fakeAsync(inject([I18NRouterService], - (i18nRouter: I18NRouterService) => { - const injector = getTestBed(); - const router = injector.get(Router); - - i18nRouter.init(); - i18nRouter.changeLanguage('tr'); - - const fixture = TestBed.createComponent(TestBootstrapComponent); - fixture.detectChanges(); - - // navigate to /about/banana - router.navigate(['/tr/hakkinda/banana']) - .then(() => { - expect(router.url).toEqual('/tr/hakkinda/banana'); - }); - }))); - - it('should be able to `catchall` redirect to `i18n-root`', - fakeAsync(inject([I18NRouterService], - (i18nRouter: I18NRouterService) => { - const injector = getTestBed(); - const router = injector.get(Router); - - i18nRouter.init(); - i18nRouter.changeLanguage('en'); - - const fixture = TestBed.createComponent(TestBootstrapComponent); - fixture.detectChanges(); - - // catchall navigation - router.navigate(['/about']) - .then(() => { - expect(router.url).toEqual('/en'); - }); - }))); - - it('should be able to translate `path` property of routes', - fakeAsync(inject([I18NRouterService], - (i18nRouter: I18NRouterService) => { - const injector = getTestBed(); - const router = injector.get(Router); - - i18nRouter.init(); - i18nRouter.changeLanguage('tr'); - - const fixture = TestBed.createComponent(TestBootstrapComponent); - fixture.detectChanges(); - - // navigate to /about - router.navigate(['/tr/hakkinda']) - .then(() => { - expect(router.url).toEqual('/tr/hakkinda'); - }); - }))); - - it('should be able to translate `redirectTo` property of routes', - fakeAsync(inject([I18NRouterService], - (i18nRouter: I18NRouterService) => { - const injector = getTestBed(); - const router = injector.get(Router); - - i18nRouter.init(); - i18nRouter.changeLanguage('tr'); - - const fixture = TestBed.createComponent(TestBootstrapComponent); - fixture.detectChanges(); - - // redirection from /about/plum - router.navigate(['/tr/hakkinda/erik']) - .then(() => { - expect(router.url).toEqual('/tr/hakkinda/banana'); - }); - }))); - - it('should be able to translate routes outside the `i18n-root`', - fakeAsync(inject([I18NRouterService], - (i18nRouter: I18NRouterService) => { - const injector = getTestBed(); - const router = injector.get(Router); - - i18nRouter.init(); - i18nRouter.changeLanguage('tr'); - - const fixture = TestBed.createComponent(TestBootstrapComponent); - fixture.detectChanges(); - - // navigate to /change-language - router.navigate(['/dil-secimi/en']) - .then(() => { - expect(router.url).toEqual('/dil-secimi/en'); - }); - }))); - - it('should be able to translate routes w/`i18n-root` route `non-empty` string', - fakeAsync(() => { - const someRoutes: Routes = [ - { - path: 'home', - component: TestBootstrapComponent, - children: [ - { - path: '', - component: TestComponent - }, - { - path: 'about', - component: TestComponent - } - ], - data: { - i18n: { - isRoot: true - } - } - } - ]; - - const someTranslations = { - "en": { - "ROOT.HOME": 'home', - "ROOT.ABOUT": 'about' - }, - "tr": { - "ROOT.HOME": 'ana-sayfa', - "ROOT.ABOUT": 'hakkinda' - } - }; - - const i18nRouterFactory = () => new I18NRouterStaticLoader(someRoutes, someTranslations); - - testModuleConfig(someRoutes, [{ provide: I18NRouterLoader, useFactory: (i18nRouterFactory) }]); - - const injector = getTestBed(); - const router = injector.get(Router); - const i18nRouter = injector.get(I18NRouterService); - - i18nRouter.init(); - i18nRouter.changeLanguage('tr'); - - const fixture = TestBed.createComponent(TestBootstrapComponent); - fixture.detectChanges(); - - // navigate to /home - router.navigate(['/tr/ana-sayfa']) - .then(() => { - expect(router.url).toEqual('/tr/ana-sayfa'); - }); - })); - - it('should be able to translate routes w/o `i18n-root`', - fakeAsync(() => { - const someRoutes: Routes = [ - { - path: 'home', - component: TestBootstrapComponent, - children: [ - { - path: '', - component: TestComponent - }, - { - path: 'about', - component: TestComponent - } - ] - } - ]; - - const someTranslations = { - "en": { - "HOME": 'home', - "ABOUT": 'about' - }, - "tr": { - "HOME": 'ana-sayfa', - "ABOUT": 'hakkinda' - } - }; - - const i18nRouterFactory = () => new I18NRouterStaticLoader(someRoutes, someTranslations); - - testModuleConfig(someRoutes, [{ provide: I18NRouterLoader, useFactory: (i18nRouterFactory) }]); - - const injector = getTestBed(); - const router = injector.get(Router); - const i18nRouter = injector.get(I18NRouterService); - - i18nRouter.init(); - i18nRouter.changeLanguage('tr'); - - const fixture = TestBed.createComponent(TestBootstrapComponent); - fixture.detectChanges(); - - // navigate to /home - router.navigate(['/ana-sayfa']) - .then(() => { - expect(router.url).toEqual('/ana-sayfa'); - }); - })); - }); - }); \ No newline at end of file + () => { + beforeEach(() => { + const i18nRouterFactory = () => new I18NRouterStaticLoader(testRoutes, testTranslations); + + testModuleConfig(testRoutes, [{ + provide: I18NRouterLoader, + useFactory: (i18nRouterFactory) + }]); + }); + + describe('I18NRouterService', + () => { + it('is defined', + inject([I18NRouterService], + (i18nRouter: I18NRouterService) => { + expect(I18NRouterService).toBeDefined(); + expect(i18nRouter).toBeDefined(); + expect(i18nRouter instanceof I18NRouterService).toBeTruthy(); + })); + + it('should not translate routes w/o default initialization', + inject([I18NRouterService], + (i18nRouter: I18NRouterService) => { + const injector = getTestBed(); + const router = injector.get(Router); + + spyOn(router, 'resetConfig').and.callThrough(); + + i18nRouter.init(false); + i18nRouter.changeLanguage('en'); + + expect(router.resetConfig).not.toHaveBeenCalled(); + expect(router.config).toEqual(testRoutes); + + const fixture = TestBed.createComponent(TestBootstrapComponent); + fixture.detectChanges(); + + // initial navigation + router.navigate(['/']) + .then(() => { + expect(router.url).toEqual('/'); + }); + })); + + it('should not translate routes w/o translations', + inject([Router, I18NRouterService], + (router: Router, i18nRouter: I18NRouterService) => { + spyOn(router, 'resetConfig').and.callThrough(); + + i18nRouter.init(); + i18nRouter.changeLanguage('fr'); + + expect(router.resetConfig).not.toHaveBeenCalled(); + expect(router.config).toEqual(testRoutes); + })); + + it('should be able to `partly` translate routes w/missing translations', + fakeAsync(inject([I18NRouterService], + (i18nRouter: I18NRouterService) => { + const injector = getTestBed(); + const router = injector.get(Router); + + i18nRouter.init(); + i18nRouter.changeLanguage('tr'); + + const fixture = TestBed.createComponent(TestBootstrapComponent); + fixture.detectChanges(); + + // navigate to /about/banana + router.navigate(['/tr/hakkinda/banana']) + .then(() => { + expect(router.url).toEqual('/tr/hakkinda/banana'); + }); + }))); + + it('should be able to `catchall` redirect to `i18n-root`', + fakeAsync(inject([I18NRouterService], + (i18nRouter: I18NRouterService) => { + const injector = getTestBed(); + const router = injector.get(Router); + + i18nRouter.init(); + i18nRouter.changeLanguage('en'); + + const fixture = TestBed.createComponent(TestBootstrapComponent); + fixture.detectChanges(); + + // catchall navigation + router.navigate(['/about']) + .then(() => { + expect(router.url).toEqual('/en'); + }); + }))); + + it('should be able to translate `path` property of routes', + fakeAsync(inject([I18NRouterService], + (i18nRouter: I18NRouterService) => { + const injector = getTestBed(); + const router = injector.get(Router); + + i18nRouter.init(); + i18nRouter.changeLanguage('tr'); + + const fixture = TestBed.createComponent(TestBootstrapComponent); + fixture.detectChanges(); + + // navigate to /about + router.navigate(['/tr/hakkinda']) + .then(() => { + expect(router.url).toEqual('/tr/hakkinda'); + }); + }))); + + it('should be able to translate `redirectTo` property of routes', + fakeAsync(inject([I18NRouterService], + (i18nRouter: I18NRouterService) => { + const injector = getTestBed(); + const router = injector.get(Router); + + i18nRouter.init(); + i18nRouter.changeLanguage('tr'); + + const fixture = TestBed.createComponent(TestBootstrapComponent); + fixture.detectChanges(); + + // redirection from /about/plum + router.navigate(['/tr/hakkinda/erik']) + .then(() => { + expect(router.url).toEqual('/tr/hakkinda/banana'); + }); + }))); + + it('should be able to translate routes outside the `i18n-root`', + fakeAsync(inject([I18NRouterService], + (i18nRouter: I18NRouterService) => { + const injector = getTestBed(); + const router = injector.get(Router); + + i18nRouter.init(); + i18nRouter.changeLanguage('tr'); + + const fixture = TestBed.createComponent(TestBootstrapComponent); + fixture.detectChanges(); + + // navigate to /change-language + router.navigate(['/dil-secimi/en']) + .then(() => { + expect(router.url).toEqual('/dil-secimi/en'); + }); + }))); + + it('should be able to translate routes w/`i18n-root` route `non-empty` string', + fakeAsync(() => { + const someRoutes: Routes = [ + { + path: 'home', + component: TestBootstrapComponent, + children: [ + { + path: '', + component: TestComponent + }, + { + path: 'about', + component: TestComponent + } + ], + data: { + i18n: { + isRoot: true + } + } + } + ]; + + const someTranslations = { + 'en': { + 'ROOT.HOME': 'home', + 'ROOT.ABOUT': 'about' + }, + 'tr': { + 'ROOT.HOME': 'ana-sayfa', + 'ROOT.ABOUT': 'hakkinda' + } + }; + + const i18nRouterFactory = () => new I18NRouterStaticLoader(someRoutes, someTranslations); + + testModuleConfig(someRoutes, [{ + provide: I18NRouterLoader, + useFactory: (i18nRouterFactory) + }]); + + const injector = getTestBed(); + const router = injector.get(Router); + const i18nRouter = injector.get(I18NRouterService); + + i18nRouter.init(); + i18nRouter.changeLanguage('tr'); + + const fixture = TestBed.createComponent(TestBootstrapComponent); + fixture.detectChanges(); + + // navigate to /home + router.navigate(['/tr/ana-sayfa']) + .then(() => { + expect(router.url).toEqual('/tr/ana-sayfa'); + }); + })); + + it('should be able to translate routes w/o `i18n-root`', + fakeAsync(() => { + const someRoutes: Routes = [ + { + path: 'home', + component: TestBootstrapComponent, + children: [ + { + path: '', + component: TestComponent + }, + { + path: 'about', + component: TestComponent + } + ] + } + ]; + + const someTranslations = { + 'en': { + 'HOME': 'home', + 'ABOUT': 'about' + }, + 'tr': { + 'HOME': 'ana-sayfa', + 'ABOUT': 'hakkinda' + } + }; + + const i18nRouterFactory = () => new I18NRouterStaticLoader(someRoutes, someTranslations); + + testModuleConfig(someRoutes, [{ + provide: I18NRouterLoader, + useFactory: (i18nRouterFactory) + }]); + + const injector = getTestBed(); + const router = injector.get(Router); + const i18nRouter = injector.get(I18NRouterService); + + i18nRouter.init(); + i18nRouter.changeLanguage('tr'); + + const fixture = TestBed.createComponent(TestBootstrapComponent); + fixture.detectChanges(); + + // navigate to /home + router.navigate(['/ana-sayfa']) + .then(() => { + expect(router.url).toEqual('/ana-sayfa'); + }); + })); + }); + }); diff --git a/tests/index.spec.ts b/tests/index.spec.ts index ff076be..afa58ca 100644 --- a/tests/index.spec.ts +++ b/tests/index.spec.ts @@ -10,143 +10,148 @@ import { RouterTestingModule } from '@angular/router/testing'; // module import { I18NRouterModule, I18N_ROUTER_PROVIDERS } from '../index'; -@Component({ template: '' }) -export class TestBootstrapComponent {} +@Component({template: ''}) +export class TestBootstrapComponent { +} -@Component({ template: '' }) -export class TestComponent {} +@Component({template: ''}) +export class TestComponent { +} @NgModule({ - declarations: [ - TestBootstrapComponent, - TestComponent - ], - imports: [RouterTestingModule] + declarations: [ + TestBootstrapComponent, + TestComponent + ], + imports: [RouterTestingModule] }) -class TestSharedModule { } +class TestSharedModule { +} @NgModule({ - imports: [ - HttpModule, - TestSharedModule, - I18NRouterModule.forChild([ - { - path: '', - component: TestComponent - } - ], - 'home') - ] + imports: [ + HttpModule, + TestSharedModule, + I18NRouterModule.forChild([ + { + path: '', + component: TestComponent + } + ], + 'home') + ] }) -class TestHomeModule { } +class TestHomeModule { +} @NgModule({ - imports: [ - TestSharedModule, - I18NRouterModule.forChild([ - { - path: '', - component: TestBootstrapComponent, - children: [ - { - path: 'us/:topicId', - component: TestComponent - }, - { - path: 'banana', - component: TestComponent - }, - { - path: 'apple/:fruitId/pear', - component: TestComponent - }, - { - path: 'plum', - redirectTo: '/about/banana', - pathMatch: 'full' - } - ] - } - ], - 'about') - ] -}) -class TestAboutModule { } - -export const testRoutes: Routes = [ - { - path: '', - children: [ + imports: [ + TestSharedModule, + I18NRouterModule.forChild([ + { + path: '', + component: TestBootstrapComponent, + children: [ { - path: '', - loadChildren: () => TestHomeModule + path: 'us/:topicId', + component: TestComponent }, { - path: 'about', - loadChildren: () => TestAboutModule - } - ], - data: { - i18n: { - isRoot: true + path: 'banana', + component: TestComponent + }, + { + path: 'apple/:fruitId/pear', + component: TestComponent + }, + { + path: 'plum', + redirectTo: '/about/banana', + pathMatch: 'full' } + ] } - }, - { - path: 'change-language/:languageCode', - component: TestComponent - }, - { - path: '**', - redirectTo: '', - pathMatch: 'full' + ], + 'about') + ] +}) +class TestAboutModule { +} + +export const testRoutes: Routes = [ + { + path: '', + children: [ + { + path: '', + loadChildren: () => TestHomeModule + }, + { + path: 'about', + loadChildren: () => TestAboutModule + } + ], + data: { + i18n: { + isRoot: true + } } + }, + { + path: 'change-language/:languageCode', + component: TestComponent + }, + { + path: '**', + redirectTo: '', + pathMatch: 'full' + } ]; export const testTranslations = { - "en": { - "ROOT.ABOUT": 'about', - "ROOT.ABOUT.US": 'us', - "ROOT.ABOUT.BANANA": 'banana', - "ROOT.ABOUT.APPLE": 'apple', - "ROOT.ABOUT.APPLE.PEAR": 'pear', - "ROOT.ABOUT.PLUM": 'plum', - "CHANGE_LANGUAGE": 'change-language' - }, - "tr": { - "ROOT.ABOUT": 'hakkinda', - "ROOT.ABOUT.US": 'biz', - //"ROOT.ABOUT.BANANA": 'muz', // commented on purpose - "ROOT.ABOUT.APPLE": 'elma', - "ROOT.ABOUT.APPLE.PEAR": 'armut', - "ROOT.ABOUT.PLUM": 'erik', - "CHANGE_LANGUAGE": 'dil-secimi' - } + "en": { + "ROOT.ABOUT": 'about', + "ROOT.ABOUT.US": 'us', + "ROOT.ABOUT.BANANA": 'banana', + "ROOT.ABOUT.APPLE": 'apple', + "ROOT.ABOUT.APPLE.PEAR": 'pear', + "ROOT.ABOUT.PLUM": 'plum', + "CHANGE_LANGUAGE": 'change-language' + }, + "tr": { + "ROOT.ABOUT": 'hakkinda', + "ROOT.ABOUT.US": 'biz', + //"ROOT.ABOUT.BANANA": 'muz', // commented on purpose + "ROOT.ABOUT.APPLE": 'elma', + "ROOT.ABOUT.APPLE.PEAR": 'armut', + "ROOT.ABOUT.PLUM": 'erik', + "CHANGE_LANGUAGE": 'dil-secimi' + } }; // test module configuration for each test export const testModuleConfig = (routes: Routes = [], moduleOptions?: Array) => { - // reset the test environment before initializing it. - TestBed.resetTestEnvironment(); + // reset the test environment before initializing it. + TestBed.resetTestEnvironment(); - TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()) - .configureTestingModule({ - imports: [ - TestSharedModule, - RouterTestingModule.withRoutes(routes), - I18NRouterModule.forRoot(routes, moduleOptions) - ], - providers: [ - { - provide: Http, - useFactory: (mockBackend: MockBackend, options: BaseRequestOptions) => { - return new Http(mockBackend, options); - }, - deps: [MockBackend, BaseRequestOptions] - }, - MockBackend, - BaseRequestOptions, - I18N_ROUTER_PROVIDERS - ] - }); + TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()) + .configureTestingModule({ + imports: [ + TestSharedModule, + RouterTestingModule.withRoutes(routes), + I18NRouterModule.forRoot(routes, moduleOptions) + ], + providers: [ + { + provide: Http, + useFactory: (mockBackend: MockBackend, options: BaseRequestOptions) => { + return new Http(mockBackend, options); + }, + deps: [MockBackend, BaseRequestOptions] + }, + MockBackend, + BaseRequestOptions, + I18N_ROUTER_PROVIDERS + ] + }); }; diff --git a/tslint.json b/tslint.json index 06a687e..70d6a73 100644 --- a/tslint.json +++ b/tslint.json @@ -148,14 +148,10 @@ "no-input-rename": true, "no-output-rename": true, "no-forward-ref": false, - "use-life-cycle-interface": true, "use-pipe-transform-interface": true, "pipe-naming": [ true, "camelCase", "i18n" ], "component-class-suffix": true, "directive-class-suffix": true, - "import-destructuring-spacing": true, - "templates-use-public": true, - "no-access-missing-member": true, "invoke-injectable": true } }