I18N
I18N
This commit is contained in:
		
							parent
							
								
									e4afe5819b
								
							
						
					
					
						commit
						9183cc0c71
					
				@ -6,6 +6,10 @@ import fcbh.cli_args as fcbh_cli
 | 
			
		||||
fcbh_cli.parser.add_argument("--share", action='store_true', help="Set whether to share on Gradio.")
 | 
			
		||||
fcbh_cli.parser.add_argument("--preset", type=str, default=None, help="Apply specified UI preset.")
 | 
			
		||||
 | 
			
		||||
fcbh_cli.parser.add_argument("--language", type=str, default=None,
 | 
			
		||||
                             help="Translate UI using json files in [language] folder. "
 | 
			
		||||
                                  "For example, [--language example] will use [language/example.json] for translation.")
 | 
			
		||||
 | 
			
		||||
fcbh_cli.args = fcbh_cli.parser.parse_args()
 | 
			
		||||
fcbh_cli.args.disable_cuda_malloc = True
 | 
			
		||||
fcbh_cli.args.auto_launch = True
 | 
			
		||||
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
version = '2.1.718'
 | 
			
		||||
version = '2.1.719'
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										205
									
								
								javascript/localization.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								javascript/localization.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,205 @@
 | 
			
		||||
 | 
			
		||||
// localization = {} -- the dict with translations is created by the backend
 | 
			
		||||
 | 
			
		||||
