nuxt-modules / i18n

I18n module for Nuxt
https://i18n.nuxtjs.org
MIT License
1.73k stars 479 forks source link

Add support of non-git based messages source #833

Closed coremyslo closed 1 month ago

coremyslo commented 4 years ago

Is your feature request related to a problem? Please describe.

In my project, I want to have the ability to host messages on an external server (Amazon S3 in my case) and fetch them on every page request, this way I would be able to update messages almost real-time without dependency on deploys. Also, I want to use git messages as a fallback (I'm doing it using deepmerge of git-based and cloud-based messages per locale)

Describe the solution you'd like

Since I already have some requests in nuxtServerInit, I want to have a request to the external server there, to parallel my requests for performance reasons. But as for now, overwrite messages using context.app.i18n.setLocaleMessage get overwritten on the client, noted here: https://github.com/nuxt-community/i18n-module/issues/831 (Solution with hosting messages in nuxtStore would fit)

Describe alternatives you've considered

I tried another way using lazy:true and having the same js file for all locales. In this file, I have a promise containing requests to the external server. Cons are: 1) this requests can't be parallel with nuxtServerInit 2) these requests are sent again on the client

rchl commented 4 years ago

these requests are sent again on the client

Not anymore in latest versions. nuxtState is used to carry over server-loaded messages (when using lazy).

rchl commented 4 years ago

BTW. As for your current solution, the problem I see with it is that when the user switches locale (or navigates to another locale) on the client, then your logic in nuxtServerInit won't run. How do you handle that?

I think the absolutely correct solution might actually require using lazy and let module load the locales.

coremyslo commented 4 years ago

to change locale with differentDomains: true the domain will change, page reload would happen in this case, realtime change of locale doesn't have sense in differentDomains mode.

rchl commented 4 years ago

True that.

coremyslo commented 4 years ago

Some managing translation services provide tools for in-context live translation edit. This is not my case, but just example of how this feature can be used https://docs.lokalise.com/en/articles/1592525-livejs-web-in-context-editor https://help.phrase.com/help/set-up-in-context-editor

Also developing some sort of cms to manage translations and change messages without reload could be an another case.

Both cases nuxtStore would solve.

rchl commented 4 years ago

I mean, technically it's already supported by using lazy and having custom loading logic in your code. It's just that you want to do it differently for the purpose of optimizing for your use case.

rchl commented 4 years ago

BTW. While I intend to fix this, you could create a plugin that fetches data from your API and injects it into the context (on the server only). Then you could use that data both from nuxtServerInit and your custom locale loading code. You would have your optimization and also better organization.

coremyslo commented 4 years ago

Not anymore in latest versions. nuxtState is used to carry over server-loaded messages (when using lazy).

There is one bug here, If I provide defaultLocale and load domain in browser where domain locale match defaultLocale I would see the call on client for that domain with nuxt 2.14.1 and nuxt-i18n 6.13.6, except that, for my case, I can use lazy.

BTW. While I intend to fix this, you could create a plugin that fetches data from your API and injects it into the context (on the server only). Then you could use that data both from nuxtServerInit and your custom locale loading code. You would have your optimization and also better organization.

hm, I was thinking on using store instead, If the messages could be in store state in i18n module, I could call the mutation from it, from my store module action, which I'll use in nuxtServerInit

I mean, technically it's already supported by using lazy and having custom loading logic in your code. It's just that you want to do it differently for the purpose of optimizing for your use case.

well, technically I can, after your note about fixes with not having calls on client. But I believe context.app.i18n.setLocaleMessage was indented to add the ability to change messages after page loaded, which is not working in i18n-module, I'm honestly not sure, didn't test vue-i18n without i18n-module in nuxt. But the fix should open new opportunities not only for my case, noted above.

loicmarie commented 4 years ago

I am probably missing something, but as far as I understand we can't use Translation APIs along with nuxt-i18n. If that's true, it's not about optimizing a specific use case, that means we can't dynamically translate messages. In my case : I have 1600 products in my shop, I want to call the Google Cloud Translation API each time a product page is displayed to translate product description. I would like to use lazy option, but I cannot have 1600 * (number of locales) message files. Moreover, I don't want to regenerate files and translate all the shop everytime a simple change is made.

So : I just want to translate messages in asyncData function, and use payload in $t(...), but it does not seem possible currently. What would be the nuxt-i18n way to do this ?

A workaround would be to use setLocaleMessage in asyncData, but as stated above, it is overridden client side.

rchl commented 4 years ago

I would like to use lazy option, but I cannot have 1600 * (number of locales) message files. Moreover, I don't want to regenerate files and translate all the shop everytime a simple change is made.

You can assign same JS "lang" file for all locales and load translations dynamically using javascript.

So : I just want to translate messages in asyncData function, and use payload in $t(...), but it does not seem possible currently. What would be the nuxt-i18n way to do this ?

