Skip to content

Commit

Permalink
feat: added redirect ignore glob support
Browse files Browse the repository at this point in the history
  • Loading branch information
niftylettuce committed Nov 20, 2019
1 parent 467d297 commit 9f422a1
Show file tree
Hide file tree
Showing 5 changed files with 751 additions and 547 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ If the given locale was not available then it will redirect the user to the dete
Redirects user with permanent `302` redirect to their detected locale if a valid language was not found for them.

**NOTE:** As of v1.2.2 we have added a `ignoredRedirectGlobs` option you can pass to `new I18N({ ... })` which will ignore these paths for locale redirection. This is incredibly useful if you are using authentication providers and the `passport` library, e.g. you want to set `/auth/github/ok` as the callback URL for GitHub, but a redirect to `/en/auth/github/ok` would have occurred, thus causing authentication to fail due to a bad code. In this case, you would set `{ ignoredRedirectGlobs: [ '/auth/**/*' ] }` or simply `[ '/auth/google/ok' ]`. This package uses [multimatch][] internally which supports an Array, therefore you could negate certain paths if needed. See the documentation for [multimatch][] for more insight.

It also sets the cookie `locale` for future requests to their detected locale.

This also stores the `last_locale` (or whatever you configure the property name to be in the config option `lastLocaleField`) for a user via `ctx.state.user.save()`.
Expand Down Expand Up @@ -123,7 +125,8 @@ const i18n = new I18N({
__mf: 'tmf'
},
register: i18n.api,
lastLocaleField: 'last_locale'
lastLocaleField: 'last_locale',
ignoredRedirectGlobs: []
});
```

Expand Down Expand Up @@ -175,3 +178,5 @@ For a list of all available locales see [i18n-locales][].
[language-support]: https://github.com/nodejs/nodejs.org/commit/d6cdd942a8fc0fffcf6879eca124295e95991bbc#diff-78c12f5adc1848d13b1c6f07055d996eR59

[cabin]: https://cabinjs.com

[multimatch]: https://github.com/sindresorhus/multimatch
40 changes: 20 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,41 +20,41 @@
"Nick Baugh <niftylettuce@gmail.com> (http://niftylettuce.com/)"
],
"dependencies": {
"@hapi/boom": "^7.4.3",
"auto-bind": "^2.1.0",
"boolean": "^1.0.0",
"@hapi/boom": "^8.0.1",
"boolean": "1.0.0",
"country-language": "^0.1.7",
"debug": "^4.1.1",
"i18n": "^0.8.3",
"i18n": "^0.8.4",
"i18n-locales": "^0.0.2",
"lodash": "^4.17.15",
"moment": "^2.24.0",
"qs": "^6.8.0",
"multimatch": "^4.0.0",
"qs": "^6.9.1",
"titleize": "^2.1.0"
},
"devDependencies": {
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@commitlint/cli": "^8.1.0",
"@commitlint/config-conventional": "^8.1.0",
"ava": "^2.3.0",
"codecov": "^3.5.0",
"cross-env": "^5.2.1",
"eslint": "^6.3.0",
"@babel/cli": "^7.7.0",
"@babel/core": "^7.7.2",
"@babel/preset-env": "^7.7.1",
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"ava": "^2.4.0",
"codecov": "^3.6.1",
"cross-env": "^6.0.3",
"eslint": "^6.6.0",
"eslint-config-xo-lass": "^1.0.3",
"eslint-plugin-node": "^10.0.0",
"fixpack": "^2.3.1",
"husky": "^3.0.5",
"koa": "^2.8.1",
"husky": "^3.1.0",
"koa": "^2.11.0",
"koa-generic-session": "^2.0.1",
"lint-staged": "^9.2.5",
"lint-staged": "^9.4.3",
"nyc": "^14.1.1",
"remark-cli": "^7.0.0",
"remark-cli": "^7.0.1",
"remark-preset-github": "^0.0.16",
"sinon": "^7.4.2",
"sinon": "^7.5.0",
"supertest": "^4.0.2",
"xo": "^0.24.0"
"xo": "0.24"
},
"engines": {
"node": ">=6.4.0"
Expand Down
16 changes: 13 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const { extname, resolve } = require('path');

const Boom = require('@hapi/boom');
const autoBind = require('auto-bind');
const boolean = require('boolean');
const debug = require('debug')('ladjs:i18n');
const i18n = require('i18n');
const locales = require('i18n-locales');
const moment = require('moment');
const multimatch = require('multimatch');
const titleize = require('titleize');
const { getLanguage } = require('country-language');
const { isEmpty, sortBy, every, isFunction } = require('lodash');
Expand Down Expand Up @@ -37,7 +37,8 @@ class I18N {
__mf: 'tmf'
},
register: i18n.api,
lastLocaleField: 'last_locale'
lastLocaleField: 'last_locale',
ignoredRedirectGlobs: []
},
config
);
Expand All @@ -56,7 +57,9 @@ class I18N {
// configure i18n
this.configure(this.config);

autoBind(this);
this.translate = this.translate.bind(this);
this.middleware = this.middleware.bind(this);
this.redirect = this.redirect.bind(this);
}

translate(key, locale) {
Expand Down Expand Up @@ -175,6 +178,13 @@ class I18N {
// do not redirect static paths
if (extname(ctx.path) !== '') return next();

// check against ignored/whitelisted redirect middleware paths
const match = multimatch(ctx.path, this.config.ignoredRedirectGlobs);
if (Array.isArray(match) && match.length > 0) {
debug(`multimatch found matches for ${ctx.path}:`, match);
return next();
}

// inspired by nodejs.org language support
// <https://github.com/nodejs/nodejs.org/commit/d6cdd942a8fc0fffcf6879eca124295e95991bbc#diff-78c12f5adc1848d13b1c6f07055d996eR59>
const locale = ctx.url.split('/')[1].split('?')[0];
Expand Down
37 changes: 37 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const { resolve } = require('path');

const test = require('ava');
const request = require('supertest');
const session = require('koa-generic-session');
const sinon = require('sinon');
const Koa = require('koa');

const I18N = require('../lib');

const phrases = { HELLO: 'Hello there!', hello: 'hello' };
Expand Down Expand Up @@ -185,6 +187,41 @@ test('prefers cookie over Accept-Language header', async t => {
t.is(res.body.locale, 'es');
});

test('does not redirect with ignored redirect globs', async t => {
const app = new Koa();
const i18n = new I18N({
phrases,
directory,
ignoredRedirectGlobs: ['/auth/**/*', '/login']
});

app.use(session());
app.use(i18n.middleware);
app.use(i18n.redirect);

app.use(ctx => {
const { locale } = ctx;
ctx.body = { locale };
ctx.status = 200;
});

let res = await request(app.listen())
.get('/login')
.set('Cookie', ['locale=es']);
t.is(res.status, 200);

res = await request(app.listen())
.get('/auth/google/ok')
.set('Cookie', ['locale=es']);
t.is(res.status, 200);

res = await request(app.listen())
.get('/login/beep/baz')
.set('Cookie', ['locale=es']);
t.is(res.status, 302);
t.is(res.headers.location, '/es/login/beep/baz');
});

test('redirects to correct path based on locale set via cookie', async t => {
const app = new Koa();
const i18n = new I18N({ phrases, directory });
Expand Down
Loading

0 comments on commit 9f422a1

Please sign in to comment.