Open arslabora opened 3 years ago
Hi @arslabora,
if I understand well the issue is about relating /about
to the translated path /chi-siamo
and viceversa. Right now this plugin cannot handle this relation, Gridsome know that the pages exist (created when passing them the "routes" property) but there are no relation between them. This type of dynamism was intended to use with a connected external system (wordpress, storyblok..) that can print the correct URL without passing from $tp
utility.
I like your approach using a subset of i18n message keys, I think it is the quickest solution and it gives you the control you need.
So you have translated slugs inside it.json
file:
{
"slug": {
"about": "/chi-siamo"
}
}
and inside the en.json
file the related one:
{
"slug": {
"about": "/who-we-are"
}
}
once messages are registered in the main.js
:
appOptions.i18n.setLocaleMessage("it", require("./locales/it.json"));
appOptions.i18n.setLocaleMessage("en", require("./locales/en.json"));
you should now be able to combine $t
and $tp
utility to translate the path:
<nav class="nav">
<g-link class="nav__link" :to="$tp('/')">Home</g-link>
<g-link class="nav__link" :to="$tp($t('slug.about'))">About</g-link>
<locale-switcher />
</nav>
so $t('slug.about')
will return the translated path /chi-siamo
and $tp('/chi-siamo')
will take care of the language path parameter returning:
/it/chi-siamo
/en/who-we-are
maybe you were just miss this last part.
Just did a try with this setup and seems working fine: https://codesandbox.io/s/gridsome-plugin-issue-40-6k1ne?file=/src/layouts/Default.vue
Can this help you?
Thanks, buddy! Actually, I found an hidden feature, which is surprisingly not in the documentation, which solved the problem:
context: {
slug: ''
It's just a matter of set the same slug for each translated path, as this:
"en-US": [
{
path: '/en',
component: './src/pages/Index.vue',
context: {
slug: '/'
}
},
{
path: '/en/about-us/',
component: './src/pages/About.vue',
context: {
slug: '/about'
}
},
],
"it-IT": [
{
path: '/it',
component: './src/pages/Index.vue',
context: {
slug: '/'
}
},
{
path: '/it/chi-siamo/',
component: './src/pages/About.vue',
context: {
slug: '/about'
}
},
{ ...
and, as you mentioned in your previous comment, use a combination of $tp
with $t
, as:
<g-link class="nav__link" :to="$tp($t('/about'))">About</g-link>
and, in locales JSON:
(en.json) "/about": "about-us",
(it.json) "/about": "chi-siamo",
Than, we must change how LocaleSwitcher.vue components deal with the translated slug:
methods: {
localeChanged () {
this.$router.push({
path: this.$tp( // get the translated path
this.$t(this.$context.slug, this.currentLocale), // using slugs translations
this.currentLocale, true)
})
}
}
It works perfectly. ;)
I have a working starter, https://github.com/arslabora-i18n/gridsome-i18n-starter, published on https://gridsome-i18n-starter.netlify.app/. I plan to use it as a full working example for anyone, and a starter for my use, to save some time. I'll implement other i18n goodies I have in my nuxt boilerplate during this week, and also I'll try to create pages which document how to work with GraphQL from an i18n perspective. If you have any ideas, or suggestion, we could work on that together. As soon we are satisfied, we can publish it in the i18n documentation, so to help other users to better understand how to start. 😉
Hi @daaru00. I want to give you some updates of what I got so far, and ask for an help to understand how can I solve my current issue:
routes.js
under ./src
; I finally used a key named ref
, not slug
, as content got from sources plugins seems that uses this key under the hood, and it was messing all../src/content/blog
, with an uncommon yet quite usable nested layout. Each post lives in its own folder, as well as its images and its translations as well:
I defined three different post types, one for each locale (PostEn, PostIt, PostPt...), with this configuration -
{
use: '@gridsome/source-filesystem',
options: {
typeName: 'PostEn',
path: './src/content/blog/**/*.en.md',
},
},
{
use: '@gridsome/source-filesystem',
options: {
typeName: 'PostIt',
path: './src/content/blog/**/*.it.md',
},
},
{
use: '@gridsome/source-filesystem',
options: {
typeName: 'PostPt',
path: './src/content/blog/**/*.pt.md',
},
},
and, under ./src/pages
, I have one blog page for each locale too (./src/pages/en/Blog.vue
, ./src/pages/it/Blog.vue
, ...).
Posts are queried with GraphQL in this way:
query allPostEn {
post: allPostEn {
edges {
node {
title
ref
}
}
}
}
and they are correctly retrieved:
{
"data": {
"post": {
"edges": [
{
"node": {
"title": "Reasons to own a cat",
"ref": "blog/reasons-to-own-a-cat/"
}
},
{
"node": {
"title": "Cats are cute!",
"ref": "cats-are-cute"
}
}
]
}
}
}
If I open my development server, and navigate to blog
, I correctly switch from a post listing to another:
I can open each post from there, as expected. Now, problem is that nodes under edges
do not has $context
, and I used this.$context.ref
as key to manage path translation. So, there's a better, global way to do that? Or, can I create using gridsome.server.js a $context.ref properties for my blog posts?
@daaru00, after many tests, I found that using a modified LocaleSwitcher.vue
on posts templates do the trick. That is:
Each post's md file has, in its frontmatter, a ref:
key, which must be unique, however being the same for all translations. So, a post in three languages share the same ref: a-name
.
Than, each locales JSON must have the translated key, equal to the localised slug. So, for example, if I have a post with its title as "I am a post", its slug will be "i-am-a-post". In Italian, its title is "Io sono un post", so its slug will be created as "io-sono-un-post". Both will share the same ref
key, which can be something like "iamapost".
---
title: "I am a post"
ref: "iamapost"
---
en.json:
{
"iamapost": "blog/i-am-a-post"
}
it.json:
{
"iamapost": "blog/io-sono-un-post"
}
Now, the LocalesSwitcher.vue must be copied to something else, and the path string must be changed from:
path: this.$tp(
// ...using "ref" key translations,...
this.$t("ref." + this.$context.ref, this.currentLocale),
// ...and set the new locale!
this.currentLocale,
true
to
path: this.$tp(
// ...using "ref" key translations,...
this.$t("ref." + this.$page.post.ref, this.currentLocale),
// ...and set the new locale!
this.currentLocale,
true
This way, when opening a post, if we want to change the locale, address is correctly translated.
A new issue I have is that all posts, even if I put locale: "it-IT"
in the frontmatter, for example, are created with the default locale, which prevent gridsome to work as we want. There's a way to force a properties while retrieving file with filesystem source plugin? So that all PostIt will has it-IT as locale, PostPt pt-BR and so on?
@daaru00, we can see that GraphQL report all posts with the same default locale:
I tried forcing the correct locale from source-filesystem, as
{
use: '@gridsome/source-filesystem',
options: {
typeName: 'PostIt',
path: './src/content/posts/**/*.it.md',
context: {
locale: "it-IT"
}
},
},
but, naturally it didn't work, as this is not implemented. So, I tried with the folling under routes.js
:
{
path: '/pt/blog/:title',
component: './src/templates/PostPt.vue',
context: {
locale: 'pt-BR'
}
},
With this, a route is created: but it strangely prevent to navigate to my posts.
As I'm quite new to gridsome and graphql, it's probably a very simple issue to solve, but I would love some help...
Hi @arslabora,
it is not very easy to solve this issue.. I think the main problem is maintaining the relationship between the translated pages when they have different slug, title and content and serve them to the routing system so that it can redirect the user to the correct route (with a Vue router middleware for example).
I can propose a solution using a different approach, create the pages directly using the Gridsome Page API from gridsome.server.js
passing relations using page context:
module.exports = function (api) {
api.createPages(async ({ createPage, graphql }) => {
const { data } = await graphql(`
{
allPost(filter: { published: { eq: true } }) {
edges {
node {
id
slug
locale
fileInfo {
name
}
}
}
}
}
`);
for (const { node } of data.allPost.edges) {
const fileName = node.fileInfo.name.replace(`.${node.locale}`, "");
const { data: dataTranslations } = await graphql(`{
allPost(filter: { published: {eq: true}, fileInfo: {name: {regex: "^${fileName}"}} }) {
edges {
node {
id
slug
locale
fileInfo {
name
}
}
}
}
}`);
createPage({
path:
(node.locale !== "en" ? `/${node.locale}` : "") + `/${node.slug}/`,
component: "./src/templates/Post.vue",
context: {
id: node.id,
locale: node.locale,
translations: dataTranslations.allPost.edges.map(({ node }) => ({
path:
(node.locale !== "en" ? `/${node.locale}` : "") +
`/${node.slug}/`,
locale: node.locale
}))
},
route: {
meta: {
locale: node.locale
}
}
});
}
});
};
In the post template list the translations links:
<template>
<Layout>
<div class="post-title">
<h1 class="post-title__text">
{{ $page.post.title }}
</h1>
</div>
<div class="post content-box">
<div class="post__content" v-html="$page.post.content" />
</div>
Translations:
<ul>
<li v-for="translation in translations" :key="translation.locale">
<g-link class="post-card__link" :to="translation.path">
{{ translation.locale }}
</g-link>
</li>
</ul>
</Layout>
</template>
Here you can find a PoC with a ""working"" example of this solution: https://codesandbox.io/s/gridsome-blog-i18n-sqfkz?file=/gridsome.server.js (some parts are repeated, others somewhat hacky... it's just an idea).
Some attention points:
locale
that contain the locale code and slug
that contain the translated route path.en.md
or .it.md
) in order to link pages togetherI think it is difficult to find a pattern that satisfies everyone, for some things it is better to do it yourself the generation of the pages from gridsome.server.js
based on the data source and/or management preferences.
Fantastico, carissimo!
Thank you so much for your time, I'll play with your suggestion. Indeed, your suggested solution looks very elegant, and probably it will be the right direction.
I'll post here the results and continuing updating the starter. 💪
Italian power! 🇮🇹🍷
Dear @daaru00, I successfully included your idea to Gridsome i18h Starter. I'm working on a branch with much more content, so to allow to show various solutions.
However, I think that we could improve the plugin by allow for a way to properly set the locale of any content which has a locale
key in the frontmatter. Or, better, have a config boolean, named useFilenameToSetLocale
(or something similar), which check if a markdown file extension is prefixed by a locale string (such a .en.md, .it.md) and set the locale for it. Do you believe it is possible?
Hi @arslabora,
I'm working on a branch with much more content, so to allow to show various solutions.
good job :+1: let me know if you publish an article/blog post/repo, I am interested in seeing the final solution.
However, I think that we could improve the plugin by allow for a way to properly set the locale of any content which has a locale key in the frontmatter. Or, better, have a config boolean, named useFilenameToSetLocale (or something similar), which check if a markdown file extension is prefixed by a locale string (such a .en.md, .it.md) and set the locale for it. Do you believe it is possible?
I fully understand your need, but is a specific solution in combination with @gridsome/source-filesystem
plugin. The main idea behind this plugin is to give as much support as possible but remaining agnostic respect the plugin used. For example, you may need a specific implementation for Wordpress, Storyblok or any other service connected via GraphQL, it's very complex to cover all use cases.
Even the use of a prefix on the file extension can be opinionable, maybe there are those who prefer using different folders. See the ability to create page using gridsome.server.js
gives you the complete control over your personal solution.
Perhaps such an helper function like this:
const { createLocalizedPages } = require('gridsome-plugin-i18n/helper');
module.exports = function (api) {
api.createPages(async ({ createPage, graphql }) => {
// ....
await createLocalizedPages(createPage, {
route: '/my-custom-path',
component: './src/templates/Post.vue',
locale: 'en'
})
});
};
just to help for route and context parameters. (I don't know if it is possible to extend the api object or its parameter).
Following the instructions at gridsome i18n plugin documentation, I'm currently experiencing a very weird behaviour. I have my i18n plugin configured this way:
and a
routes.js
file with the following content:As it is listed, it works perfectly, however the about slug aren't localised; they still are called "about", only their locale code prefix is changed.
If I translate the path strings in
routes.js
, as demonstrated in the documentationwhile having
<g-link class="nav__link" :to="$tp('/about')">About</g-link>
in my headers, it still expect paths as /en/about, /it/about and so on, so if navigate to /it/about, for example, it thrown an error, as the localised path for the italian language is /it/chi-siamo, not /it/about.If I follow another logic, so using translated strings and locales, as something like that in my locale JSON:
and links as
<g-link class="nav__link" :to="$t('code') + '/' + $t('slug.about')">About</g-link>
in my header, it works, but if I change the locale while in /it/chi-siamo, for example, it again thrown an error, because it expect to land to a page called /en/chi-siamo, which do not exists.Of course, I could write alternatives routes for each and every page with a fallback path expected as coming from a language but, as I usually work with website translated in more than five languages, it will became absurd.
It's pretty probable that I missed something from the documentation, however I'm quite lost at the moment. Any clue? Should I give up and remain with unlocalised path? Hope not... ;)