(function () { const customCSS = ` .bilingual__trans_wrapper { display: inline-flex; flex-direction: column; align-items: center; font-size: 13px; line-height: 1; } .bilingual__trans_wrapper em { font-style: normal; } fieldset span.text-gray-500:has(.bilingual__trans_wrapper), .gr-block.gr-box span.text-gray-500:has(.bilingual__trans_wrapper), label.block span:has(.bilingual__trans_wrapper) { top: -0.8em; line-height: 1; } #txtimg_hr_finalres .bilingual__trans_wrapper em, #tab_ti .output-html .bilingual__trans_wrapper em, #dynamic-prompting .output-html .bilingual__trans_wrapper em, #txt2img_script_container .output-html .bilingual__trans_wrapper em, #available_extensions .extension-tag .bilingual__trans_wrapper em { display: none; } #settings .bilingual__trans_wrapper:not(#settings .tabitem .bilingual__trans_wrapper), label>span>.bilingual__trans_wrapper, .w-full>span>.bilingual__trans_wrapper, .output-html .bilingual__trans_wrapper:not(th .bilingual__trans_wrapper), .output-markdown .bilingual__trans_wrapper, .posex_setting_cont .bilingual__trans_wrapper:not(.posex_bg .bilingual__trans_wrapper) /* Posex extension */ { font-size: 12px; align-items: flex-start; } #extensions label .bilingual__trans_wrapper, #available_extensions td .bilingual__trans_wrapper { font-size: inherit; line-height: inherit; } textarea::placeholder { line-height: 1; padding: 4px 0; } label>span { line-height: 1; } div[data-testid="image"]>div>div.touch-none>div { background-color: rgba(255, 255, 255, .6); color: #222; } /* Posex extension */ .posex_bg { white-space: nowrap; } ` let i18n = null, i18nRegex = {}, config = null; // First load function setup() { config = { enabled: opts["bilingual_localization_enabled"], file: opts["bilingual_localization_file"], dirs: opts["bilingual_localization_dirs"], order: opts["bilingual_localization_order"], enableLogger: opts["bilingual_localization_logger"] } 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]), (key, value) => { // parse regex translations if (key.startsWith('@@')) { i18nRegex[key.slice(2)] = value } else { return value } }) logger.group('Localization file loaded.') logger.log('i18n', i18n) logger.log('i18nRegex', i18nRegex) logger.groupEnd() translatePage() } // Translate page function translatePage() { if (!i18n) return logger.time('Full Page') querySelectorAll([ "label span, fieldset span, button", // major label and button description text "textarea[placeholder], select, option", // text box placeholder and select element ".transition > div > span:not([class])", // collapse panel added by extension ".tabitem .pointer-events-none", // upper left corner of image upload panel "#modelmerger_interp_description .output-html", // model merger description "#lightboxModal span" // image preview lightbox ]) .forEach(el => translateEl(el, { deep: true })) querySelectorAll([ 'div[data-testid="image"] > div > div', // description of image upload panel '#extras_image_batch > div', // description of extras image batch file upload panel ".output-html:not(#footer), .output-markdown", // output html exclude footer '#dynamic-prompting' // dynamic-prompting extension ]) .forEach(el => translateEl(el, { rich: true })) logger.timeEnd('Full Page') } const ignore_selector = [ '.bilingual__trans_wrapper', // self '.resultsFlexContainer', // tag-autocomplete '#setting_sd_model_checkpoint select', // model checkpoint '#setting_sd_vae select', // vae model '#txt2img_styles, #img2txt_styles', // styles select '.extra-network-cards .card .actions .name', // extra network cards name 'script, style, svg, g, path', // script / style / svg elements ] // Translate element function translateEl(el, { deep = false, rich = false } = {}) { if (!i18n) return // translation not ready. if (el.matches?.(ignore_selector)) return // ignore some elements. if (el.title) { doTranslate(el, el.title, 'title') } if (el.placeholder) { doTranslate(el, el.placeholder, 'placeholder') } if (el.tagName === 'OPTION') { doTranslate(el, el.textContent, 'option') } 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') } } else if (node.childNodes.length > 0) { translateEl(node, { deep, rich }) } }) } else { doTranslate(el, el.textContent, 'element') } } const re_num = /^[\.\d]+$/, re_emoji = /[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\u{1F9B0}-\u{1F9B3}]/u function doTranslate(el, source, type) { if (el.__bilingual_localization_translated) return source = source.trim() if (!source) return if (re_num.test(source)) return // if (re_emoji.test(source)) return let translation = i18n[source] if (!translation) { for (let regex in i18nRegex) { regex = getRegex(regex) if (regex && regex.test(source)) { logger.log('regex', regex, source) translation = source.replace(regex, i18nRegex[regex]) break; } } } if (!translation) return if (source === translation) return if (config.order === "Original First") { [source, translation] = [translation, source] } switch (type) { case 'text': el.textContent = translation break; case 'element': const htmlStr = `