mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			128 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			128 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import { registerDebugFunction } from "./power-user.js";
 | 
						|
import { waitUntilCondition } from "./utils.js";
 | 
						|
 | 
						|
const storageKey = "language";
 | 
						|
export const localeData = await fetch("i18n.json").then(response => response.json());
 | 
						|
 | 
						|
function getMissingTranslations() {
 | 
						|
    const missingData = [];
 | 
						|
 | 
						|
    for (const language of localeData.lang) {
 | 
						|
        $(document).find("[data-i18n]").each(function () {
 | 
						|
            const keys = $(this).data("i18n").split(';'); // Multi-key entries are ; delimited
 | 
						|
            for (const key of keys) {
 | 
						|
                const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key
 | 
						|
                if (attributeMatch) { // attribute-tagged key
 | 
						|
                    const localizedValue = localeData?.[language]?.[attributeMatch[2]];
 | 
						|
                    if (!localizedValue) {
 | 
						|
                        missingData.push({ key, language, value: $(this).attr(attributeMatch[1]) });
 | 
						|
                    }
 | 
						|
                } else { // No attribute tag, treat as 'text'
 | 
						|
                    const localizedValue = localeData?.[language]?.[key];
 | 
						|
                    if (!localizedValue) {
 | 
						|
                        missingData.push({ key, language, value: $(this).text().trim() });
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    // Remove duplicates
 | 
						|
    const uniqueMissingData = [];
 | 
						|
    for (const { key, language, value } of missingData) {
 | 
						|
        if (!uniqueMissingData.some(x => x.key === key && x.language === language && x.value === value)) {
 | 
						|
            uniqueMissingData.push({ key, language, value });
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Sort by language, then key
 | 
						|
    uniqueMissingData.sort((a, b) => a.language.localeCompare(b.language) || a.key.localeCompare(b.key));
 | 
						|
 | 
						|
    // Map to { language: { key: value } }
 | 
						|
    const missingDataMap = {};
 | 
						|
    for (const { key, language, value } of uniqueMissingData) {
 | 
						|
        if (!missingDataMap[language]) {
 | 
						|
            missingDataMap[language] = {};
 | 
						|
        }
 | 
						|
        missingDataMap[language][key] = value;
 | 
						|
    }
 | 
						|
 | 
						|
    console.table(uniqueMissingData);
 | 
						|
    console.log(missingDataMap);
 | 
						|
 | 
						|
    toastr.success(`Found ${uniqueMissingData.length} missing translations. See browser console for details.`);
 | 
						|
}
 | 
						|
 | 
						|
export function applyLocale(root = document) {
 | 
						|
    const overrideLanguage = localStorage.getItem("language");
 | 
						|
    var language = overrideLanguage || navigator.language || navigator.userLanguage;
 | 
						|
    language = language.toLowerCase();
 | 
						|
    //load the appropriate language file
 | 
						|
    if (localeData.lang.indexOf(language) < 0) language = "en";
 | 
						|
 | 
						|
    const $root = root instanceof Document ? $(root) : $(new DOMParser().parseFromString(root, "text/html"));
 | 
						|
 | 
						|
    //find all the elements with `data-i18n` attribute
 | 
						|
    $root.find("[data-i18n]").each(function () {
 | 
						|
        //read the translation from the language data
 | 
						|
        const keys = $(this).data("i18n").split(';'); // Multi-key entries are ; delimited
 | 
						|
        for (const key of keys) {
 | 
						|
            const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key
 | 
						|
            if (attributeMatch) { // attribute-tagged key
 | 
						|
                const localizedValue = localeData?.[language]?.[attributeMatch[2]];
 | 
						|
                if (localizedValue) {
 | 
						|
                    $(this).attr(attributeMatch[1], localizedValue);
 | 
						|
                }
 | 
						|
            } else { // No attribute tag, treat as 'text'
 | 
						|
                const localizedValue = localeData?.[language]?.[key];
 | 
						|
                if (localizedValue) {
 | 
						|
                    $(this).text(localizedValue);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    });
 | 
						|
 | 
						|
    if (root !== document) {
 | 
						|
        return $root.get(0).body.innerHTML;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
function addLanguagesToDropdown() {
 | 
						|
    if (!Array.isArray(localeData?.lang)) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    for (const lang of localeData.lang) {
 | 
						|
        const option = document.createElement('option');
 | 
						|
        option.value = lang;
 | 
						|
        option.innerText = lang;
 | 
						|
        $('#ui_language_select').append(option);
 | 
						|
    }
 | 
						|
 | 
						|
    const selectedLanguage = localStorage.getItem(storageKey);
 | 
						|
    if (selectedLanguage) {
 | 
						|
        $('#ui_language_select').val(selectedLanguage);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
jQuery(async () => {
 | 
						|
    waitUntilCondition(() => !!localeData);
 | 
						|
    applyLocale();
 | 
						|
    addLanguagesToDropdown();
 | 
						|
 | 
						|
    $('#ui_language_select').on('change', async function () {
 | 
						|
        const language = String($(this).val());
 | 
						|
 | 
						|
        if (language) {
 | 
						|
            localStorage.setItem(storageKey, language);
 | 
						|
        } else {
 | 
						|
            localStorage.removeItem(storageKey);
 | 
						|
        }
 | 
						|
 | 
						|
        location.reload();
 | 
						|
    });
 | 
						|
 | 
						|
    registerDebugFunction('getMissingTranslations', 'Get missing translations', 'Detects missing localization data and dumps the data into the browser console.', getMissingTranslations);
 | 
						|
    registerDebugFunction('applyLocale', 'Apply locale', 'Reapplies the currently selected locale to the page.', applyLocale);
 | 
						|
});
 |