Search code examples
javascriptsvelteserver-side-renderingsveltekit

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;

Solution

  • 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.