A workaround would be to use setLocaleMessage in asyncData, but as stated above, it is overridden client side.

I think that the setLocaleMessage "workaround" that you've mentioned is actually the proper way to do it if not using "lazy" option. And the messages should not get overridden on the client with latest nuxt-i18n versions.

(I might be missing something that makes those solutions not work...)

loicmarie commented 4 years ago

I am in SSG mode so I would like to use the messages in payload (translated at build time at nuxt generate). I probably misunderstood the purpose of the lazy option but it does not seem suitable to the SSG case.

Concerning the setLocaleMessage function, with nuxt-i18n at ^6.13.9 version, it works when navigating but not when coming for the first time, nor when refreshing (but I only tried in SSG mode) I can make a minimal repro in a new issue if necessary. I use it this way:

export default {
  async asyncData({ payload, app }) {
    app.i18n.setLocaleMessage('en', {
      title: 'My title'
    })
  }
}

Is this the way to do ? But then only 'title' is displayed, and if I navigate and come back, 'My title' is displayed.

rchl commented 4 years ago

Right, setLocaleMessage from asyncData will work on the server but the client will overwrite it. Also, it won't work for you if you want to update translations without regenerating the site. I have a feeling, like you said, that you might be better of not using nuxt-i18n in this case but feel free to create new issue/question if you want to discuss this further.

coremyslo commented 4 years ago

I tried to:

  1. set messages as {"source": "nuxtConfig"} in nuxt.config.js
  2. call setLocaleMessage with { "source": "nuxtServerInit" } in nuxtServerInit
  3. call setLocaleMessage with { "source": "plugin" } in my plugin

The order of execution was like this:

// server
{ source: "nuxtConfig" }
{ source: "plugin" }
{ source: "nuxtServerInit" }

// client
{ source: "nuxtConfig" }
{ source: "plugin" }

I believe nuxt-i18n looks to be a plugin called on server-side and client, and the context changed after nuxt-i18n execution on server (in my plugin, and nuxtServerInit) was ignored on the client call.

In browser "nuxtServerInit" text displays, changing to "plugin" after client call.

Is it possible to not call the plugin on client-side, or to make client call see the data changed after other plugins and nuxtServerInit calls?

rchl commented 4 years ago

That's why you should utilize "lazy" option. Then messages would be initialized from the plugin already and client would reuse the ones set on the server.

coremyslo commented 4 years ago

You inspired me to test something. With lazy: true and empty file (export default {}), the behavior changed to

// server
{ source: "nuxtConfig" }
{ source: "plugin" }
{ source: "nuxtServerInit" }

// client
{ source: "nuxtServerInit" }
{ source: "plugin" }

Which makes nuxt-i18n see changes done in nuxtServerInit on client-side. So I can have my parallel requests to translations 3rd party together with other requests in nuxtServerInit.

rchl commented 4 years ago

Not sure what you mean because nuxtServerInit doesn't run on the client.

I meant to set the messages from the file used for lazy loading.

coremyslo commented 4 years ago

It doesn't run on the server. I meant if I will do this:

// nuxt.config.js
modules: [
    ["nuxt-i18n", {
        ...,
        locales: [
            {
                code: "en-GB",
            }
        ],
        vueI18n: {
            ...,
            messages: {
                "en-GB": {
                    "source": "nuxtConfig",
                },
            },
        },
    ],
],

// store/index.js
nuxtServerInit ({ commit, dispatch }, context) {
    this.$i18n.setLocaleMessage("en-GB", { "source": "nuxtServerInit" });
},

I will see text for $t("source") as nuxtServerInit rendered on server, replaced by text nuxtConfig on client. (messages changes in nuxtServerInit are ignored)

But if I will do this:

// nuxt.config.js
modules: [
    ["nuxt-i18n", {
        ...,
        locales: [
            {
                code: "en-GB",
                file: "index.js"
            }
        ],
        lazy: true,
        langDir: "locales/"
    ],
],

// locales/index.js
export default {
    "en-GB": {
        "source": "file",
    }
}

// store/index.js
nuxtServerInit ({ commit, dispatch }, context) {
    this.$i18n.setLocaleMessage("en-GB", { "source": "nuxtServerInit" });
},

I will see text for $t("source") as nuxtServerInit rendered on server, replaced by same text nuxtServerInit on client.

So it looks like the lazy:true somehow consider changes done in nuxtServerInit, which is not the case with lazy:false.

coremyslo commented 4 years ago

The order of execution is

  1. nuxt-i18n plugin server
  2. nuxtServerInit server
  3. nuxt-i18n plugin client

lazy: false run nuxt-i18n plugin client with the state of $i18n set on nuxt-i18n plugin server, excluding changes done of $i18n in nuxtServerInit. Which is not the case for lazy:true.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

BobbieGoede commented 1 month ago

Closing this as v7 is not being actively worked on (critical hotfixes only), if this still applies to v8 and later please open a new issue!