[SOLVED] Dynamic set of Quasar lang from store value



  • I have a value that is set in my store that should be read to determine what language quasar will use. Following the instructions, I have created a boot file and included it, but it failes to find my store. I assume this is because the boot process happens earlier than the store load? Not sure how to call my store value in the boot file then, any suggestions? Here is my boot file:

    // for when you don't specify quasar.conf.js > framework: 'all'
    import { Quasar } from 'quasar'
    // OTHERWISE:
    // import Quasar from 'quasar'
    
    export default async () => {
      let langIso = 'en-gb'
      if (this.$store.state.rstruth.lang_prefs !== 'en-gb') {
        langIso = this.$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
      }
    }
    

    I have also tried Store in the place of this.$store and tried importing the store in this file, to no avail (although perhaps my syntax was wrong). Any suggestions for how to choose the lang from a vuex store value? Thanks.



  • Following the instructions

    Where does it say to do the language selection in the boot file?

    You should put your language selection in a component for language selection. Since the two language systems (assuming you are also using vue-i18n) are global properties of Vue, that’s all you need to “manipulate” to get the language changed. See here (pro tip at the bottom). If you need to change it with a Vuex mutation or store value, you can build that into your “language selection component” too.

    Scott



  • @ssuess https://quasar.dev/quasar-cli/cli-documentation/boot-files#Anatomy-of-a-boot-file
    Pass your store context as param like so export default async ({store}) => { ... store.state.rstruth.lang_prefs ...}.



  • @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 }
    

Log in to reply