Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prefix routes with locale in vue.js (using vue-i18n)

I have a locale.js file which is responsible for defining user locale. Here it is:

import store from '@/vuex/index'

let locale

const defaultLocale = 'en_US'

if (store.getters['auth/authenticated']) {
  locale = store.getters['auth/currentUser'].locale || defaultLocale
} else {
  if (localStorage.getItem('locale')) {
    locale = localStorage.getItem('locale')
  } else {
    locale = defaultLocale
  }
}

export default locale

Also I have a i18n.js file which is responsible for making i18n instance which I use when I init my app.

import Vue from 'vue'
import VueI18n from 'vue-i18n'
import locale from '@/services/locale'

Vue.use(VueI18n)

const fallbackLocale = 'en_US'

let i18n = new VueI18n({
  locale,
  fallbackLocale,
})

i18n.setLocaleMessage('ru_RU', require('@/lang/ru_RU.json'))
i18n.setLocaleMessage('en_US', require('@/lang/en_US.json'))

export { i18n }

Now I think that it'd be more convenient to have URLs prefixed with locale, like /en/profile or /ru/profile. This way I can share a link with locale which would be already set.

Not sure how do to this though. Making all routes child and put /:locale? is not that convenient because router is not yet initialized (I pass i18n and router instances simultaneously when initing root app instance).

How can I achieve that, what would be the best approach?

like image 233
Victor Avatar asked Dec 14 '22 16:12

Victor


2 Answers

You can implement router

routes: [{
    path: '/:lang',
    children: [
      {
        path: 'home'
        component: Home
      },
      {
        path: 'about',
        component: About
      },
      {
        path: 'contactus',
        component: ContactUs
      }
    ]
  }]

and set locale in beforeEach hook

// use beforeEach route guard to set the language
router.beforeEach((to, from, next) => {

  // use the language from the routing param or default language
  let language = to.params.lang;
  if (!language) {
    language = 'en';
  }

  // set the current language for vuex-i18n. note that translation data
  // for the language might need to be loaded first
  Vue.i18n.set(language);
  next();

});
like image 100
ittus Avatar answered Dec 18 '22 07:12

ittus


There are two or three problems I can think of that comes with nesting all your routes under a single /:locale?.

  1. Route definitions may become ambiguous. If you have paths /:locale?/foo/bar and /:locale?/bar defined as routes, what will <RouterLink to="/foo/bar" /> match? That will depend on which of those routes is defined first, and if the second of my examples is matched it will lead to an invalid locale. This problem has a simple-enough solution; just constrain your :locale parameter using a regex. If you know the exact list of supported locales statically, you could do something like:

     import locales from '@/lang'       // Your list of supported locales.
    
     const regexp = locales.join('|')   // You may want to filter out 'en' first.
     const routes = [{
       path: `/:locale(${regexp})?`,
       children: [
         ...
       ],
     }]
    

    If your translations and list of supported locales are otherwise only available at runtime (e.g. they're retrieved via an API), you may be forced to create a regex specific to your locale tag format. If they match BCP-47, I believe that means either 2 or 3 characters for the primary subtag, and the script and region are optional. If you use normalized tags (lowercase primary, titlecase script, uppercase region), that's even better, because that will further reduce ambiguity:

     const routes = [{
       path: '/:locale([a-z]{2,3}(-[A-Z][a-z]+)?(-([A-Z]{2}|[0-9]{3}))?',
       caseSensitive: true,
       children: [
         ...
       ],
     }]
    

    You'll want to read the spec more closely than I have to ensure that regex is correct. You'll also need to guard against unsupported locales in your beforeEach hook, so that you can load a "Not found" error page.

    As long as you do not define any routes whose first path segment could be mistaken for a locale tag, the above should fix the ambiguity problem.

  2. Routes may accidentally be defined using root paths. Nested routes are usually defined using relative paths, i.e. paths not anchored with a /. However, nesting is not simply a mechanism for sharing prefixes or parameters among many routes, it is most often used for sharing layout components. Vue-router therefore allows you to override the parent route definition's path by defining an absolute path. The documentation explains:

    Note that nested paths that start with / will be treated as a root path. This allows you to leverage the component nesting without having to use a nested URL.

    Mistakenly defining an absolute path will cause the route to only be matched for the fallback (I assume English) locale. As developers are likely to prototype and test using English most of the time, it might not appear like anything is amiss.

    For a small application where all your routes are defined within a single file, this may not be a big deal as the error is probably easy to spot. But for a large application with many route definition files and many developers, such an error is going to be more difficult to catch.

  3. Every usage of <RouterLink> and programmatic navigation will require injecting the locale parameter. You'll need to remember to interpolate $i18n.locale into every to prop and push() call. Not doing so does not cause an error or break the page, so your tests are unlikely to catch this, and you won't notice any problems if you're only browsing in English. You could wrap or extend <RouterLink> with your own component that does this automatically, but that doesn't prevent someone from accidentally using RouterLink, as it is still globally-registered. You could also write a global mixin to add convenience methods for router.push()/.replace()/.go(), but this again would not protect you against accidental use of those methods.

One not-ideal solution to the above problems is to forego defining the locale as a path parameter, and instead match it prior to initializing the router. To do this, you have to pass it as the base constructor option. Unfortunately, the base path does not appear to be alterable, meaning locale changes will require a new page request. Since most users will likely change locale at most once, this might not be a huge problem, but nonetheless does not give the best user experience.

like image 31
Victor van Poppelen Avatar answered Dec 18 '22 07:12

Victor van Poppelen