nuxt-modules / i18n

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

[I18n] Google page speed - reduce execution time? #815

Closed koteezy closed 4 years ago

koteezy commented 4 years ago

Hi. Google says that the script for loading languages on mobile devices is very slow. 1.5 seconds, while the main application is 400ms, how i can fix that?

In General, why not add an option that will add JSON with translations inside the HTML page, similar to ExtractCss.

Google page speed

koteezy commented 4 years ago

Also, I must say that when we specify one language - everything works well, but, unfortunately , we do not have one language, we have a lot of languages.

Each translation has a size from 13 to 16 KB.

List of languages (95) ``` [{ "code":"ru", "iso":"ru", "name":"Русский", "file":"ru.json" }, { "code":"de", "iso":"de", "name":"Deutsch", "file":"de.json" }, { "code":"be", "iso":"be", "name":"Беларуская", "file":"be.json" }, { "code":"en", "iso":"en", "name":"English", "file":"en.json" }, { "code":"hi", "iso":"hi", "name":"हिन्दी, हिंदी", "file":"hi.json" }, { "code":"gd", "iso":"gd", "name":"Gàidhlig", "file":"gd.json" }, { "code":"ja", "iso":"ja", "name":"日本語 (にほんご/にっぽんご)", "file":"ja.json" }, { "code":"hy", "iso":"hy", "name":"Հայերեն", "file":"hy.json" }, { "code":"xh", "iso":"xh", "name":"IsiXhosa", "file":"xh.json" }, { "code":"ur", "iso":"ur", "name":"اردو", "file":"ur.json" }, { "code":"th", "iso":"th", "name":"ไทย", "file":"th.json" }, { "code":"cy", "iso":"cy", "name":"Cymraeg", "file":"cy.json" }, { "code":"tr", "iso":"tr", "name":"Türkçe", "file":"tr.json" }, { "code":"tl", "iso":"tl", "name":"Wikang Tagalog, ᜏᜒᜃᜅ᜔ ᜆᜄᜎᜓᜄ᜔", "file":"tl.json" }, { "code":"tt", "iso":"tt", "name":"татарча, tatarça, تاتارچا‎", "file":"tt.json" }, { "code":"udm", "iso":"udm", "name":"Удмуртский", "file":"udm.json" }, { "code":"uk", "iso":"uk", "name":"українська", "file":"uk.json" }, { "code":"fa", "iso":"fa", "name":"فارسی", "file":"fa.json" }, { "code":"tg", "iso":"tg", "name":"тоҷикӣ, toğikī, تاجیکی‎", "file":"tg.json" }, { "code":"eo", "iso":"eo", "name":"Esperanto", "file":"eo.json" }, { "code":"te", "iso":"te", "name":"తెలుగు", "file":"te.json" }, { "code":"ca", "iso":"ca", "name":"Català", "file":"ca.json" }, { "code":"ta", "iso":"ta", "name":"தமிழ்", "file":"ta.json" }, { "code":"am", "iso":"am", "name":"አማርኛ", "file":"am.json" }, { "code":"sw", "iso":"sw", "name":"Kiswahili", "file":"sw.json" }, { "code":"ka", "iso":"ka", "name":"ქართული", "file":"ka.json" }, { "code":"sv", "iso":"sv", "name":"Svenska", "file":"sv.json" }, { "code":"is", "iso":"is", "name":"Íslenska", "file":"is.json" }, { "code":"su", "iso":"su", "name":"Basa Sunda", "file":"su.json" }, { "code":"ht", "iso":"ht", "name":"Kreyòl ayisyen", "file":"ht.json" }, { "code":"sr", "iso":"sr", "name":"српски језик", "file":"sr.json" }, { "code":"gu", "iso":"gu", "name":"ગુજરાતી", "file":"gu.json" }, { "code":"sq", "iso":"sq", "name":"Shqip", "file":"sq.json" }, { "code":"fr", "iso":"fr", "name":"Français, langue française", "file":"fr.json" }, { "code":"sl", "iso":"sl", "name":"Slovenščina", "file":"sl.json" }, { "code":"et", "iso":"et", "name":"Eesti, eesti keel", "file":"et.json" }, { "code":"sk", "iso":"sk", "name":"Slovenčina", "file":"sk.json" }, { "code":"vi", "iso":"vi", "name":"Tiếng Việt", "file":"vi.json" }, { "code":"si", "iso":"si", "name":"සිංහල", "file":"si.json" }, { "code":"cs", "iso":"cs", "name":"česky, čeština", "file":"cs.json" }, { "code":"yi", "iso":"yi", "name":"ייִדיש", "file":"yi.json" }, { "code":"bn", "iso":"bn", "name":"বাংলা", "file":"bn.json" }, { "code":"ko", "iso":"ko", "name":"한국어 (韓國語), 조선말 (朝鮮語)", "file":"ko.json" }, { "code":"az", "iso":"az", "name":"Azərbaycan dili", "file":"az.json" }, { "code":"km", "iso":"km", "name":"ភាសាខ្មែរ", "file":"km.json" }, { "code":"sah", "iso":"sah", "name":"Якутский", "file":"sah.json" }, { "code":"uz", "iso":"uz", "name":"Zbek, Ўзбек, أۇزبېك‎", "file":"uz.json" }, { "code":"ky", "iso":"ky", "name":"кыргыз тили", "file":"ky.json" }, { "code":"ro", "iso":"ro", "name":"Română", "file":"ro.json" }, { "code":"kn", "iso":"kn", "name":"ಕನ್ನಡ", "file":"kn.json" }, { "code":"pt", "iso":"pt", "name":"Português", "file":"pt.json" }, { "code":"kk", "iso":"kk", "name":"Қазақ тілі", "file":"kk.json" }, { "code":"pl", "iso":"pl", "name":"Polski", "file":"pl.json" }, { "code":"jv", "iso":"jv", "name":"Basa Jawa", "file":"jv.json" }, { "code":"pap", "iso":"pap", "name":"Папьяменто", "file":"pap.json" }, { "code":"it", "iso":"it", "name":"Italiano", "file":"it.json" }, { "code":"pa", "iso":"pa", "name":"ਪੰਜਾਬੀ, پنجابی‎", "file":"pa.json" }, { "code":"id", "iso":"id", "name":"Bahasa Indonesia", "file":"id.json" }, { "code":"no", "iso":"no", "name":"Norsk", "file":"no.json" }, { "code":"hu", "iso":"hu", "name":"Magyar", "file":"hu.json" }, { "code":"nl", "iso":"nl", "name":"Nederlands, Vlaams", "file":"nl.json" }, { "code":"hr", "iso":"hr", "name":"Hrvatski", "file":"hr.json" }, { "code":"ne", "iso":"ne", "name":"नेपाली", "file":"ne.json" }, { "code":"he", "iso":"he", "name":"עברית", "file":"he.json" }, { "code":"my", "iso":"my", "name":"ဗမာစာ", "file":"my.json" }, { "code":"gl", "iso":"gl", "name":"Galego", "file":"gl.json" }, { "code":"mt", "iso":"mt", "name":"Malti", "file":"mt.json" }, { "code":"ga", "iso":"ga", "name":"Gaeilge", "file":"ga.json" }, { "code":"ms", "iso":"ms", "name":"Bahasa Melayu, بهاس ملايو‎", "file":"ms.json" }, { "code":"fi", "iso":"fi", "name":"Suomi, suomen kieli", "file":"fi.json" }, { "code":"mrj", "iso":"mrj", "name":"Горномарийский", "file":"mrj.json" }, { "code":"eu", "iso":"eu", "name":"Euskara, euskera", "file":"eu.json" }, { "code":"mr", "iso":"mr", "name":"मराठी", "file":"mr.json" }, { "code":"es", "iso":"es", "name":"Español, castellano", "file":"es.json" }, { "code":"mn", "iso":"mn", "name":"монгол", "file":"mn.json" }, { "code":"el", "iso":"el", "name":"Ελληνικά", "file":"el.json" }, { "code":"ml", "iso":"ml", "name":"മലയാളം", "file":"ml.json" }, { "code":"da", "iso":"da", "name":"Dansk", "file":"da.json" }, { "code":"mk", "iso":"mk", "name":"македонски јазик", "file":"mk.json" }, { "code":"cv", "iso":"cv", "name":"чӑваш чӗлхи", "file":"cv.json" }, { "code":"mi", "iso":"mi", "name":"Te reo Māori", "file":"mi.json" }, { "code":"ceb", "iso":"ceb", "name":"Себуанский", "file":"ceb.json" }, { "code":"mhr", "iso":"mhr", "name":"Марийский", "file":"mhr.json" }, { "code":"bs", "iso":"bs", "name":"Bosanski jezik", "file":"bs.json" }, { "code":"mg", "iso":"mg", "name":"Malagasy fiteny", "file":"mg.json" }, { "code":"bg", "iso":"bg", "name":"български език", "file":"bg.json" }, { "code":"lv", "iso":"lv", "name":"Latviešu valoda", "file":"lv.json" }, { "code":"ba", "iso":"ba", "name":"башҡорт теле", "file":"ba.json" }, { "code":"lt", "iso":"lt", "name":"Lietuvių kalba", "file":"lt.json" }, { "code":"ar", "iso":"ar", "name":"العربية", "file":"ar.json" }, { "code":"lo", "iso":"lo", "name":"ພາສາລາວ", "file":"lo.json" }, { "code":"af", "iso":"af", "name":"Afrikaans", "file":"af.json" }, { "code":"lb", "iso":"lb", "name":"Lëtzebuergesch", "file":"lb.json" }, { "code":"la", "iso":"la", "name":"Latine, lingua latina", "file":"la.json" }, { "code":"zh", "iso":"zh", "name":"中文 (Zhōngwén), 汉语, 漢語", "file":"zh.json" }] ```

