xiCO2k / laravel-vue-i18n

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

Translation not showing on initial app launch #62

Closed iMaBo closed 2 years ago

iMaBo commented 2 years ago

Hi,

We recently updated our project to use vite instead of webpack and also modified also our vuejs files to make it compatible, everything is working well except for one thing. Yesterday I noticed that the requested translations did not load on the first load of the project when you refresh or visit the website.

For example when I put the url in my webbrowser and visit it, the translations will be in English but if I click on 1 menu item to go to another page, everything will change to the Dutch translations (which is correct).

Our app.js is as follows:

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

// Import modules...
import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/inertia-vue3'
import { InertiaProgress } from '@inertiajs/progress'
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { library } from "@fortawesome/fontawesome-svg-core";
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { faEdit } from "@fortawesome/free-solid-svg-icons";
import { faEye } from "@fortawesome/free-solid-svg-icons";
import { faSun } from "@fortawesome/free-solid-svg-icons";
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { i18nVue } from 'laravel-vue-i18n';

library.add(faInfoCircle);
library.add(faEdit);
library.add(faEye);
library.add(faSun);

  createInertiaApp({
      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, {
                  resolve: async lang => {
                      const langs = import.meta.glob('../../lang/*.json');
                      return await langs[`../../lang/${lang}.json`]();
                  }
              })
              .mixin({ methods: { route, hasAnyPermission: function (permissions) {
                      var allPermissions = this.$page.props.auth.can;
                      var hasPermission = false;

                      permissions.forEach(function(item){
                          if(allPermissions[item]) hasPermission = true;
                      });
                      return hasPermission;
                  } } })
              .mixin({ methods: { route } })
              .component("font-awesome-icon", FontAwesomeIcon)
              .mount(el);
      },
  });

and in our vuejs files we use {{ $t('translation') }} to load the translations for a specific text, what can be the reason that the translation only load when you navigate to another page?

xiCO2k commented 2 years ago

Hey @iMaBo, everything looks good to me, can you check a couple things to help me debug it:

Thanks.

iMaBo commented 2 years ago

Hey @xiCO2k

xiCO2k commented 2 years ago

Thanks, I think I found the issue, can you try again with v2.2.1 version.

Let me know if that is working well.

Thanks, Francisco

iMaBo commented 2 years ago

Just updated the package to version v2.2.1 and deployed it, and it seems to be fixed now. The translations do get loaded on the initial launch of the app.

Much appreciated for the quick fix 😄

xiCO2k commented 2 years ago

No problem, anytime.

diego-lipinski-de-castro commented 1 year ago

iam having the same problem, on v2.2.2, vue v3.2.31, cant find why

xiCO2k commented 1 year ago

Can you try v2.3.0. and check if there is any issue on the console?

Thanks.

diego-lipinski-de-castro commented 1 year ago

Still didnt seem to work, I can workaround this by adding :key to the root component, and then, in the onMounted lifecycle, i can inside a 50ms timeout update the key to force the entire app to re-render and then it will update the ui with the correct language, but its a hack, the ui always flashes the content because of this.

You can check the code

https://github.com/diego-lipinski-de-castro/linkarme

xiCO2k commented 1 year ago

Awesome, will take a look on that.

xiCO2k commented 1 year ago

Hey @diego-lipinski-de-castro just checked and looks like to avoid that (since the language is being read async) you may need to change your resolve method to the SSR example like this:

resolve: lang => {
    const langs = import.meta.globEager('../../lang/*.json');
    return langs[`../../lang/${lang}.json`].default;
}
diego-lipinski-de-castro commented 1 year ago

Still doesnt seem to work unfortunately, is there an working example I could test of this working? or maybe another way of testing

xiCO2k commented 1 year ago

made that switch on your repo, then I did run vite build and that was it.

Mondotosz commented 1 year ago

edit: I found out why it didn't work while writing this and testing on the side and thought it might be interesting for others. and still uploaded it alongside what I found at the end.

made that switch on your repo, then I did run vite build and that was it.

I tried it on my project and it still doesn't work on the first page load. I'm sorry I can't share the full repository as it is a project I'm working on as an intern but here are some insights which might be useful.

I'm working with the jetstream/inertia preset and the specific use case where I'm having this issue is when I'm loading graphs with chartjs.

here's the abstract class I made to get the translations

import { trans, isLoaded } from 'laravel-vue-i18n'

export abstract class Lang {
    public static keys: Readonly<object>
    public static fallbacks: Readonly<object>

    public static getLang(keyName: string | number) {
        return this.keys[keyName] ?? null
    }

