diff --git a/javascript/bilingual_localization.js b/javascript/bilingual_localization.js index b7c29d0..c903333 100644 --- a/javascript/bilingual_localization.js +++ b/javascript/bilingual_localization.js @@ -61,15 +61,19 @@ enabled: opts["bilingual_localization_enabled"], file: opts["bilingual_localization_file"], dirs: opts["bilingual_localization_dirs"], - order: opts["bilingual_localization_order"] + order: opts["bilingual_localization_order"], + enableLogger: opts["bilingual_localization_logger"] } - let { enabled, file, dirs } = config + let { enabled, file, dirs, enableLogger } = config if (!enabled || file === "None" || dirs === "None") return dirs = JSON.parse(dirs) + enableLogger && logger.init('Bilingual') + logger.log('Bilingual Localization initialized.') + // Load localization file i18n = JSON.parse(readFile(dirs[file])) @@ -114,7 +118,7 @@ if (!i18n) return // translation not ready. if (el.matches?.(ignore_selector)) return // ignore some elements. - if (el.title || el.tagName === 'SELECT') { + if (el.title) { doTranslate(el, el.title, 'title') } @@ -128,59 +132,30 @@ doTranslate(el, el.textContent, 'element') } - Array.from(el.childNodes).forEach(node => { - if (node.nodeName === '#text') { - if (rich) { - doTranslate(node, node.textContent, 'text') - return - } + if (deep || rich) { + Array.from(el.childNodes).forEach(node => { + if (node.nodeName === '#text') { + if (rich) { + doTranslate(node, node.textContent, 'text') + return + } - if (deep) { - doTranslate(node, node.textContent, 'element') + if (deep) { + doTranslate(node, node.textContent, 'element') + } + } else if (node.childNodes.length > 0) { + translateEl(node, { deep, rich }) } - } else if (node.childNodes.length > 0) { - translateEl(node, { deep, rich }) - } - }) + }) + } } const re_num = /^[\.\d]+$/, re_emoji = /[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\u{1F9B0}-\u{1F9B3}]/u function doTranslate(el, source, type) { - source = source.trim() - - /** - * Process the element's title property. - * Due to hint.js resetting the title when the UI is updated, some extra work is required here. - */ - if (type === 'title') { - const elDescriptor = Object.getOwnPropertyDescriptor(el, 'title') - if (elDescriptor && elDescriptor.get) return // already translated - - const title_translation = doTranslate(el, source) - let title = title_translation ? `${title_translation}\n${source}` : source - - // set the translation to the element title - el.setAttribute('title', title) - - // translate when title is changed - Object.defineProperty(el, 'title', { - get() { - return title - }, - set(value) { - const title_translation = doTranslate(el, value) - title = title_translation ? `${title_translation}\n${value}` : value - this.setAttribute('title', title) - } - }) - - return - } - if (el.__bilingual_localization_translated) return - + source = source.trim() if (!source) return if (re_num.test(source)) return if (re_emoji.test(source)) return @@ -208,12 +183,17 @@ } else { el.replaceWith(htmlEl) } + Object.defineProperty(el, '__bilingual_localization_translated', { value: true }) break; case 'option': el.textContent = `${translation} (${source})` break; + case 'title': + el.title = `${translation}\n${source}` + break; + case 'placeholder': el.placeholder = `${translation}\n\n${source}` break; @@ -221,8 +201,6 @@ default: return translation } - - Object.defineProperty(el, '__bilingual_localization_translated', { value: true }) } function querySelector(...args) { @@ -254,18 +232,21 @@ const logger = (function () { const loggerTimerMap = new Map() - const loggerConf = { badge: true, label: 'Logger' } + const loggerConf = { badge: true, label: 'Logger', enable: false } return new Proxy(console, { get: (target, propKey) => { if (propKey === 'init') { return (label) => { loggerConf.label = label + loggerConf.enable = true } } if (!(propKey in target)) return undefined return (...args) => { + if (!loggerConf.enable) return + let color = ['#39cfe1', '#006cab'] let label, start @@ -321,32 +302,35 @@ } gradioApp().appendChild($styleEL); - logger.init('Bilingual') - logger.log('Bilingual Localization initialized.') - let loaded = false let _count = 0 - onUiUpdate((m) => { + + const observer = new MutationObserver(mutations => { if (Object.keys(localization).length) return; // disabled if original translation enabled if (Object.keys(opts).length === 0) return; // does nothing if opts is not loaded let _nodesCount = 0, _now = performance.now() - m.forEach(mutation => { - mutation.addedNodes.forEach(node => { - if (node.className === 'bilingual__trans_wrapper') return - + for (const mutation of mutations) { + if (mutation.type === 'attributes') { _nodesCount++ - if (node.nodeType === 1 && node.className === 'output-html') { - translateEl(node, { rich: true }) - } else { - translateEl(node, { deep: true }) - } - }) - }) + translateEl(mutation.target) + } else { + mutation.addedNodes.forEach(node => { + if (node.className === 'bilingual__trans_wrapper') return + + _nodesCount++ + if (node.nodeType === 1 && node.className === 'output-html') { + translateEl(node, { rich: true }) + } else { + translateEl(node, { deep: true }) + } + }) + } + } if (_nodesCount > 0) { - logger.info(`UI Update #${_count++}: ${performance.now() - _now} ms, ${_nodesCount} nodes`) + logger.info(`UI Update #${_count++}: ${performance.now() - _now} ms, ${_nodesCount} nodes`, mutations) } if (loaded) return; @@ -355,6 +339,13 @@ loaded = true setup() }) + + observer.observe(gradioApp(), { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['title', 'placeholder'] + }) } // Init after page loaded diff --git a/scripts/bilingual_localization_helper.py b/scripts/bilingual_localization_helper.py index 29ff620..73ddcfa 100644 --- a/scripts/bilingual_localization_helper.py +++ b/scripts/bilingual_localization_helper.py @@ -17,6 +17,9 @@ def on_ui_settings(): BL_SECTION = ("bl", "Bilingual Localization") # enable in settings shared.opts.add_option("bilingual_localization_enabled", shared.OptionInfo(True, "Enable Bilingual Localization", section=BL_SECTION)) + + # enable devtools log + shared.opts.add_option("bilingual_localization_logger", shared.OptionInfo(False, "Enable Devtools Log", section=BL_SECTION)) # current localization file shared.opts.add_option("bilingual_localization_file", shared.OptionInfo("None", "Localization file (Please leave `User interface` - `Localization` as None)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(shared.cmd_opts.localizations_dir), section=BL_SECTION))