Nuxt.config.js

i18n: {
    locales, // 95
    defaultLocale: 'en',
    lazy: true,
    langDir: 'lang/',
},

Update: Damn, I specified one language with lazy: true - and the picture is about the same. 1000MS.

rchl commented 4 years ago

This is interesting but I'm not sure why that measurement by Lighthouse is so slow and if it makes sense.

I'm also working with ~12KB JSON translation files and I'm seeing some ridiculously bad times in lighthouse for it: Screenshot 2020-07-31 at 22 58 02

That javascript file basically has a one JSON.parse call and when I extract that code and run it in the console it seems to take 0 ms... While, it surely would be slower during page loading (and also my testing is with a simulated mobile device) I'm not sure if it should be that slow...

In General, why not add an option that will add JSON with translations inside the HTML page, similar to ExtractCss.

At least when it comes to the "script evaluation" (which is the bottleneck here), it shouldn't really matter much if it's in the main HTML (in a script element) or in an external JS file. External JS file adds an overhead of an extra network request but shouldn't have any effect on how long it takes to evaluate the code.

BTW. According to the Internet (or v8 engine), parsing JSON object (like happens here) should be more efficient than parsing a JS object - https://v8.dev/blog/cost-of-javascript-2019#json So it should be as efficient as possible already when it comes to the code.

