[SOLVED] Dynamic set of Quasar lang from store value



  • @s-molinari I assumed I needed to put it into a boot file, because whenever I refresh my app, it is back to en-gb, even if I have switched to another language (like es) that was working just fine until a refresh. The page with the instructions I was following are from here: https://quasar.dev/options/quasar-language-packs#Dynamically-Picking-Default-Language

    Once someone navigates to my prefs page with a chooser, they can set the language no problem. But it does not save across a refresh and this is what I am trying to fix. I can store my value in vuex, but need a way to force my app to that language pref across restart/refresh of app.



  • Ah. An important piece of information missing from your original post. Cross-session persistence. 😁

    Did the post from @metalsadman help you? Also, how is Vuex persisting the value across the sessions?

    Scott



  • @s-molinari I am using vuex-persist to persist my values. As for the post from @metalsadman I need to examine it more closely, but it seems that the logic is backwards. I need to access the vuex store BEFORE (or at boot), not after, and it seems that solution is for passing it later (but as I said, perhaps I am missing something about that and I need to investigate).



  • It’s not backwards. The store is initialized before the boot files are ran and “offered” to the boot file as an argument. It’s dependency injection in action.

    I have this in a freshly created Quasar app.

    export default function ({ store }) {
      const Router = new VueRouter({
        scrollBehavior: () => ({ x: 0, y: 0 }),
        routes,
    
        // Leave these as is and change from quasar.conf.js instead!
        // quasar.conf.js -> build -> vueRouterMode
        // quasar.conf.js -> build -> publicPath
        mode: process.env.VUE_ROUTER_MODE,
        base: process.env.VUE_ROUTER_BASE
      })
      console.log('store:', store)
      return Router
    }
    

    And I see the store when the app initially loads:

    676c97d5-690c-4674-98d6-ec46d14b6952-image.png

    Try it! 😃

    Scott



  • That is great news, I will try it. Thanks!



  • Alas, this does not work for me, here is my boot file:

    import { Quasar } from 'quasar'
    import store from 'src/router'
    
    console.log(store)
    export default async () => {
      let langIso = 'en-gb'
      if (store.state.rstruth.lang_prefs !== 'en-gb') {
        langIso = store.state.rstruth.lang_prefs
      }
    
      try {
        await import(`quasar/lang/${langIso}`)
          .then(lang => {
            Quasar.lang.set(lang.default)
          })
      } catch (err) {
        // Requested Quasar Language Pack does not exist,
        // let's not break the app, so catching error
      }
    }
    

    Which results in this from the console:

    [HMR] Waiting for update signal from WDS...
    quasar-lang-pack.js?f5b9:6 ƒ (_ref) {
      var store = _ref.store;
      var Router = new vue_router__WEBPACK_IMPORTED_MODULE_4__["default"]({
        scrollBehavior: function scrollBehavior() {
          return {
            y: 0
          };
        },
    …
    client-entry.js?2f39:62 [Quasar] Running PWA.
    client-entry.js?2f39:63 [Quasar] Forcing PWA into the network-first approach to not break Hot Module Replacement while developing.
    client-entry.js?2f39:100 [Quasar] boot error: TypeError: Cannot read property 'rstruth' of undefined
        at _callee$ (quasar-lang-pack.js?f5b9:9)
        at tryCatch (runtime.js?4bc9:45)
        at Generator.invoke [as _invoke] (runtime.js?4bc9:271)
        at Generator.prototype.<computed> [as next] (runtime.js?4bc9:97)
        at asyncGeneratorStep (asyncToGenerator.js?fa84:5)
        at _next (asyncToGenerator.js?fa84:27)
        at eval (asyncToGenerator.js?fa84:34)
        at new Promise (<anonymous>)
        at new F (_export.js?63b6:36)
        at Array.eval (asyncToGenerator.js?fa84:23)
    _callee$ @ client-entry.js?2f39:100
    tryCatch @ runtime.js?4bc9:45
    invoke @ runtime.js?4bc9:271
    prototype.<computed> @ runtime.js?4bc9:97
    asyncGeneratorStep @ asyncToGenerator.js?fa84:5
    _throw @ asyncToGenerator.js?fa84:31
    Promise.then (async)
    asyncGeneratorStep @ asyncToGenerator.js?fa84:15
    _next @ asyncToGenerator.js?fa84:27
    Promise.then (async)
    asyncGeneratorStep @ asyncToGenerator.js?fa84:15
    _next @ asyncToGenerator.js?fa84:27
    (anonymous) @ asyncToGenerator.js?fa84:34
    F @ _export.js?63b6:36
    (anonymous) @ asyncToGenerator.js?fa84:23
    _start @ client-entry.js?2f39:77
    start @ client-entry.js?2f39:77
    (anonymous) @ client-entry.js?2f39:120
    ./.quasar/client-entry.js @ app.js:933
    __webpack_require__ @ app.js:770
    fn @ app.js:130
    0 @ app.js:7166
    __webpack_require__ @ app.js:770
    (anonymous) @ app.js:908
    (anonymous) @ app.js:911
    client?f1f8:48 [WDS] Hot Module Replacement enabled.
    client?f1f8:52 [WDS] Live Reloading enabled.
    register-service-worker.js?407e:12 App is being served from cache by a service worker.
    register-service-worker.js?407e:15 Service worker has been registered.
    

    and I have tried several ways of importing the store to that file (or not importing at all and just trying to call store or this.$store as in other places in my app, and they all fail…

    I should note that when I call this.$store in any of my components, it works fine. Which leads me to think that it is not yet available to boot files like I mentioned above…



  • @ssuess have you looked and tried the sample code me and @s-molinari shown above?.. anyway you can check that it works here https://codesandbox.io/s/0ybb3 if you have doubts :x.



  • @metalsadman yes I have, and in fact, I already had the same store export from my router. As I mentioned above, I am able to access it in any of my components without any issue. The problem is trying to access the store from within the BOOT file above. Unless I am not understanding you?

    Here is my src/router/index.js file btw in case it helps:

    export default function ({ store }) {
      const Router = new VueRouter({
        scrollBehavior: () => ({ y: 0 }),
        routes,
    
        // Leave these as is and change from quasar.conf.js instead!
        // quasar.conf.js -> build -> vueRouterMode
        // quasar.conf.js -> build -> publicPath
        mode: process.env.VUE_ROUTER_MODE,
        base: process.env.VUE_ROUTER_BASE
      })
      // console.log(store._vm.$root._data)
      let previouslyRestored = false
      const waitForStorageToBeReady = async (to, from, next) => {
        await store.restored
        if (!previouslyRestored) {
          // do the things you want to do only once after the store is restored
          previouslyRestored = true
        }
        next()
      }
      Router.beforeEach(waitForStorageToBeReady)
    
      return Router
    }
    

    So perhaps I am missing something fundamental, but I don’t currently see it. Thanks again for any suggestions you have, I really appreciate it.



  • well i don’t know why it’s not working for you, nowhere i need to import the store in a bootfile, since the store is already available for me if i add it is as a param. unless you’re not providing anymore info :/.



  • Interestingly, when I look at the sandbox you set up, I don’t see a single case of any file inside /boot that is actually accessing store data



  • @ssuess you’re not looking enough, see lines 95 and 102 of src/boot/axios.js :). line 14 of src/router/index.js and see further how i’m tapping into the store to change the title of the current page.



  • Thanks! Sorry I missed that, it gives me an idea of something new to try…I will do that and get back to you…



  • Finally! Now I see what the problem was! I was neglecting to use {store} INSIDE the EXPORT of the boot logic, thinking it was provided by other components. Now it seems so obvious (and I feel pretty dumb). Thanks @metalsadman and @s-molinari for helping me troubleshoot this!

    FYI, the code change was from this:

    export default async () => {
      let langIso = 'en-gb'
      console.log(store.state.rstruth.lang_prefs)
      if (store.state.rstruth.lang_prefs !== 'en-gb') {
        langIso = store.state.rstruth.lang_prefs
      }
    

    to THIS:

    export default async ({ store }) => {
      let langIso = 'en-gb'
      console.log(store.state.rstruth.lang_prefs)
      if (store.state.rstruth.lang_prefs !== 'en-gb') {
        langIso = store.state.rstruth.lang_prefs
      }
    


  • Hehehe…sorry. But, this deserves a 🤦♂.

    😁

    Scott



  • @s-molinari Since we are still on the same subject, I am now trying to also set the language for i18n at the same time, but it does not seem there is a param to pass through in the same way is there? So I assume I need to do something like this?

    import i18n from 'boot/i18n.js'
    
    export default async ({ store }) => {
        i18n.locale = myStoredLocale
    }
    

    or do you recommend another way to set the i18n locale at boot?



  • Try it out. I’ve never dealt with presetting the language and in fact, this is a use case that we should know about. I’m going to be updating my article with what we find out here. So, try what you can. If it works or maybe @metalsadman has a better idea, then that is what we’ll go with for now.

    Scott



  • FYI, it totally works with the code shown above. Now we have a way to set lang (for both Quasar and i18n) across restarts/reloads. This assumes that the i18n boot file is configured to export it like so (from @s-molinari medium article):

    import VueI18n from 'vue-i18n'
    import messages from 'src/i18n'
    
    let i18n
    
    export default async ({ app, Vue }) => {
      Vue.use(VueI18n)
    
      // Set i18n instance on app
      app.i18n = new VueI18n({
        locale: 'en-gb',
        fallbackLocale: 'en-gb',
        // enableInSFC: true,
        messages
      })
      i18n = app.i18n
    }
    
    export { i18n }
    


  • Great that helped.

    Can you share your boot file, where it brings it all together please?

    Scott



  • sure, here is my boot file:

    // for when you don't specify quasar.conf.js > framework: 'all'
    import { Quasar } from 'quasar'
    import { i18n } from 'boot/i18n.js'
    
    export default async ({ store }) => {
      let previouslyRestored = false
      await store.restored
      if (!previouslyRestored) {
        previouslyRestored = true
      }
    
      let langIso = 'en-gb'
      if (previouslyRestored) {
        console.log(store.state.rstruth.lang_prefs)
        if (store.state.rstruth.lang_prefs !== 'en-gb') {
          langIso = store.state.rstruth.lang_prefs
        }
        i18n.locale = langIso
        try {
          await import(`quasar/lang/${langIso}`)
            .then(lang => {
              Quasar.lang.set(lang.default)
            })
        } catch (err) {
          // Requested Quasar Language Pack does not exist,
          // let's not break the app, so catching error
        }
      }
    }
    

    Some important things to note:

    • This relies on a vuex store and a way to persist my vuex values (I use vuex-persist but there are other ways)
    • I am using a call in vuex-persist (previouslyRestored) to check if my state has been restored before I try to access values from the store.


  • Thanks @ssuess. I’ll dissect what I need and update my article for pro tip #3. 😀

    Scott


Log in to reply