node.jskoakoa-router

Localized routes in koa


I'm developing a site with multiple languages. Some routes will therefore also have to be localized and I'm not sure how to do this properly.

I'm using @koa/router for routing.

For this example it's only English and Swedish but the site will handle more languages.

I can setup routes to match words in different languages like

router.get('/(create-account|skapa-konto)/', (ctx, next) => {
  ctx.body = translate('signup_welcome');
  await next();
});

But, I want the English site to only respond to '/sign-up' and send 404 for '/skapa-konto' (and vice versa).

In the real world the route would point to some controller function. So if I set up individual routes for each language I would have to change all localized routes manually should the controller function change in the future. That's something I would like to avoid ;)

Any suggestions?


Solution

  • I ended up solving this by extending the Router like this:

    const LocalizedRouter = class extends Router {
        /**
         * Set up route mapping
         * @param {object} options
         */
        constructor(options) {
            if (!Array.isArray(options.languages)) {
                throw new TypeError('Languages must be of type Array');
            }
    
            super(options);
            this.languages = options.languages;
        }
    
        /**
         * Router function for GET method
         * @param {string | Object<string, string>} RouteCollection
         */
        get(routes, func) {
            if (typeof(routes) === 'string') {
                super.get(routes, func);
                return;
            }
            if (typeof(routes) === 'object') {
                for(const key in routes) {
                    if(!this.languages.includes(key)) {
                        continue;
                    }
                    if(typeof(func) !== 'function') {
                        throw new TypeError('Middleware must be a function');
                    }
                    const checkLanguageAndMount = async (ctx, next) => {
                        if(ctx.state.lang !== key) {
                            return next();
                        }
                        return func(ctx, next);
                    };
    
                    super.get(routes[key], checkLanguageAndMount);
                }
                return;
            }
            throw new TypeError('"Routes" must be a string or an object');
        }
    };
    
    

    I can then set up my routes like this:

    const myRouter = new LocalizedRouter({
        languages: ['en', 'sv']
    });
    
    myRouter.get({
        'en': '/create-account',
        'sv': '/skapa-konto'
    }, (ctx, next) => {
      ctx.body = translate('signup_welcome');
      await next();
    };
    

    This can probably be cleaned up but it does solve what I wanted to do.

    EDIT: Fixed bug that caused 404 if two languages had identical paths