[vue-router] localized urls and dynamic routes

hello.

is it possible to have localized urls with vue-router where uri is prefixed with i18n slug:

both point to same resource

thanx!

1 Like

Hi,

You can maybe try the approach im using in nuxt.js:

This might be bad way, and its not tested correctly, but its atleast working for me.

Component that is used for i18n uri’s that will set the lang and render right component.

<template>
  <component :is="component"/>
</template>

<script>
export default {
  validate ({ params, store }) {
    return store.state.locales.includes(
      params.lang.toLowerCase()
    )
  },
  beforeCreate () {
    const lang = this.$route.params.lang.toLowerCase()
    this.$store.commit('SET_LANG', lang)
    this.component = () => import(`~/pages/index`)
  }
}
</script>

Route can be something like this

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'i18n',
      path: '/:lang',
      component: 'pages/_lang.vue'
    }
  ]
}

:estonia:

Hi,

looking around the forums for a solution to your exact problem.
We have a multilanguage site that also has a default language…

so as you wrote:
https://www.site.com/products
https://www.site.com/en/products
https://www.site.com/fr/products
these should render the same route

Is there any way to accomplish this without duplicating all routes, meaning:

{
  path: "/:locale",
  name: "locale",
  component: Layout,
  children: [
    {
      path: "products",
      name: "products",
      component: Products
    },
    {
      path: "test2",
      name: "test2",
      component: Test2
    }
  ]
},
{
  path: "products",
  name: "global-products",
  component: Products
},
{
  path: "test2",
  name: "global-test2",
  component: Test2
}     

it would really suck to duplicate all routes and give different names
just for the default language, while all others are prefixed and will
be children of the lang route…
I saw one other relevant topic in these forums >https://forum.vuejs.org/t/dynamic-base-in-vue-router/1026
but haven’t seen a mainline solution there either…

I tried making the locale route optional, i.e: path: “/:locale?”,

but that’s not enough since I need access to it’s “children” in case the lang
is default and doesn’t use the param (/en/)

Please if anyone can point me in the right direction… thank you.This text will be hidden

For plain vue-js the simplest way I found was to put all the routes as children of a main route that set the page language.

Example:

import InvalidRoute from '@/views/404.vue';

// vue-router => routes.js

export default [
  // 404 page is not localized
  {
    path: '/404',
    name: 'invalid',
    components: {
      default: InvalidRoute,
    },
  },

  // All pages are preceded by /en/ or /fr/
  {
    path: '/:lang',
    components: {
      default: App,
    },
    children: [
      // All the pages are mapped from the routes
      ...routes.map((p) => ({
        path: p.enShort,
        alias: p.frShort,
        name: p.name,
        components: p.components,
      })),

      // All the / to /en redirects
      ...routes.map((p) => ({
        path: `/${p.enShort}`,
        redirect: p.enFull,
      })),

      // All the / to /fr redirects
      ...routes.map((p) => ({
        path: `/${p.frShort}`,
        redirect: p.frFull,
      })),

      // Redirect /en and /fr to map
      {
        path: '',
        redirect: { name: 'map' },
      },
    ],
  },
 
  // Catch all for the 404 page
  {
    path: '*',
    redirect: { name: 'invalid' },
  },
];

As you can see I map an array of routes to make it more scalable and avoid having to write down the same route many times. This array looks like this:

const routes = [
  {
    name: 'user',
    enFull: '/en/user/:locId',
    enShort: 'user/:locId',
    frFull: '/fr/utilisateur/:locId',
    frShort: 'utilisateur/:locId',
    components: {
      default: () => import('@/views/User.vue'),
    },
  },
  // etc.
];

export default routes;

This method lets you use routes such as /utilisateur/123 to set the app’s language to french and then redirect to /fr/utilisateur/123.

You can also add a beforeEach logic to the router to make sure that the language is always set:

router.beforeEach((to, from, next) => {
  // Evaluate whether or not the URL contains a language
  const hasLang = to.params && to.params.lang;
  const hadLang = from.params && from.params.lang;

  // If the language hasn't changed since last route we're done
  if (hasLang && hadLang && from.params.lang.toLowerCase() === to.params.lang.toLowerCase()) {
      next();
  }

  // Get the save language if it exists
  let lang = localStorage.lang ? localStorage.lang.toLowerCase() : 'en';

  // Overwrite the language with the route if there is one
  if (hasLang) {
    lang = to.params.lang.toLowerCase();
  }

  // Make sure the language is valid
  if (!['en', 'fr'].includes(lang)) {
    lang = 'en';
  }

  // Set the website language based on the URL
  i18n.locale = lang;
  localStorage.lang = lang;

  // Redirect to a url with the language
  if (!hasLang) {
    return next(`/${lang}${to.fullPath}`);
  }
  return next();
});

Hope this helps!

P-O

I solved this using advanced matching patterns:

// Make sure to *not* prepend your paths with a `/`!
const routes = [{
  path: '',
  component: Home,
}, {
  path: 'about',
  component: About,
}, { ... }];

const router = new Router({
  routes: [{
    // Include the locales you support between ()
    path: '/:locale(en|nl|fr)?',
    component: {
      beforeRouteEnter: setLocale,
      beforeRouteUpdate: setLocale,
      render(h) { return h('router-view'); }
    },
  }],
});

function setLocale(to, from, next) {
  let { locale } = to.params;
  if (!locale) {
    locale = 'default-locale';
  }

  // Do something with locale, check availability of messages etc.
  i18n.locale = locale;
  next();

}