var ignore_ids_for_localization = {
 | 
			
		||||
    setting_sd_hypernetwork: 'OPTION',
 | 
			
		||||
    setting_sd_model_checkpoint: 'OPTION',
 | 
			
		||||
    modelmerger_primary_model_name: 'OPTION',
 | 
			
		||||
    modelmerger_secondary_model_name: 'OPTION',
 | 
			
		||||
    modelmerger_tertiary_model_name: 'OPTION',
 | 
			
		||||
    train_embedding: 'OPTION',
 | 
			
		||||
    train_hypernetwork: 'OPTION',
 | 
			
		||||
    txt2img_styles: 'OPTION',
 | 
			
		||||
    img2img_styles: 'OPTION',
 | 
			
		||||
    setting_random_artist_categories: 'OPTION',
 | 
			
		||||
    setting_face_restoration_model: 'OPTION',
 | 
			
		||||
    setting_realesrgan_enabled_models: 'OPTION',
 | 
			
		||||
    extras_upscaler_1: 'OPTION',
 | 
			
		||||
    extras_upscaler_2: 'OPTION',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
var re_num = /^[.\d]+$/;
 | 
			
		||||
var re_emoji = /[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\u{1F9B0}-\u{1F9B3}]/u;
 | 
			
		||||
 | 
			
		||||
var original_lines = {};
 | 
			
		||||
var translated_lines = {};
 | 
			
		||||
 | 
			
		||||
function hasLocalization() {
 | 
			
		||||
    return window.localization && Object.keys(window.localization).length > 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function textNodesUnder(el) {
 | 
			
		||||
    var n, a = [], walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
 | 
			
		||||
    while ((n = walk.nextNode())) a.push(n);
 | 
			
		||||
    return a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function canBeTranslated(node, text) {
 | 
			
		||||
    if (!text) return false;
 | 
			
		||||
    if (!node.parentElement) return false;
 | 
			
		||||
 | 
			
		||||
    var parentType = node.parentElement.nodeName;
 | 
			
		||||
    if (parentType == 'SCRIPT' || parentType == 'STYLE' || parentType == 'TEXTAREA') return false;
 | 
			
		||||
 | 
			
		||||
    if (parentType == 'OPTION' || parentType == 'SPAN') {
 | 
			
		||||
        var pnode = node;
 | 
			
		||||
        for (var level = 0; level < 4; level++) {
 | 
			
		||||
            pnode = pnode.parentElement;
 | 
			
		||||
            if (!pnode) break;
 | 
			
		||||
 | 
			
		||||
            if (ignore_ids_for_localization[pnode.id] == parentType) return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (re_num.test(text)) return false;
 | 
			
		||||
    if (re_emoji.test(text)) return false;
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getTranslation(text) {
 | 
			
		||||
    if (!text) return undefined;
 | 
			
		||||
 | 
			
		||||
    if (translated_lines[text] === undefined) {
 | 
			
		||||
        original_lines[text] = 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var tl = localization[text];
 | 
			
		||||
    if (tl !== undefined) {
 | 
			
		||||
        translated_lines[tl] = 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return tl;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function processTextNode(node) {
 | 
			
		||||
    var text = node.textContent.trim();
 | 
			
		||||
 | 
			
		||||
    if (!canBeTranslated(node, text)) return;
 | 
			
		||||
 | 
			
		||||
    var tl = getTranslation(text);
 | 
			
		||||
    if (tl !== undefined) {
 | 
			
		||||
        node.textContent = tl;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function processNode(node) {
 | 
			
		||||
    if (node.nodeType == 3) {
 | 
			
		||||
        processTextNode(node);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (node.title) {
 | 
			
		||||
        let tl = getTranslation(node.title);
 | 
			
		||||
        if (tl !== undefined) {
 | 
			
		||||
            node.title = tl;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (node.placeholder) {
 | 
			
		||||
        let tl = getTranslation(node.placeholder);
 | 
			
		||||
        if (tl !== undefined) {
 | 
			
		||||
            node.placeholder = tl;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    textNodesUnder(node).forEach(function(node) {
 | 
			
		||||
        processTextNode(node);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function localizeWholePage() {
 | 
			
		||||
    processNode(gradioApp());
 | 
			
		||||
 | 
			
		||||
    function elem(comp) {
 | 
			
		||||
        var elem_id = comp.props.elem_id ? comp.props.elem_id : "component-" + comp.id;
 | 
			
		||||
        return gradioApp().getElementById(elem_id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (var comp of window.gradio_config.components) {
 | 
			
		||||
        if (comp.props.webui_tooltip) {
 | 
			
		||||
            let e = elem(comp);
 | 
			
		||||
 | 
			
		||||
            let tl = e ? getTranslation(e.title) : undefined;
 | 
			
		||||
            if (tl !== undefined) {
 | 
			
		||||
                e.title = tl;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (comp.props.placeholder) {
 | 
			
		||||
            let e = elem(comp);
 | 
			
		||||
            let textbox = e ? e.querySelector('[placeholder]') : null;
 | 
			
		||||
 | 
			
		||||
            let tl = textbox ? getTranslation(textbox.placeholder) : undefined;
 | 
			
		||||
            if (tl !== undefined) {
 | 
			
		||||
                textbox.placeholder = tl;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function dumpTranslations() {
 | 
			
		||||
    if (!hasLocalization()) {
 | 
			
		||||
        // If we don't have any localization,
 | 
			
		||||
        // we will not have traversed the app to find
 | 
			
		||||
        // original_lines, so do that now.
 | 
			
		||||
        localizeWholePage();
 | 
			
		||||
    }
 | 
			
		||||
    var dumped = {};
 | 
			
		||||
    if (localization.rtl) {
 | 
			
		||||
        dumped.rtl = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const text in original_lines) {
 | 
			
		||||
        if (dumped[text] !== undefined) continue;
 | 
			
		||||
        dumped[text] = localization[text] || text;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return dumped;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function download_localization() {
 | 
			
		||||
    var text = JSON.stringify(dumpTranslations(), null, 4);
 | 
			
		||||
 | 
			
		||||
    var element = document.createElement('a');
 | 
			
		||||
    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
 | 
			
		||||
    element.setAttribute('download', "localization.json");
 | 
			
		||||
    element.style.display = 'none';
 | 
			
		||||
    document.body.appendChild(element);
 | 
			
		||||
 | 
			
		||||
    element.click();
 | 
			
		||||
 | 
			
		||||
    document.body.removeChild(element);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
document.addEventListener("DOMContentLoaded", function() {
 | 
			
		||||
    if (!hasLocalization()) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onUiUpdate(function(m) {
 | 
			
		||||
        m.forEach(function(mutation) {
 | 
			
		||||
            mutation.addedNodes.forEach(function(node) {
 | 
			
		||||
                processNode(node);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    localizeWholePage();
 | 
			
		||||
 | 
			
		||||
    if (localization.rtl) { // if the language is from right to left,
 | 
			
		||||
        (new MutationObserver((mutations, observer) => { // wait for the style to load
 | 
			
		||||
            mutations.forEach(mutation => {
 | 
			
		||||
                mutation.addedNodes.forEach(node => {
 | 
			
		||||
                    if (node.tagName === 'STYLE') {
 | 
			
		||||
                        observer.disconnect();
 | 
			
		||||
 | 
			
		||||
                        for (const x of node.sheet.rules) { // find all rtl media rules
 | 
			
		||||
                            if (Array.from(x.media || []).includes('rtl')) {
 | 
			
		||||
                                x.media.appendMedium('all'); // enable them
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        })).observe(gradioApp(), {childList: true});
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
// based on https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/v1.6.0/script.js
 | 
			
		||||
 | 
			
		||||
function gradioApp() {
 | 
			
		||||
    const elems = document.getElementsByTagName('gradio-app');
 | 
			
		||||
    const elem = elems.length == 0 ? document : elems[0];
 | 
			
		||||
@ -12,22 +11,162 @@ function gradioApp() {
 | 
			
		||||
    return elem.shadowRoot ? elem.shadowRoot : elem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function playNotification() {
 | 
			
		||||
    gradioApp().querySelector('#audio_notification audio')?.play();
 | 
			
		||||
/**
 | 
			
		||||
 * Get the currently selected top-level UI tab button (e.g. the button that says "Extras").
 | 
			
		||||
 */
 | 
			
		||||
function get_uiCurrentTab() {
 | 
			
		||||
    return gradioApp().querySelector('#tabs > .tab-nav > button.selected');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
document.addEventListener('keydown', function(e) {
 | 
			
		||||
    var handled = false;
 | 
			
		||||
    if (e.key !== undefined) {
 | 
			
		||||
        if ((e.key == "Enter" && (e.metaKey || e.ctrlKey || e.altKey))) handled = true;
 | 
			
		||||
    } else if (e.keyCode !== undefined) {
 | 
			
		||||
        if ((e.keyCode == 13 && (e.metaKey || e.ctrlKey || e.altKey))) handled = true;
 | 
			
		||||
/**
 | 
			
		||||
 * Get the first currently visible top-level UI tab content (e.g. the div hosting the "txt2img" UI).
 | 
			
		||||
 */
 | 
			
		||||
function get_uiCurrentTabContent() {
 | 
			
		||||
    return gradioApp().querySelector('#tabs > .tabitem[id^=tab_]:not([style*="display: none"])');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var uiUpdateCallbacks = [];
 | 
			
		||||
var uiAfterUpdateCallbacks = [];
 | 
			
		||||
var uiLoadedCallbacks = [];
 | 
			
		||||
var uiTabChangeCallbacks = [];
 | 
			
		||||
var optionsChangedCallbacks = [];
 | 
			
		||||
var uiAfterUpdateTimeout = null;
 | 
			
		||||
var uiCurrentTab = null;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Register callback to be called at each UI update.
 | 
			
		||||
 * The callback receives an array of MutationRecords as an argument.
 | 
			
		||||
 */
 | 
			
		||||
function onUiUpdate(callback) {
 | 
			
		||||
    uiUpdateCallbacks.push(callback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Register callback to be called soon after UI updates.
 | 
			
		||||
 * The callback receives no arguments.
 | 
			
		||||
 *
 | 
			
		||||
 * This is preferred over `onUiUpdate` if you don't need
 | 
			
		||||
 * access to the MutationRecords, as your function will
 | 
			
		||||
 * not be called quite as often.
 | 
			
		||||
 */
 | 
			
		||||
function onAfterUiUpdate(callback) {
 | 
			
		||||
    uiAfterUpdateCallbacks.push(callback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Register callback to be called when the UI is loaded.
 | 
			
		||||
 * The callback receives no arguments.
 | 
			
		||||
 */
 | 
			
		||||
function onUiLoaded(callback) {
 | 
			
		||||
    uiLoadedCallbacks.push(callback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Register callback to be called when the UI tab is changed.
 | 
			
		||||
 * The callback receives no arguments.
 | 
			
		||||
 */
 | 
			
		||||
function onUiTabChange(callback) {
 | 
			
		||||
    uiTabChangeCallbacks.push(callback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Register callback to be called when the options are changed.
 | 
			
		||||
 * The callback receives no arguments.
 | 
			
		||||
 * @param callback
 | 
			
		||||
 */
 | 
			
		||||
function onOptionsChanged(callback) {
 | 
			
		||||
    optionsChangedCallbacks.push(callback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function executeCallbacks(queue, arg) {
 | 
			
		||||
    for (const callback of queue) {
 | 
			
		||||
        try {
 | 
			
		||||
            callback(arg);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.error("error running callback", callback, ":", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (handled) {
 | 
			
		||||
        var button = gradioApp().querySelector('button[id=generate_button]');
 | 
			
		||||
        if (button) {
 | 
			
		||||
            button.click();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Schedule the execution of the callbacks registered with onAfterUiUpdate.
 | 
			
		||||
 * The callbacks are executed after a short while, unless another call to this function
 | 
			
		||||
 * is made before that time. IOW, the callbacks are executed only once, even
 | 
			
		||||
 * when there are multiple mutations observed.
 | 
			
		||||
 */
 | 
			
		||||
function scheduleAfterUiUpdateCallbacks() {
 | 
			
		||||
    clearTimeout(uiAfterUpdateTimeout);
 | 
			
		||||
    uiAfterUpdateTimeout = setTimeout(function() {
 | 
			
		||||
        executeCallbacks(uiAfterUpdateCallbacks);
 | 
			
		||||
    }, 200);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var executedOnLoaded = false;
 | 
			
		||||
 | 
			
		||||
document.addEventListener("DOMContentLoaded", function() {
 | 
			
		||||
    var mutationObserver = new MutationObserver(function(m) {
 | 
			
		||||
        if (!executedOnLoaded && gradioApp().querySelector('#txt2img_prompt')) {
 | 
			
		||||
            executedOnLoaded = true;
 | 
			
		||||
            executeCallbacks(uiLoadedCallbacks);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        executeCallbacks(uiUpdateCallbacks, m);
 | 
			
		||||
        scheduleAfterUiUpdateCallbacks();
 | 
			
		||||
        const newTab = get_uiCurrentTab();
 | 
			
		||||
        if (newTab && (newTab !== uiCurrentTab)) {
 | 
			
		||||
            uiCurrentTab = newTab;
 | 
			
		||||
            executeCallbacks(uiTabChangeCallbacks);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    mutationObserver.observe(gradioApp(), {childList: true, subtree: true});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a ctrl+enter as a shortcut to start a generation
 | 
			
		||||
 */
 | 
			
		||||
document.addEventListener('keydown', function(e) {
 | 
			
		||||
    const isEnter = e.key === 'Enter' || e.keyCode === 13;
 | 
			
		||||
    const isModifierKey = e.metaKey || e.ctrlKey || e.altKey;
 | 
			
		||||
 | 
			
		||||
    const interruptButton = get_uiCurrentTabContent().querySelector('button[id$=_interrupt]');
 | 
			
		||||
    const generateButton = get_uiCurrentTabContent().querySelector('button[id$=_generate]');
 | 
			
		||||
 | 
			
		||||
    if (isEnter && isModifierKey) {
 | 
			
		||||
        if (interruptButton.style.display === 'block') {
 | 
			
		||||
            interruptButton.click();
 | 
			
		||||
            setTimeout(function() {
 | 
			
		||||
                generateButton.click();
 | 
			
		||||
            }, 500);
 | 
			
		||||
        } else {
 | 
			
		||||
            generateButton.click();
 | 
			
		||||
        }
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * checks that a UI element is not in another hidden element or tab content
 | 
			
		||||
 */
 | 
			
		||||
function uiElementIsVisible(el) {
 | 
			
		||||
    if (el === document) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const computedStyle = getComputedStyle(el);
 | 
			
		||||
    const isVisible = computedStyle.display !== 'none';
 | 
			
		||||
 | 
			
		||||
    if (!isVisible) return false;
 | 
			
		||||
    return uiElementIsVisible(el.parentNode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function uiElementInSight(el) {
 | 
			
		||||
    const clRect = el.getBoundingClientRect();
 | 
			
		||||
    const windowHeight = window.innerHeight;
 | 
			
		||||
    const isOnScreen = clRect.bottom > 0 && clRect.top < windowHeight;
 | 
			
		||||
 | 
			
		||||
    return isOnScreen;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function playNotification() {
 | 
			
		||||
    gradioApp().querySelector('#audio_notification audio')?.play();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								language/example.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								language/example.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "Generate": "生成",
 | 
			
		||||
  "Input Image": "入力画像",
 | 
			
		||||
  "Advanced": "고급",
 | 
			
		||||
  "SAI 3D Model": "SAI 3D Modèle"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								modules/localization.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								modules/localization.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
localization_root = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'language')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def localization_js(filename):
 | 
			
		||||
    data = {}
 | 
			
		||||
 | 
			
		||||
    if isinstance(filename, str):
 | 
			
		||||
        full_name = os.path.abspath(os.path.join(localization_root, filename + '.json'))
 | 
			
		||||
        if os.path.exists(full_name):
 | 
			
		||||
            try:
 | 
			
		||||
                with open(full_name, encoding='utf-8') as f:
 | 
			
		||||
                    data = json.load(f)
 | 
			
		||||
                    assert isinstance(data, dict)
 | 
			
		||||
                    for k, v in data.items():
 | 
			
		||||
                        assert isinstance(k, str)
 | 
			
		||||
                        assert isinstance(v, str)
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                print(str(e))
 | 
			
		||||
                print(f'Failed to load localization file {full_name}')
 | 
			
		||||
 | 
			
		||||
    return f"window.localization = {json.dumps(data)}"
 | 
			
		||||
@ -2,6 +2,10 @@
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import gradio as gr
 | 
			
		||||
import args_manager
 | 
			
		||||
 | 
			
		||||
from modules.localization import localization_js
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
GradioTemplateResponseOriginal = gr.routes.templates.TemplateResponse
 | 
			
		||||
 | 
			
		||||
@ -21,8 +25,11 @@ def webpath(fn):
 | 
			
		||||
def javascript_html():
 | 
			
		||||
    script_js_path = webpath('javascript/script.js')
 | 
			
		||||
    context_menus_js_path = webpath('javascript/contextMenus.js')
 | 
			
		||||
    head = f'<script type="text/javascript" src="{script_js_path}"></script>\n'
 | 
			
		||||
    localization_js_path = webpath('javascript/localization.js')
 | 
			
		||||
    head = f'<script type="text/javascript">{localization_js(args_manager.args.language)}</script>\n'
 | 
			
		||||
    head += f'<script type="text/javascript" src="{script_js_path}"></script>\n'
 | 
			
		||||
    head += f'<script type="text/javascript" src="{context_menus_js_path}"></script>\n'
 | 
			
		||||
    head += f'<script type="text/javascript" src="{localization_js_path}"></script>\n'
 | 
			
		||||
    return head
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								readme.md
									
									
									
									
									
								
							@ -296,3 +296,36 @@ Special thanks to [twri](https://github.com/twri) and [3Diva](https://github.com
 | 
			
		||||
## Update Log
 | 
			
		||||
 | 
			
		||||
The log is [here](update_log.md).
 | 
			
		||||
 | 
			
		||||
# Localization/Translation/I18N
 | 
			
		||||
 | 
			
		||||
**We need your help!** Please help with translating Fooocus to international languages.
 | 
			
		||||
 | 
			
		||||
You can put json files in the `language` folder to translate the user interface.
 | 
			
		||||
 | 
			
		||||
For example, below is the content of `Fooocus/language/example.json`:
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "Generate": "生成",
 | 
			
		||||
  "Input Image": "入力画像",
 | 
			
		||||
  "Advanced": "고급",
 | 
			
		||||
  "SAI 3D Model": "SAI 3D Modèle"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If you add `--language example` arg, Fooocus will read `Fooocus/language/example.json` to translate the UI.
 | 
			
		||||
 | 
			
		||||
For example, you can edit the ending line of Windows `run.bat` as
 | 
			
		||||
 | 
			
		||||
    .\python_embeded\python.exe -s Fooocus\entry_with_update.py --language example
 | 
			
		||||
 | 
			
		||||
Or `run_anime.bat` as
 | 
			
		||||
 | 
			
		||||
    .\python_embeded\python.exe -s Fooocus\entry_with_update.py --language example --preset anime
 | 
			
		||||
 | 
			
		||||
Or `run_realistic.bat` as
 | 
			
		||||
 | 
			
		||||
    .\python_embeded\python.exe -s Fooocus\entry_with_update.py --language example --preset realistic
 | 
			
		||||
 | 
			
		||||
For practical translation, you may create your own file like `Fooocus/language/jp.json` or `Fooocus/language/cn.json` and then use flag `--language jp` or `--language cn`. Apparently, these files do not exist now. **We need your help to create these files!**
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,7 @@
 | 
			
		||||
# 2.1.719
 | 
			
		||||
 | 
			
		||||
* I18N
 | 
			
		||||
 | 
			
		||||
# 2.1.718
 | 
			
		||||
 | 
			
		||||
* Corrected handling dash in wildcard names, more wildcards (extended-color).
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user