xiCO2k / laravel-vue-i18n

Allows to connect your `Laravel` Framework translation files with `Vue`.
MIT License
594 stars 49 forks source link

Translation flickering on full page load if lang other than 'en' #117

Closed onlime closed 1 year ago

onlime commented 1 year ago

First of all, thanks @xiCO2k for this great plugin! I am planning to migrate from vue-i8n (with my own json parsing implementation) to laravel-vue-i18n and got it almost working. One thing still is not working as expected:

When using another language than en as default/primary language, I am always experiencing a short flickering on a full page load (not on XHR/Inertia requests). The English translations show up for some milliseconds and only then get replaced by the German translations.

My setup in a Laravel/Inertia.js/Vue3 project (bootstrapped by Jetstream) is as follows:

import './bootstrap'
import '../css/app.css'

import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
import { i18nVue } from 'laravel-vue-i18n'

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

createInertiaApp({
    title: (title) => [title, appName].filter(Boolean).join(' - '),
    resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
    setup({ el, App, props, plugin }) {
        return createApp({ render: () => h(App, props) })
            .use(plugin)
            .use(i18nVue, {
                lang: 'de',
                resolve: async (lang) => {
                    const langs = import.meta.glob('../../lang/*.json')
                    return await langs[`../../lang/${lang}.json`]()
                },
            })
            .mount(el)
    },
})

I only have those two language files (not loading the php files):

lang/
├── de.json
└── en.json

In my app.blade.php layout, I am setting the lang like this:

<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">

But even if I override this to a static <html lang="de">, laravel-vue-i18n would still flicker to English translations and only showing the German ones afterwards on full page reload.

I've also tried to configure i18nVue plugin with both lang: 'de', fallbackLang: 'de', same problem. Somehow, the plugin always first loads English and only overrides the language after rendering.

I have never experienced such a flickering with vue-i18n which I configured like this (but I must admin that the messages object loads all available languages, which might be not as performant):

// Composable i18n.js
import { createI18n } from 'vue-i18n'

function loadLocaleMessages() {
    const messages = {}
    const locales = import.meta.glob('../../lang/[a-zA-Z_]+.json', { eager: true })
    for (const file in locales) {
        messages[file.replace(/(.*\/|\.json)/g, '')] = locales[file].default
    }
    return messages
}

export default createI18n({
    legacy: false,
    globalInjection: true,
    locale: 'de',
    fallbackLocale: 'en',
    messages,
})

// app.js
import i18n from './i18n'

createInertiaApp({
    // ...
    setup({ el, App, props, plugin }) {
        return createApp({ render: () => h(App, props) })
            // ...
            .use(plugin)
            .use(i18n)
            .mount(el)
    },
})

Any way I can achieve translation loading in laravel-vue-i18n without flickering? Or if not possible, how can I define de as primary language, so the flickering would only happen if frontend language is set to en?

xiCO2k commented 1 year ago

Will check that out, thanks for the report

xiCO2k commented 1 year ago

are you using the $trans('With English?') or with de by default?

onlime commented 1 year ago

are you using the $trans('With English?') or with de by default?

I don't quite get your question. Yes, I am using English translation keys, so I'm using $trans('With English?') with the following de.json:

{
    "With English?": "Mit Englisch?",

... while most of my en.json is empty, as the English phrases are already correct English and don't need to be translated. I only define English translations for key based translations.

But the same flickering also happens if I put the translation into en.json:

{
    "With English?": "With English?",

Or did I misunderstand your question? I use de as default language by setting it on plugin initialization in my app.js, as described above:

            .use(i18nVue, {
                lang: 'de',
                resolve: async (lang) => {
                    const langs = import.meta.glob('../../lang/*.json')
                    return await langs[`../../lang/${lang}.json`]()
                },
            })
mpenna commented 1 year ago

I'm also experiencing this issue: a quick but noticeable flicker of the 'en-US' locale on full page loads, triggered either via a browser navigation action or a Vite server start or restart. I even thought it could have something to do with the fact that my OS's default language was configured as American English, and that I was trying to set the app's default locale to pt_BR. Apparently, that was not the case: upon swapping the OS's default language to make it match that of the app's, the flickering issue still occurs.

mpenna commented 1 year ago

I finally figured out what was causing the language swapping, at least in my case. I'm using the Quasar framework, and it was Quasar's language configuration (which was defaulting to en-US) that was affecting the HTML lang attribute on page reloads. However, even though that has now been fixed, all screen elements that are being translated/localized (via the $t(...) function) are still being briefly displayed in the language they have been originally written, code-wise, before the translation engine kicks-in, as it seems. Any ideas on how to fix this?

ronnieparalejas commented 1 year ago

I have similar issue. When I refresh the page it will show translation key first like "admin.dashboard" then value "Dashboard". Any comment about this flickering issue?

chris-hud commented 1 year ago

Working through some issues and saw this ticket, and realized it was a solve for this so wanted to let you guys know. https://github.com/xiCO2k/laravel-vue-i18n/issues/93

xiCO2k commented 1 year ago

Yes a solution for that is this:

const app = createApp({ render: () => h(App, props) });

return app
.use(i18nVue, {
    resolve: async lang => {
        const langs = import.meta.glob('../../lang/*.json');
        return await langs[`../../lang/${lang}.json`]();
    },
    onLoad: () => {
        app.mount(el); // Mounted here so translations are loaded before vue.
    }
});
bbauti commented 8 months ago

Hello! when i apply this fix, the loadLanguageAsync function stops working. it says that the app is already mounted.

novack96 commented 6 months ago

Hello! when i apply this fix, the loadLanguageAsync function stops working. it says that the app is already mounted.

I am not sure if you solved the issue, but it could be useful for someone later. You just need to check whether the Vue app is mounted to the el. For example like this:

const app = createApp({ render: () => h(App, props) });

return app
.use(i18nVue, {
    resolve: async lang => {
        const langs = import.meta.glob('../../lang/*.json');
        return await langs[`../../lang/${lang}.json`]();
    },
    onLoad: () => {
        // return early if app is mounted already
        if(el && el.__vue_app__){
             return;
        }
        app.mount(el); // Mounted here so translations are loaded before vue.
    }
});