    public static getFallback(keyName: string | number) {
        return this.fallbacks[keyName] ?? null
    }

    public static get translations() {
        // if (isLoaded()) {
            return Object.assign({},
                ...Object.entries(this.keys)
                    .map(([index, value]) =>
                        ({ [index]: trans(value) }))
            )
        // }

        // return this.fallbacks
    }

    public static translate(keyName: string | number): string | null {
        const key: string | undefined = this.keys[keyName]

        if (key === undefined) {
            return null
        }

        if (isLoaded()) {
            return trans(key)
        }

        return this.fallbacks[keyName] ?? keyName
    }
}

The LogLevel class which extends this

import { Lang } from "../Base/Lang";

export class LogLevelLang extends Lang {
    public static keys = Object.freeze({
        1: 'activity.emergency',
        2: 'activity.alert',
        3: 'activity.critical',
        4: 'activity.error',
        5: 'activity.warning',
        6: 'activity.notice',
        7: 'activity.info',
        8: 'activity.debug',
    })
    public static fallbacks = Object.freeze({
        1: 'Emergency',
        2: 'Alert',
        3: 'Critical',
        4: 'Error',
        5: 'Warning',
        6: 'Notice',
        7: 'Info',
        8: 'Debug',
    })
}

and what I'm doing inside the setup

<script  setup>
//...
const activitiesMeta = Meta.new()
    .doughnut()
    .setLabels(LogLevelLang.translations)
    .setColors(LogLevelColor.colors)
    .title({
        display: true,
        text: 'Activity levels'
    })
    .setTooltipPercentage()

const activitiesStats = computed(() => datasetGenerator(props.stats.activities, activitiesMeta))
//...
</script>

<template>
    ...
    <Doughnut :chart-data="activitiesStats" :chart-options="activitiesMeta.options" />
    ...
</template>

I tested some things while writing this and found out why my code didn't work as intended. When using the trans function this way

const tmp = trans('my.translation.key')

the translation would occure before the translation got loaded and I was able to fix this by using

const tmp = computed(()=>trans('my.translation.key'))

In my specific case I had to turn activitiesMeta into a computed property and pass it to the activities computed property using .value

As I'm still quite new to vue I'm not quite sure of the impact on performances from using it this way but at least it works.

I think the documentation should warn about such details when using the function from within the setup

(In my case, switching the resolve method to the SSR example didn't cause any issues but it wasn't required to make it work)

diego-lipinski-de-castro commented 1 year ago

made that switch on your repo, then I did run vite build and that was it.

U might got confused, because if any state changes, the app rerenders and then renders the translations, but if you F5 you will see that the translations arent being loaded, that might be the case, im not sure, I made that change and ran vite build but got the same results, after logging in and F5

kskrlinnorth2 commented 1 year ago

Same issue, translations aren't available in mounted hooks or outside of hooks on first load (refresh/f5), but we are using webpack, not vite. Using version: 2.3.0.

Translations work only in rendered html (template).

app.js

.use(i18nVue, {
  resolve: (lang) => import(`../../lang/${lang}.json`),
})

some component where we need translations in mounted hook

onMounted(() => {
  console.log("error.generic"); // Output is error.generic
});

en.json { "error.generic": "This is generic error!" }

pristavu commented 1 year ago

https://github.com/pristavu/inertia-vue3-i18n

Throttle the network via devtools

pushparajbhatta commented 1 year ago

I am experiencing the same issue while using it with Inertia JS. I have found out that the "lang" attribute on <html> tag is not updating after the language changer route redirects user back to the page. And after refresh the "lang" attribute on <html> tag is also updated, translation also works. When the user clicks to change the language, after changing the locale on the session, I redirect back to the same page. Even if I redirect to any other pages, it gives the same issue.

pushparajbhatta commented 1 year ago

Finally, figured it out. In my case, the issue is caused by the <Link> component of "inertiajs/inertia-vue3"

As per Inertia JS docs: Link component is a light wrapper around a standard anchor <a> link that intercepts click events and prevents full page reloads from occurring.

Hence, the page is not reloading the <html> tag and the "lang" attribute remains unchanged. However, it works on F5/refresh because it reloads the full page and the <html> tag gets refreshed as well.

The solution for me is to use normal anchor tag or any other language switching alternatives instead of the Link component. Make sure the <html> tag's "lang" attribute is updated when you change the language and it should then work on inital page loads too

@iMaBo @diego-lipinski-de-castro @Mondotosz @kskrlinnorth2 @pristavu Please try this out and see if it works.