Repository: digitalocean/nginxconfig.io
File: src/nginxconfig/templates/app.vue (Vue)

Code before:
                    if (!(data.value in i18nPacks)) data.computed = data.default;

                    // Update the locale
                    this.$i18n.locale = data.computed;
                },
                deep: true,
            },
        },
        mounted() {
            // Import any data from the URL query params, defaulting to one domain
            // Fallback to the window hash if no search query params, from the Angular version of nginxconfig
            // The config file watcher will handle setting the app as ready
            const query = window.location.search || window.location.hash.slice(1);
            // Adaptive system language,The system language is not read after the language is set
            if (!query.includes('global.app.lang') && typeof navigator !== undefined){
                if (typeof navigator.language === 'string'){
                    let lang = navigator.language.match(/(\w+)/g).map(function(value){
                        return value.toUpperCase();
                    });
                    lang[0] = lang[0].toLowerCase();
                    lang = lang.join('');
                    if (lang in i18nPacks){
                        this.$data.global.app.lang.value = lang;
                        this.$data.global.app.lang.computed = lang;
                    }
                }
            }
            importData(query, this.$data.domains, this.$data.global, this.$nextTick);

            // Send an initial GA event for column mode
            this.splitColumnEvent(true);
        },
        methods: {
            changes(index) {
                const data = this.$data.domains[index];
                const changes = Object.entries(data).reduce((prev, current) => {
                    if (current[0] === 'presets') return prev; // Ignore changes from presets
                    if (!isObject(current[1])) return prev; // Ignore non-objects (things that aren't tabs)
                    prev += Object.keys(current[1]).filter(key => isChanged(current[1][key], current[0], key)).length;
                    return prev;
                }, 0);
                if (changes) return ` (${changes.toLocaleString()})`;
                return '';
            },
            add() {
                const data = clone(Domain.delegated);

                // Avoid dupe domains
                let count = 1;
                while (this.$data.domains.some(d => d && d.server.domain.computed === data.server.domain.computed)) {
                    count++;

Review comment (suggestion):
```suggestion
                const rawLangs = new Set();
                
                // Get the user language
                if (Array.isArray(window.navigator.languages))
                    window.navigator.languages.forEach(lang => langs.add(lang));
                if (typeof window.navigator.language === 'string')
                    langs.add(window.navigator.language);
                if (Intl && 'DateTimeFormat' in Intl)
                    langs.add(Intl.DateTimeFormat().resolvedOptions().locale);
                
                const toPack = lang => lang.split('-')[0].toLowerCase() + lang.split('-').slice(1).map(x => x.toUpperCase()).join('-');
                const i18nPackLangs = i18nPacks.reduce((acc, data) => {
                    const lang = data.match(/^[a-z]+/)[0];
                    if (!(lang in acc)) acc[lang] = data;
                    return acc;
                }, {});
                
                // Try to get an exact region match, or fallback to language match
                const exactMatch = [...rawLangs.values()].find(x => i18nPacks.includes(toPack(x)));
                const langMatch = [...rawLangs.values()].find(x => i18nPackLangs.includes(x.split('-')[0].toLowerCase()));
                
                if (exactMatch || langMatch) {
                    this.$data.global.app.lang.value = exactMatch || i18nPackLangs[langMatch];
                    this.$data.global.app.lang.computed = exactMatch || i18nPackLangs[langMatch];
                }
```
I haven't tested this, but I think this will help with giving the best chances of making a positive region/language match.

Code after:

                    // Update the locale
                    this.$i18n.locale = data.computed;
                },
                deep: true,
            },
        },
        mounted() {
            // Import any data from the URL query params, defaulting to one domain
            // Fallback to the window hash if no search query params, from the Angular version of nginxconfig
            // The config file watcher will handle setting the app as ready
            const query = window.location.search || window.location.hash.slice(1);
            // Set language based on browser preferences
            if (!query.includes('global.app.lang') && typeof window === 'object' && typeof window.navigator === 'object'){
                const rawLangs = new Set();

                // Get the user language
                if (Array.isArray(window.navigator.languages))
                    window.navigator.languages.forEach(lang => rawLangs.add(lang));
                if (typeof window.navigator.language === 'string')
                    rawLangs.add(window.navigator.language);
                if (Intl && 'DateTimeFormat' in Intl)
                    rawLangs.add(Intl.DateTimeFormat().resolvedOptions().locale);

                const toPack = lang => lang.split('-')[0].toLowerCase() + lang.split('-').slice(1).map(x => x.toUpperCase()).join('-');

                // Get the support language
                const i18nPackLangs = Object.keys(i18nPacks);

                // Try to get an exact region match, or fallback to language match
                const exactMatch = [...rawLangs.values()].find(x => i18nPackLangs.includes(toPack(x)));
                const langMatch = [...rawLangs.values()].find(x => i18nPackLangs.includes(x.split('-')[0].toLowerCase())).split('-')[0].toLowerCase();

                if (exactMatch || langMatch) {
                    this.$data.global.app.lang.value = toPack(exactMatch) || langMatch.split('-')[0].toLowerCase();
                    this.$data.global.app.lang.computed = toPack(exactMatch) || langMatch.split('-')[0].toLowerCase();
                }
            }
            importData(query, this.$data.domains, this.$data.global, this.$nextTick);

            // Send an initial GA event for column mode
            this.splitColumnEvent(true);
        },
        methods: {
            changes(index) {
                const data = this.$data.domains[index];
                const changes = Object.entries(data).reduce((prev, current) => {
                    if (current[0] === 'presets') return prev; // Ignore changes from presets
                    if (!isObject(current[1])) return prev; // Ignore non-objects (things that aren't tabs)
                    prev += Object.keys(current[1]).filter(key => isChanged(current[1][key], current[0], key)).length;
                    return prev;