Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

this.$forceUpdate does not refresh the whole page when choosing another language #22

Open
scarneros opened this issue May 31, 2021 · 12 comments

Comments

@scarneros
Copy link

Expected behavior

All Vue components on the page are updated when the language is changed.

Current behavior

Only the language selector is updated and the rest of the page remains the same. this.$forceUpdate() seems not to work.

Versions

  • Laravel: 8.43.0
  • Matice: 1.1.4
  • Vue: 3.0.5

Description

The backend package works fine and the translations on the frontend, when I change the language on the Laravel config side, works wonderfully too. The problem here is that when I change the language with a selector I have created on the frontend, the this.$forceUpdate() method won't refresh all components on the page, so the translation only applies to the selector itself and nowhere else on the page.

I am using Vue3 if that matters. Here is my code on app.js:

...

createApp({
    render: () =>
        h(InertiaApp, {
            initialPage: JSON.parse(el.dataset.page),
            resolveComponent: (name) => require(`./Pages/${name}`).default,
        }),
})
    .mixin({
        methods: {
            route,
            $trans: trans,
            $__: __,
            $transChoice: transChoice,
            $setLocale(locale) {
                if (this.$locale() !== locale) {
                    setLocale(locale);
                    this.$forceUpdate(); // Refresh the vue instance(The whole app in case of SPA) after the locale changes.
                }
            },
            // The current locale
            $locale() {
                return getLocale()
            },
            // A listing of the available locales
            $locales() {
                return locales()
            }
        }
    })
    .use(InertiaPlugin)
    .mount(el);
    
    ...

and this is the code that calls the $setLocale(locale) method on my language selector component:

...

<select v-on:change="$setLocale($event.target.value)" v-model="selectedLang">
    <option v-for="(lang, code) in languages" :value="code">
        {{ lang }}
    </option>
</select>

...
@GENL
Copy link
Owner

GENL commented Jun 5, 2021

In vue 3. If you want to refresh the app, store an instance of the app, then call $forceUpdate on that instance.

const MaticeMixin = {
  methods: {
    route,
    $trans: trans,
    $__: __,
    $transChoice: transChoice,
    $setLocale(locale) {
      if (locale !== getLocale()) {
        setLocale(locale);
        app.$forceUpdate() // Refresh the vue instance after locale change.
      }
    },
    // The current locale
    $locale() {
      return getLocale()
    },
    $locales() {
      return locales()
    },
  },
}

const root = document.querySelector('#app');

let app = createApp({
  render: () => h(App, {
    initialPage: JSON.parse(root.dataset.page),
    resolveComponent: (name) => require(`./Pages/${name}`).default,
  }),
}).use(plugin)
  .mixin(MaticeMixin)
  .mount(root)

@GENL
Copy link
Owner

GENL commented Jun 5, 2021

Also, I suggest you upgrade to the latest version of matice. There are no breaking changes, but improved performance and more features,

@GENL GENL closed this as completed Jun 11, 2021
@alessiovietri
Copy link

alessiovietri commented Sep 13, 2021

This solution didn't work for me with this combination:

  • "genl/matice": "^1.1",
  • "inertiajs/inertia-laravel": "^0.4.3",
  • "laravel/framework": "^8.54",
  • "laravel/jetstream": "^2.3",

But I made it work using Inertia:

// resoures/js/app.js

require('./bootstrap');

import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/inertia-vue3';
import { InertiaProgress } from '@inertiajs/progress';
import '@fortawesome/fontawesome-free/js/all.js';
import { __, trans, setLocale, getLocale, transChoice, MaticeLocalizationConfig, locales } from "matice"

const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';

