xKeeg
xKeeg

Reputation: 43

How can I stop sveltekit serving a 7MB bundled lang file in favour of just the correct one?

I have a static class for localising the data on my site, and I want to be able to load the required files, rather than all of them. It was fine for testing, but in production, each page that requires translation loads ~7MB of bundled lang files, despite only using one.

I have a single page on the site to change the language, and that page does not translation, so there is no occasion where a user needs the see the language visibly change on the same page.

These language files are not created by me and therefore I cannot reduce their sizes, how could I make it so that just the correct file is served

Using svelte-kit, rollup, vite

import cn from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_cn.csv';
import de from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_de.csv';
import en from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_en.csv';
import fr from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_fr.csv';
import id from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_id.csv';
import jp from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_JP.csv';
import kr from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_kor.csv';
import pt from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_pt.csv';
import ru from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_ru.csv';
import sp from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_sp.csv';
import tc from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_tc.csv';
import th from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_th.csv';
import vn from '../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_vn.csv';

const bindings = {
    cn: cn,
    de: de,
    en: en,
    fr: fr,
    id: id,
    jp: jp,
    kr: kr,
    pt: pt,
    ru: ru,
    sp: sp,
    tc: tc,
    th: th,
    vn: vn
};

class Lang {
    // eslint-disable-next-line no-unused-vars
    static Get(str, remove_spaces = false, must_translate = false) {
        if (remove_spaces) {
            str = str.replace(/ /g, '_').toUpperCase();
        }

        try {
            const entry = bindings[Cookies.get('lang')].find((entry) => entry.name === str);
            let composedregex = new RegExp('<[^>]*>', 'g');

            return entry.value.replaceAll(composedregex, '');
        } catch (e) {
            if (must_translate) {
                return null;
            } else {
                try {
                    // Fallback to English
                    const entry = en.find((entry) => entry.name === str);
                    let composedregex = new RegExp('<[^>]*>', 'g');

                    const ret = entry.value.replaceAll(composedregex, '');
                    return ret;
                } catch (error) {
                    // Total failure -> return original string
                    return str;
                }
            }
        }
    }
}
export default Lang;

Upvotes: 1

Views: 378

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074385

Caveat: I don't use SvelteKit. (Nothing against it or Svelte, I just don't use them, at least not yet.)

But the usual solution here is to use dynamic import(), like this (see *** comments):

// *** Since we always use `en`, import it statically
import en from "../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_en.csv";

// *** Why are the filenames not consistently aligned with the `lang` values?
// This map is unnecessary if the files are renamed (or the `lang` is updated
// to match them).
const langToFilenameFragment = {
    "jp": "JP",
    "kr": "kor",
};

// *** Import the cookie-defined language dynamically
const lang = Cookies.get("lang");
const fragment = langToFilenameFragment[lang] ?? lang; // *** Again, could avoid thsi
const bindings = {
    en,
    [lang]: await import(`../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_${fragment}.csv`),
};

class Lang {
    // eslint-disable-next-line no-unused-vars
    static Get(str, remove_spaces = false, must_translate = false) {
        if (remove_spaces) {
            str = str.replace(/ /g, "_").toUpperCase();
        }

        try {
            const entry = bindings[lang].find((entry) => entry.name === str); // *** Used `lang` here
            let composedregex = new RegExp("<[^>]*>", "g");

            return entry.value.replaceAll(composedregex, "");
        } catch (e) {
            if (must_translate) {
                return null;
            } else {
                try {
                    // Fallback to English
                    const entry = en.find((entry) => entry.name === str);
                    let composedregex = new RegExp("<[^>]*>", "g");

                    const ret = entry.value.replaceAll(composedregex, "");
                    return ret;
                } catch (error) {
                    // Total failure -> return original string
                    return str;
                }
            }
        }
    }
}
export default Lang;

If, like some bundlers, Svelte Kit doesn't like dynamic import() with a truly dynamic (rather than hardcoded) paths, you can get around that with a switch:

// *** Since we always use `en`, import it statically
import en from "../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_en.csv";

// *** Import the cookie-defined language dynamically
const lang = Cookies.get("lang");
const translation = await ((() => {
    switch (lang) {
        case "cn": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_cn.csv");
        case "de": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_de.csv");
        case "en": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_en.csv");
        case "fr": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_fr.csv");
        case "id": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_id.csv");
        case "jp": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_JP.csv");
        case "kr": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_kor.csv");
        case "pt": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_pt.csv");
        case "ru": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_ru.csv");
        case "sp": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_sp.csv");
        case "tc": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_tc.csv");
        case "th": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_th.csv");
        case "vn": return import("../dumps/files/assets/gameres/dat/cfg/lang/cfg_lang_vn.csv");
        default:   throw new Error(`Invalid 'lang' cookie "${lang}"`);
    }
})()).default;
const bindings = {
    en,
    [lang]: translation,
};

class Lang {
    // eslint-disable-next-line no-unused-vars
    static Get(str, remove_spaces = false, must_translate = false) {
        if (remove_spaces) {
            str = str.replace(/ /g, "_").toUpperCase();
        }

        try {
            const entry = bindings[lang].find((entry) => entry.name === str); // *** Used `lang` here
            let composedregex = new RegExp("<[^>]*>", "g");

            return entry.value.replaceAll(composedregex, "");
        } catch (e) {
            if (must_translate) {
                return null;
            } else {
                try {
                    // Fallback to English
                    const entry = en.find((entry) => entry.name === str);
                    let composedregex = new RegExp("<[^>]*>", "g");

                    const ret = entry.value.replaceAll(composedregex, "");
                    return ret;
                } catch (error) {
                    // Total failure -> return original string
                    return str;
                }
            }
        }
    }
}
export default Lang;

You may need a custom setting (sometimes it's a comment, like Webpack's webpackChunkName) telling SvelteKit to make each of those dynamically-imported modules its own bundle.

Upvotes: 1

Related Questions