How to internationalize routing in Svelte & Sapper

We've been working on translating our site (leaf.cloud) to Dutch, because, well, we're a Dutch (and green) cloud provider. But Svelte and Sapper have made it a little difficult.

Update: I've updated this post with a new solution! Scroll to the bottom.

This post is specifically to explain how to make a Sapper site which does path based internationalization routing. So something like this:

/about -> english page/nl/about -> dutch language page

Pre-requisites

This post really starts to make sense when you've already added internationalization to your Sapper site, for example with the excellent kaisermann/svelte-i18n library. You should be able to apply what you read here to the sapper i18n template.

What didn't work

The problem is that that the svelte router has a built in mechanism to solve for Path variables which looks like so:

src/routes/blog/[slug].svelte -> Here 'slug' will automatically be converted to a variable, so you can dynamically fetch the blogpost for example.

It also works for folders, like so: src/routes/[lang]/about.svelte -> here the page would get passed the lang variable.

Unfortunately, this is not a great solution, because it would mean that for each language we would have to duplicate all the .svelte files, causing a maintenance nightmare. I've seen solutions where they copy the files in a post-processing step, but it doesn't feel right.

What almost worked

An answer on Stackoverflow by Rich Harris (one of the lead maintainers of Sapper) led me to this solution to mutate the request object to simply strip the language prefix from the req.url, req.path and req.originalUrl

The idea is that if the user requests: nl/about we let the router think it was just a request to about. And so the file structure does not need to have any special /[lang]/ path folders.

What I did is add the following to the middleware in server.js

polka() // You can also use Express  ...  .use(    compression({ threshold: 0 }),    (req, res, next) => {          const [, lang, ...parts] = req.url.split('/')      const path = parts.join('/')      if (lang == 'nl') {        // mutate the request object        req.url = req.path = req.originalUrl = `/${path}`      }      // let Sapper handle the mutated request      next()    },    ...    )  .listen(PORT, (err) => {    if (err) console.log('error', err)  })

The result of this is that the page loads, with the path, and everything appears to be happy. EXCEPT. It now appears that events are broken. For example, an event such as on:click no longer fires.

What Actually worked

In short: To use the built-in regular expression routing system.

In order to make it so that a file can be resolved by a path like: /about AND /es-ES/about We put the following in the folder name: [...a(.*about)]. This is a regular expression.

  • ...a() The group (what is enclosed in brackets () ) should be assigned to the 'a' variable. We use the ... spread operator so that it's split on '/'
  • .*about Capture: . (any character), * (repeated 0 or more times), followed by 'about' Alternatively we can write: [...i(.{0}|es-ES|pt-BR|ar)]

Important Caveats

You MUST use a different variable: i.e....a(), ...b()etc. for each path, or they will override each other and you will pull your hair out.

You can't use anything in these regular expressions. (, ), ?, :, /, \ are not allowed characters (as they are normally in regular expressions), so this is one of the few combinations that worked.

Show me the code!

Link: https://github.com/dhrp/sapper-template-i18n/tree/feature/path_routing

References

Other options / references