const MaticeMixin = {
    methods: {
        route,
        $trans: trans,
        $__: __,
        $transChoice: transChoice,
        $setLocale(locale) {
            if (locale !== getLocale()) {
                setLocale(locale);
                this.$inertia.get(route('locale', {'locale': locale}));
                this.$inertia.reload();
            }
        },
        $locale() {
            return getLocale()
        },
        $locales() {
            return locales()
        },
    },
}

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => require(`./Pages/${name}.vue`),
    setup({ el, app, props, plugin }) {
        return createApp({ render: () => h(app, props) })
            .use(plugin)
            .mixin({
                methods: {
                    route,
                }
            })
            .mixin(MaticeMixin)
            .mount(el);
    },
});

InertiaProgress.init({ color: '#2980B9' });

Using this.$inertia.reload(); works like a charm on main pages:
https://ibb.co/B345zYg

but not on auth pages:
https://ibb.co/0DB220z

Any hint to fix this?

@GENL
Copy link
Owner

GENL commented Sep 13, 2021

Can you share the error output if any?
First check if the issue comes from Matice or inertia.
If the error is related to Matice, then make sure it is well loaded on your auth page. The best way of doing that is to check if a single component updates when the language local changes.

Please share with me the code of both pages.

Also make sure sure to use the latest version of Matice, v1.6.

@GENL GENL reopened this Sep 13, 2021
@GENL
Copy link
Owner

GENL commented Sep 13, 2021

On the links you provided, I connot interact with the local field. So it's impossible to test,

@alessiovietri
Copy link

alessiovietri commented Sep 14, 2021

There's no error output, everything works fine, but the view doesn't reload (only on auth pages). Anyway, this is not so important, I just wanted to help others using Inertia like me. But if you have any suggestion to make my view work, it would be great!

You cannot interact with my links because are images, I cannot publish my application at the moment

@alessiovietri
Copy link

alessiovietri commented Sep 14, 2021

UPDATE:
Sometimes the "non-reload" happens on labels and buttons on other pages too. Maybe is something related to Vue or Inertia themselves, I don't know, but I'm writing it down here to make it work for everybody

@GENL
Copy link
Owner

GENL commented Sep 15, 2021

Share your portion of the code with me, please.

@alessiovietri
Copy link

alessiovietri commented Sep 16, 2021

This one is the same as before:

// resoures/js/app.js

require('./bootstrap');

import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/inertia-vue3';
import { InertiaProgress } from '@inertiajs/progress';
import '@fortawesome/fontawesome-free/js/all.js';
import { __, trans, setLocale, getLocale, transChoice, MaticeLocalizationConfig, locales } from "matice"

const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';

const MaticeMixin = {
    methods: {
        route,
        $trans: trans,
        $__: __,
        $transChoice: transChoice,
        $setLocale(locale) {
            if (locale !== getLocale()) {
                setLocale(locale);
                this.$inertia.get(route('locale', {'locale': locale}));
                this.$inertia.reload();
            }
        },
        $locale() {
            return getLocale()
        },
        $locales() {
            return locales()
        },
    },
}

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => require(`./Pages/${name}.vue`),
    setup({ el, app, props, plugin }) {
        return createApp({ render: () => h(app, props) })
            .use(plugin)
            .mixin({
                methods: {
                    route,
                }
            })
            .mixin(MaticeMixin)
            .mount(el);
    },
});

InertiaProgress.init({ color: '#2980B9' });

This is my locale selector (imported inside my layout):

<template>
    <select v-on:change="$setLocale($event.target.value)">
        <option v-for="(lang) in $locales()" :value="lang" :key="lang" :selected="$locale() == lang">
            {{ lang }}
        </option>
    </select>
</template>

<script>
    import { defineComponent } from 'vue'

    export default defineComponent({
        props: [],

        methods: {
        }
    })
</script>

And this is my login page:

<template>
    <Head :title="$__('auth.login.title')" />

    <jet-authentication-card>
        <template #logo>
            <jet-authentication-card-logo />
        </template>

        <jet-validation-errors class="mb-4" />

        <div v-if="status" class="mb-4 font-medium text-sm text-green-600">
            {{ status }}
        </div>

        <form @submit.prevent="submit">
            <div>
                <jet-label for="email" :value="$__('auth.login.email-or-username')" />
                <jet-input id="email" type="text" class="mt-1 block w-full" v-model="form.email" required autofocus />
            </div>

            <div class="mt-4">
                <jet-label for="password" :value="$__('auth.login.password')" />
                <jet-input id="password" type="password" class="mt-1 block w-full" v-model="form.password" required autocomplete="current-password" />
            </div>

            <div class="block mt-4">
                <label class="flex items-center">
                    <jet-checkbox name="remember" v-model:checked="form.remember" />
                    <span class="ml-2 text-sm text-gray-600">
                        {{ $__('auth.login.remember-me') }}
                    </span>
                </label>
            </div>

            <div class="flex items-center justify-end mt-4">
                <Link v-if="canResetPassword" :href="route('password.request')" class="underline text-sm text-gray-600 hover:text-gray-900">
                    {{ $__('auth.login.forgot-password') }}
                </Link>

                <jet-button class="ml-4" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
                    {{ $__('auth.login.login') }}
                </jet-button>
            </div>
        </form>
        <hr class="my-5">
        <div class="text-center px-5" v-html="$__('auth.login.register', { args: { url: route('register') } })">
        </div>
    </jet-authentication-card>
</template>

<script>
    import { defineComponent } from 'vue'
    import JetAuthenticationCard from '@/Jetstream/AuthenticationCard.vue'
    import JetAuthenticationCardLogo from '@/Jetstream/AuthenticationCardLogo.vue'
    import JetButton from '@/Jetstream/Button.vue'
    import JetInput from '@/Jetstream/Input.vue'
    import JetCheckbox from '@/Jetstream/Checkbox.vue'
    import JetLabel from '@/Jetstream/Label.vue'
    import JetValidationErrors from '@/Jetstream/ValidationErrors.vue'
    import { Head, Link } from '@inertiajs/inertia-vue3';

    export default defineComponent({
        components: {
            Head,
            JetAuthenticationCard,
            JetAuthenticationCardLogo,
            JetButton,
            JetInput,
            JetCheckbox,
            JetLabel,
            JetValidationErrors,
            Link,
        },

        props: {
            canResetPassword: Boolean,
            status: String
        },

        data() {
            return {
                form: this.$inertia.form({
                    email: '',
                    password: '',
                    remember: false
                })
            }
        },

        methods: {
            submit() {
                this.form
                    .transform(data => ({
                        ... data,
                        remember: this.form.remember ? 'on' : ''
                    }))
                    .post(this.route('login'), {
                        onFinish: () => this.form.reset('password'),
                    });
            }
        }
    })
</script>

That's all

@GENL
Copy link
Owner

GENL commented Sep 18, 2021

$setLocale(locale) {
            if (locale !== getLocale()) {
                setLocale(locale);
                this.$inertia.get(route('locale', {'locale': locale}));
                this.$inertia.reload();
            }
  }

Matice is a frontend package to handle Laravel's translations.
Make sure everything works fine locally without the backend call.
Then, you should wait for the GET call to finish before calling inertia reload.

I plan to make a release, to change the code to make the locale update reactive. So it will not be required to reload the page anymore. Just give me a couple of days.

@alessiovietri
Copy link

I tried to put reload function call inside a callback (onSuccess and onFinish), but it causes a modal to appear because of Inertia/Jetstream functionalities. I also tried with a simple setTimeout, but nothing changed. I'll wait for your update! Thanks a lot for your support

@alessiovietri
Copy link

And this is the reason why I make a GET call:

Route::get('/locale/{locale}', function ($locale) {
    app()->setLocale($locale);
    session()->put('locale', $locale);
    $user = Auth::user();
    if($user){
        $user->profile->locale_code = $locale;
        $user->profile->save();
    }
})->name('locale');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants