0

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?

Magnus Jonsson
  • 123
  • 1
  • 5

2 Answers2

1

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

Magnus Jonsson
  • 123
  • 1
  • 5
0

This problem interested me so I created a small github repo with some code. I'll try to explain here:

I created an array with some options:

const localeConfig = [
  {
    locale: "en",
    routes: [
      {
        path: "/sign-up",
        controllers: [enController],
        method: "GET",
      },
    ],
    prefix: false,
  },
  {
    locale: "se",
    routes: [
      {
        path: "/skapa-konto",
        controllers: [seController],
        method: "GET",
      },
    ],
    prefix: false,
  },
];

I then pass this object to a setupRoutes function that basically iterates the array, generating all the routes according to those options.

const setupRoutes = (localeConfig) => {
  // Have some check to prevent duplicate routes
  localeConfig.forEach((opt) => {
    // Adding prefix according to option
    const localePrefix = opt.prefix ? `/${opt.locale}` : "";
    opt.routes.forEach((route) => {
      const path = `${localePrefix}${route.path}`;
      router[route.method.toLowerCase()].apply(router, [
        path,
        ...route.controllers,
      ]);
    });
  });
};

So, for instance, if you were to change any of the controllers in either language you would only need to update the specific locale object.route.controllers. I imagine you could even have each different locale in a different file to have some modularity.

The github repo is here and I would really like to have you contribute to it if you have any idea on how to improve this.

Cheers!

Pedro Filipe
  • 995
  • 1
  • 8
  • 17
  • Interesting! I'll take a closer look soon :) – Magnus Jonsson May 29 '20 at 19:00
  • I've now taken some time to look through it and it's not really solving the issue where I would have to update all localized versions. Let's say I have a RegisterController with the signup function. I then want to change the route to point to the signupAndVerify function instead. I would then have to edit each localized version and update them manually. – Magnus Jonsson Jun 02 '20 at 12:27