koteezy commented 4 years ago

Alright, i found something, looks like the main problem is count of routes:

helpers/routes.js

if i replace

locales = getLocaleCodes(locales)

to

locales = ['en']

the speed will increase hundreds of times.

With 95 languages, nuxt generating 1995 routes.

Do nuxt support thing like delayed loading routes, or something like that, for avoiding that?

rchl commented 4 years ago

Large number of routes does affect performance, see https://github.com/nuxt-community/i18n-module/issues/690 .

But I'm not sure that's directly related to the lighthouse issue because in my project I don't have that many translations nor routes (18 translations and 11 routes in total since I'm using no-prefix strategy).

koteezy commented 4 years ago

I left only 2 locales, and run lighthouse several times, and got 60/80% on mobile, but when i make some changes in plugin.main.js

Namely, adding JSON right to DOM, for avoid unnecessary ajax request

if ( process.server ) {
    await beforeNuxtRender( async ( {nuxtState} ) => {
      await loadLanguageAsync( context, finalLocale );

      nuxtState.i18n = app.i18n.getLocaleMessage( finalLocale );
    } );
  }

And get it in loadAndSetLocale method

if ( process.client && initialSetup ) {
      app.i18n.setLocaleMessage( newLocale, nuxtState.i18n );
}

The performance has increased, to 90/95 for mobile, for web sometimes i get 100.

rchl commented 4 years ago

I've tried that.

It has improved performance by a few points only (from 48 to 52 or so).

And as expected, the "Script Evaluation" time that was attributed to the lang file has now moved to the *.app bundle. So Lighthouse attributing all that slowness to the lang file is not really accurate.

To conclude:

BTW. I don't want the solution that is based on the store to be the main one. This core functionality of this module should be functional even without the store.

koteezy commented 4 years ago

It makes sense to have the default locale in the main bundle already. Should improve the case when the client's locale matches the default one.

Exactly! But, in which repository should we create an issue for that idea? Nuxt, Vue-router?

Spunkie commented 4 years ago

And as expected, the "Script Evaluation" time that was attributed to the lang file has now moved to the *.app bundle. So Lighthouse attributing all that slowness to the lang file is not really accurate.

Maybe a good use case for user timings? That way it's easy to find how longi18n really took to evaluate in perf tools like lighthouse.

https://web.dev/user-timings/ https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API/Using_the_User_Timing_API

rchl commented 4 years ago

Exactly! But, in which repository should we create an issue for that idea? Nuxt, Vue-router?

It's this module that is responsible for loading lang files. And I can use this issue to fix it.

koteezy commented 4 years ago

Sounds good.

rchl commented 4 years ago

Maybe a good use case for user timings? That way it's easy to find how longi18n really took to evaluate in perf tools like lighthouse.

Nuxt is in the best position to do that itself. It would be important to then know metrics for all internal components and all used modules. If I would just implement it for this module, you would know that for example, this module takes 100ms to run but you still wouldn't know where the remaining 2900ms is spent.

So it would make sense to create a feature request for Nuxt, if there isn't one already.

rchl commented 4 years ago

@koteezy Thanks for your suggestion from https://github.com/nuxt-community/i18n-module/issues/815#issuecomment-667551063 . I have initially mistaken it as using store but nuxtState is not store so it's perfectly fine to use it. I've implemented that in #823

rchl commented 4 years ago

With v6.13.3 there should be no extra network requests to fetch the languages on initial load.

I don't think it will make a big improvement but it's a good step.

Since those were the issues mentioned in here, I think this can be closed now.

koteezy commented 4 years ago

@rchl Thanks you! How about large number of routes? (comment) Did you think is possible to load on client only user's language, or something like this?

rchl commented 4 years ago

@rchl Thanks you! How about large number of routes? (comment)

There is already an issue #690 for that so we don't need to keep a separate one.

Did you think is possible to load on client only user's language, or something like this?

No, the issue is with initializing VueRouter with a large number of routes. It's an issue that affects both server and client performance. It's a fundamental issue in VueRouter and I'm not planning on doing any hacky workarounds for it.

koteezy commented 4 years ago

Got it. Thanks anyway!