gridsome-plugin-i18n icon indicating copy to clipboard operation
gridsome-plugin-i18n copied to clipboard

Localized routes

Open giuseppeaiello opened this issue 4 years ago • 15 comments

Is your feature request related to a problem? Please describe. It would be nice to have the possibility to customize the name of the routes for each single supported locale.

Describe the solution you'd like With the page "/projects" and the locales "en" and "it", it would be nice to have: "/en/projects" localized to "/it/progetti".

Describe alternatives you've considered Solutions like the one provided by Vue Router would be a nice to have: https://router.vuejs.org/guide/essentials/nested-routes.html

Proposal of plugin's configuration changes

{
      use: "gridsome-plugin-i18n",
      options: {
        routes: [
          { 
             path: '/it',
             component: './src/layouts/Default.vue'
             children: [
               { path: 'progetti', component: './src/pages/Projects.vue' }
               { path: 'contatti', component: './src/pages/Contacts.vue' }
             ]
          },
          { 
             path: '/en', 
             component: './src/layouts/Default.vue'
             children: [
               { path: 'projects', component: './src/pages/Projects.vue' }
               { path: 'contacts', component: './src/pages/Contacts.vue' }
             ]
          },
        ]
      }
    }

giuseppeaiello avatar May 13 '20 14:05 giuseppeaiello

Hi @giuseppeaiello,

yeah, it's a really nice to have feature. Just some considerations about implemention:

I don't think is so hard to translate it during page generation can be easly replaced with the translated one here:

createPage({
  path: path.join(`/${pathSegment}/`, route.path),
  // ...
})

The main issue is were store this path translation, I think we can directly use i18n to translate:

createPage({
  path: path.join(`/${pathSegment}/`, i18n(route.path)),
  // ...
})

this require to add to translation files also the path to translate:

// ./src/locales/it-it.json
{
  "/projects": "/progetti"
}

Just asking, the data source that you are using support this type of translation? Because, maybe, the shortest way to solve this could be having a settings to disable automatic page generation and create pages from your own gridsome.server.js directly generate the page with the prefered route path (just need to set locale context variable). Thinking about Storyblok or Wordpress that can directly provide this information from the data source, instead of re-map all urls inside translations files (that will be statically added and are no dynamic from data source).

Solutions like the one provided by Vue Router would be a nice to have: https://router.vuejs.org/guide/essentials/nested-routes.html

Not sure to understand how nested routes can solve the issue.. could you help me to understand better?

daaru00 avatar May 13 '20 16:05 daaru00

I think the best solution would be to add a settings to disable automatic page generation.

I'm also thinking about the need to generate a proper sitemap.xml, one per language: having pages that shouldn't be added to the sitemap could be confusing and problematic to solve, and we couldn't rely on the "pages" generated through the Pages API.

For example:

/about            -> component about
/it/about         -> component about
/en/about         -> component about

you would result having 2 of these 3 generated routes displaying the same content (same language), but on 2 different URLs; that's a practice better to avoid when working on SEO.

Just asking, the data source that you are using support this type of translation? It's a custom API, but consider it to be something similar to the Wordpress API, so your suggestion about disabling automatic page generation makes a lot of sense to me. This would cover also static websites, where a .json can be used to generate all the routes for all required locales.

Not sure to understand how nested routes can solve the issue.. Probably you don't need to use nested routes if you can support the localization of sub-routes as well; something like:

"/en/projects/category-name/project-name"
"/it/progetti/nome-categoria/nome-progetto"

(imagine the sub-routes to be static, not dynamically loaded for example). But probably you could add the full route to the translation json (it-it.json) so Nested Routes are not useful at all for this purpose.

giuseppeaiello avatar May 13 '20 19:05 giuseppeaiello

Hi @daaru00, after several tests, implementing an option for disabling automatic route generation and adding the labels in the localized json looked like the best solution for my use case.

Would you like to receive a pull request about it? If so, would enablePathGeneration be ok for the option's name?

I simply added a return in the createManagedPages method if that option is set to true:

/gridsome-plugin-i18n/gridsome.server.js

static defaultOptions () {
  return {
  //...
    enablePathGeneration: true
  }
}
createManagedPages({ findPages, createPage, removePage }) {
    if (!this.options.enablePathGeneration) return;
    //...
}

In the project's options, instead, I set the option to false: /project-folder/gridsome.server.js

{
      use: "gridsome-plugin-i18n",
      options: {
        // ...
        enablePathGeneration: false
      }
}

and in the project's gridsome.server.js I generate the routes, from a given array of routes like the following:

const routes = [
    {
      path: '/en/projects', component: './src/pages/Projects.vue', locale: 'en', label: '/projects'
    },
    {
      path: '/it/progetti', component: './src/pages/Projects.vue', locale: 'it', label: '/projects'
    }
]

and using the Pages API:

api.createManagedPages(({ createPage }) => {

    for(routeIndex in routes) {
      let route = routes[routeIndex];
      createPage({
        path: route.path,
        component: route.component,
        context: {
          locale: route.locale,
          label: route.label
        },
        route: {
          meta: {
            locale: route.locale
          }
        }
      })
    }    
    
  })

As you can see I'm adding a "label" variable to the $context: this is required to be able to access the key for the localized json files. For switching the language I'm using the following:

this.$router.push({
  path: this.$tp(this.$t(this.$context.label, newLocal), newLocal, true)
});

it-IT.json

{
    "/projects": "/progetti"
}

en-GB.json

{
    "/projects": "/projects"
}

giuseppeaiello avatar May 16 '20 08:05 giuseppeaiello

Hi @giuseppeaiello, it looks awesome!

Would you like to receive a pull request about it?

absolutely yes, thank you :+1: so I can get an idea with all the code together and do some local tests.

If so, would enablePathGeneration be ok for the option's name?

sure, it has the same style as the other activation flags, its ok for me.

daaru00 avatar May 17 '20 11:05 daaru00

@daaru00 I added as well the "customized routes" creation (to keep the project's gridsome.server.js file cleaner), simply sending in the config options an object called "routes".

Let me know if it makes sense for you as well: #18

giuseppeaiello avatar May 17 '20 14:05 giuseppeaiello

Hi @giuseppeaiello,

sorry.. I think some recent changes merged create some conflicts for this PR :disappointed: I chose to prioritize solving one of the biggest issue and had to change my approach for page generation.

I think you can move your return early check into createManagedPages class method, does it make sense to you too?

daaru00 avatar Jul 04 '20 07:07 daaru00

Hi @daaru00 , I adapted the code to the new changes, but I see that the "default" routes are generated anyway.

Is it possible that the new hook is creating the routes already, before createManagedPages?

giuseppeaiello avatar Jul 05 '20 17:07 giuseppeaiello

uhm, it shouldn't :thinking: the code just push pages objects into pagesToGenerate or pagesToReplace array for future processing on createManagedPages API hook.

Maybe I didn't understand correctly what you mean with "default"... are you referring to pages with default locale? that one are generated by Gridsome before the plugin's actions (createManagedPages).

I suppose the proposed change is the exclusion of the translated page generation, that happens here: https://github.com/daaru00/gridsome-plugin-i18n/blob/master/gridsome.server.js#L44 I missed something?

daaru00 avatar Jul 06 '20 10:07 daaru00

Hello @daaru00 , sorry for the delay. I just sent you a new Pull Request. Could you check if everything is ok now? (testing it locally didn't produce any issue).

giuseppeaiello avatar Jul 23 '20 08:07 giuseppeaiello

Hi @giuseppeaiello, how do you get the translated path?

The Locale switcher component in the doc doesn't seem to work when using localized routes, I imagine this bit should be modified?

path: this.$tp(this.$route.path, this.currentLocale, true)

edlefebvre avatar Sep 12 '20 15:09 edlefebvre

@edlefebvre "how do you get the translated path?" I see that in the doc is missing the reference to the "slug" property for each route.

You can take a look here about how to use the same slug for different paths, to be able to find the translation of each route in a specific language: https://github.com/daaru00/gridsome-plugin-i18n/pull/26#issuecomment-666234183

giuseppeaiello avatar Sep 12 '20 16:09 giuseppeaiello

Thank you for your answser @giuseppeaiello. I already followed your example and adding slugs, but the $tp function has no info of this right?

edlefebvre avatar Sep 12 '20 16:09 edlefebvre

This is how I'm using it when switching the language:

this.$tp(this.$t(this.$context.slug, newLocale), newLocale, true)

Hope it helps...

giuseppeaiello avatar Sep 12 '20 16:09 giuseppeaiello

OK thanks, I got it working using your code and by adding translated slugs in the translation json files.

I first thought that the routes.js file wad sufficient by itself, as it contains all the information already.

edlefebvre avatar Sep 12 '20 16:09 edlefebvre

I think that given the way Vue I18n works, we are forced to pass the instruction:

$t('slug')

because:

  • the URL could not contain the slug;
  • the definition of the route could not contain the slug.

But maybe this could be an interesting feature to add...

giuseppeaiello avatar Sep 12 '20 16:09 giuseppeaiello