daaru00 / gridsome-plugin-i18n

Gridsome plugin for i18n
MIT License
53 stars 12 forks source link

Localized routes #17

Open giuseppeaiello opened 4 years ago

giuseppeaiello commented 4 years ago

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' }
             ]
          },
        ]
      }
    }
daaru00 commented 4 years ago

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?

giuseppeaiello commented 4 years ago

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 commented 4 years ago

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"
}
daaru00 commented 4 years ago

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.

giuseppeaiello commented 4 years ago

@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

daaru00 commented 4 years ago

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?

giuseppeaiello commented 4 years ago

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?

daaru00 commented 4 years ago

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?

giuseppeaiello commented 4 years ago

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).

edlefebvre commented 3 years ago

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)

giuseppeaiello commented 3 years ago

@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

edlefebvre commented 3 years ago

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

giuseppeaiello commented 3 years ago

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

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

Hope it helps...

edlefebvre commented 3 years ago

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.

giuseppeaiello commented 3 years ago

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

$t('slug')

because:

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