Making a multi language site with Vue.js - vuejs2

I am not so experienced with Vue.js but I am trying to make a multi language site with Vue.js and wondering if there is an effective approach to it. For instance I do not want to create duplicate views for each page I have. Is there any plugin to use for that and effective routing to handle the links?
This is what I came up with so far
On my vue page Menu.Vue.
<v-list-item>
<v-list-item-title>English</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-title>
<router-link to="AnotherLanguagepage">
AnotherLanguagepage
</router-link>
</v-list-item-title>
Router's index.js
{
path: '/page_language1',
name: 'Display',
component: Display1,
meta: {
requiresAuth: true
}
},
{
path: '/page_language2',
name: 'Display',
component: Display2,
meta: {
requiresAuth: true
}
},
So I have views for the two languages but this is not scalable approach.

If you look for a multi language site with Vue.js, you better use Vue i18n plugin.
No, you don't need to have several views or data inside your page/components. Basically, you'll have a lang folder with en.js, es.js... (as much lang as you need).
// en.js
{
"general": {
"myListItemTitle": "My English title"
}
}
// fr.js
{
"general": {
"myListItemTitle": "Mon titre français"
}
}
<v-list-item>
<v-list-item-title>{{ $t('general.myListItemTitle') }}</v-list-item-title>
</v-list-item>
// $t is calling plugin and refers to currentLocale (en, es...). Example: en.js
Good luck !

Related

Laravel Inertia Ziggy Link Routing Issue

I'm currently having a problem in my routing. Here's the scenario:
Inertia is working fine when there's no id query.
But after navigating to edit and I want to click any of the navigation links like clicking the Dashboard link, it throws a 404 code saying the page does not exist. Simply because instead of removing the /category/{id}, it adds dashboard at the end instead of removing the query.
Is there a way to fix this by not violating the inertia routing?
Here's the code:
Authenticated Layout
const navigation = [
{ name: 'Dashboard', href: 'dashboard', current: false },
{ name: 'Category', href: 'category', current: false },
]
<nav class="hidden lg:flex lg:space-x-8 lg:py-2" aria-label="Global">
<Link v-for="item in navigation" :key="item.name"
:href="item.href" :class="[item.current ? 'bg-gray-100
text-gray-900' : 'text-gray-900 hover:bg-gray-50
hover:text-gray-900', 'rounded-md py-2 px-3 inline-flex
items-center text-sm font-medium']" :aria-
current="item.current ? 'page' : undefined">{{ item.name
}}</Link>
</nav>
Have you tried using Ziggy? Ziggy Github repo
Then the routes you create using Laravel are made available using the same route function in Vue. It works well with inertia when creating laravel apps.
<Link
:href="route('frontend.categories.show', [categories, post.slug])"
>
If you check your console in the page inspector it should show you the ziggy routes pulled from the "web.php" in json format.
It automatically makes the standard Controller class functions available for your routes in javascript (so index, create, edit, show, destroy functions).
I would be happy to share my code I created from a tutorial utilising breeze like you are.
Solved it. Just needed to add "/" to the href navigation object.
const navigation = [
{ name: 'Dashboard', href: '/dashboard', current: false },
{ name: 'Category', href: '/category', current: false },
]

What would be the best way to use vue routes?

I'm a bit new to Vue routes. I have a table in Home.vue and each row has a button in order to go to the details. I'll try to explain what I imagine. I want the redirection to open a new screen Overview with a sidebar that has 3 options: Overview, Commits and Files. My problem is to understand what should be the parent and what should the child. I'm sure that Commits and Files are children but should Overview be also a child or the parent of Commits and Files? The row that redirects to details:
<router-link :to="{ 'name': 'details', 'params': { 'tool': tool } }">{{id}}</router-link>
The routes that I currently have:
const DetailsChildren = [
{
path: 'commits/:tool',
name: 'commits',
component: commits
},
{
path: 'files/:tool',
name: 'files',
component: files
}
];
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
}
{
path: '/overview/:tool',
name: 'details',
component: details,
children: DetailsChildren
}
],
mode: 'history'
});
And DetailsChildren is as follows:
<template>
<div class="main">
<sidebar />
<router-view />
</div>
</template>
Currently DetailsChildren is Overview. But I think DetailsChildren should be the parent of Overview, Files and Commits and then I need to create another component Overview. But then I have two problems. First one is how I load Overview when I move from the table to DetailsChildren? The second one is that I want the route to be /overview/:tool. I'm a bit confused. What would be best way to handle the routes in this situation?
What you want to do is have three children under your details route, each with their own absolute path from /.
const DetailsChildren = [
{
path: '/overview/:tool',
name: 'overview',
component: overview
},
{
path: '/commits/:tool',
name: 'commits',
component: commits
},
{
path: '/files/:tool',
name: 'files',
component: files
}
];
This will create the following path mappings
/overview/:tool
Top-level component: details
Child component: overview
/commits/:tool
Top-level component: details
Child component: commits
/files/:tool
Top-level component: details
Child component: files
See the guide on Nested Routes.
You may think you can use an empty path for the overview route but this would then require the URL to have a trailing slash (ie /overview/tool/) and I figured you don't want that.
It's also recommended to remove the name from your details route and instead, link to the default child route (ie overview). Eg
<router-link :to="{ name: 'overview', params: { tool } }">{{id}}</router-link>
Otherwise, the router gets confused about which to display, the parent (empty) or child.
Your sidebar links can simply use the child route names to create their links, eg
<nav>
<ul>
<li>
<router-link :to="{name: 'overview'}">Overview</router-link>
</li>
<li>
<router-link :to="{name: 'commits'}">Commits</router-link>
</li>
<li>
<router-link :to="{name: 'files'}">Files</router-link>
</li>
</ul>
</nav>

Where should route meta data be loaded in a Vue app?

I'm in the process of setting up a VueJs SPA. I'm using vue-router and I'm trying to find the best solution to the following problem. I have a series of routes. Each of which needs to call an API to get the meta data for the given ID.
/industry/:id/overview
/industry/:id/top-stories
/industry/:id/top-tweets
/brand/:id/overview
/brand/:id/top-stories
/brand/:id/top-tweets
I've been looking at using created or beforeRouteEnter/beforeRouteUpdate and I'm a bit lost. Ideally, I would only fetch new data when a new /industry/:id is reached, not when navigating between pages within the same ID. Also, I'd like to avoid having to define the fetch to grab data in every page component. Also don't want to over complicate this, so my question is, Is there a standard method for tackling this issue?
Clarification:
When I say meta here, I mean data returned from an API about the given industry or brand which I pull using the ID in the route. The api call includes the name of the industry/brand which I want to have on page as soon as the page is presented to the user.
I have something similar. I tackle this using the following approach:
I use the same component for all /industry/:id Vue likes to reuse components wherever it can so if two routes (for example /industry/:id/overview and /industry/:id/top-stories) are using the same component it will stay the same.
What does change, however, is the route meta. So if you add a page key to the meta object in the route objects, and probably add a computed property called page that return this.$route.meta.page, you can use v-if attributes to conditionally render any component. So you might have something like <div v-if="page === 'overview'"></div><div v-else-if="page==='top-stories'"></div>
What this allows you to do is fetch all the data from the API during created or mounted lifecycle and store it as the state. Since the route change doesn't reload the component the state stays the same.
Here is a code example
// router.js
const Project = () =>
import(/* webpackChunkName: "projects" */ "./views/projects/_id");
export default new Router({
mode: "history",
routes: [
{
path: "/projects/:project_id/views",
name: "ViewProject",
component: Project,
meta: {
page: "views",
}
},
{
path: "/projects/:project_id/export",
name: "ExportProject",
component: Project,
meta: {
page: "exports"
}
},
{
path: "/projects/:project_id/recommendations",
name: "ProjectRecommendations",
component: Project,
meta: {
page: "recommendations"
}
},
]
});
And here is the template
<template>
<div v-if="project">
<h1>{{ project.name }}</h1>
<router-link :to="/project/someid/views">Views</router-link>
<router-link :to="/project/someid/exports">Exports</router-link>
<router-link :to="/project/someid/recommendations">Recommendations</router-link>
<ul v-if="page==='views">
<li v-for="(view, i) in project.views" :key="i">{{ views }}</div>
</ul>
<ul v-else-if="page==='exports">
<li v-for="(export, i) in project.exports" :key="i">{{ export }}</div>
</ul>
<ul v-else-if="page==='recommendations">
<li v-for="(recommendation, i) in project.recommendations" :key="i">{{ recommendation }}</div>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
project: null
}
},
computed: {
page() {
return this.$route.meta.page;
}
},
mounted() {
this.getProject()
},
methods: {
getProject() {
axios
.get(`/projects/someid`)
.then(res => this.project = res.data)
}
}
}
</script>

Vue - Translation in single file component

I'm using vue-i18n for my translations and it works great! But when I'm using the this.$t() function inside the data function of a single file component the translation is not working.
<template>
<VFooter
app
height="auto"
color="secondary">
<VLayout
justify-center
row
wrap>
<VBtn
v-for="link in links"
:key="link.name"
:to="{ name: link.name }"
flat
round
active-class>
{{ link.label }}
</VBtn>
<VFlex
py-3
text-xs-center
xs12>
©{{ currentYear }} — <strong>{{ $t('site_name') }}</strong>
</VFlex>
</VLayout>
</VFooter>
</template>
<script>
export default {
name: 'TheSiteFooter',
data() {
return {
links: [
{ name: 'what-is-pinshop', label: this.$t('footer.what_is_pinshop') },
{ name: 'contact-us', label: this.$t('footer.contact_us') },
{ name: 'cookie-policy', label: this.$t('footer.cookie_policy') },
{ name: 'privacy-policy', label: this.$t('footer.privacy_policy') },
{ name: 'terms-and-conditions', label: this.$t('footer.terms_and_conditions') },
],
};
},
computed: {
currentYear() {
return new Date().getFullYear();
},
},
};
</script>
But, if I instead change data with only the key of translation and then in my template use e.g {{ $t('footer.what_is_pinshop') }} the translation loaded is correct. Why does this happen? I'm using the beforeEnter router guard to change the translation. I have followed this example.
UPDATE
If I put links as a computed property the translation works. So why it does not happen only in data property? I also tried with this.$i18n.t(), but nothing
This is, because of the vue lifecycle. Push your link data into the array by using the created hook. Keep you data(model) clean and do not call functions in it. You call this up before all events and reactivity mechanisms have ever been registered.
lifecycle: https://v2.vuejs.org/v2/guide/instance.html
if you're interested how it works: https://github.com/kazupon/vue-i18n/tree/dev/src
UPDATE
I just showered and thought again about it. In depth this is because of the reactivity mechanism. You initialize your data with a function and vue cannot detect if the returned value has changed. See how it works: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty in vue 3 this is replaced by https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Dynamic Vue Router

I am researching whether a vue router is the best approach for the following scenario:
I have a page containing 'n' number of divs. Each of the divs have different content inside them. When a user clicks on a button in the div, I would like the div to open in a separate browser window (including its contents).
Can a route name/component be created on the fly to route to? Since I have 'n' number of divs, that are created dynamically, I cannot hard-code name/components for each one
<router-link :to="{ name: 'fooRoute'}" target="_blank">
Link Text
</router-link>
I want to avoid the same component instance being used (via route with params) since I may want multiple divs to be open at the same time (each one in their own browser window)
If the link is opening in a separate window, it makes no sense to use a <router-link> component as the application will load from scratch in any case. You can use an anchor element instead and generate the href property dynamically for each div.
To answer your questions:
A route name cannot be created dynamically since all routes must be defined at the beginning, when the app (along with router) is being initialized. That said, you can have a dynamic route and then dynamically generate different paths that will be matched by that route.
There is no way for the same component instance to be reused if it's running in a separate browser window/tab.
It is possible to create dynamic router name.
profileList.vue
<template>
<main>
<b-container>
<b-card
v-for="username in ['a', 'b']"
:key="username"
>
<b-link :to="{ name: profileType + 'Profile', params: { [profileType + 'name']: username }}">Details</b-link>
</b-container>
</main>
</template>
<script>
export default {
name: 'profileList',
data () {
return {
profileType: ''
}
},
watch: {
// Call again the method if the route changes.
'$route': function () {
this.whatPageLoaded()
}
},
created () {
this.whatPageLoaded()
},
methods: {
whatPageLoaded () {
this.profileType = this.$route.path // /user or /place
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
</style>
b-container, b-card, b-link are taken from bootstrap-vue, so you can freely change it.
router.js
const router = new Router({
mode: 'hash',
base: process.env.BASE_URL,
linkExactActiveClass: 'active',
routes: [
// USERS
{
path: '/user/:username',
name: userProfile,
component: userProfile
},
{
path: '/user',
name: 'userList',
component: profileList
},
// PLACES
{
path: '/place/:placename',
name: placeProfile,
component: placeProfile
},
{
path: '/place',
name: 'placeList',
component: ProfileList
}
]
})