mirror of
https://github.com/vladmandic/automatic
synced 2026-09-18 16:54:33 +02:00
massive update to hints and add localization engine
Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
+9
-1
@@ -1,7 +1,15 @@
|
||||
# Change Log for SD.Next
|
||||
|
||||
## Update for 2025-02-07
|
||||
## Update for 2025-02-08
|
||||
|
||||
- **Hints**
|
||||
- added/updated 100+ ui hints!
|
||||
- [hints](https://github.com/vladmandic/wiki/Hints) documentation and contribution guide
|
||||
- **Localization**
|
||||
- full ui localization!
|
||||
english, croatian, spanish, french, italian, portuguese, chinese, japanese, korean, russian
|
||||
- set in *settings -> user interface -> language*
|
||||
- [localization](https://github.com/vladmandic/wiki/Locale) documentation
|
||||
- **Torch**:
|
||||
- for **zluda** set default to `torch==2.6.0+cu126`
|
||||
- **Other**:
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# TODO
|
||||
|
||||
- Check wiki links
|
||||
- Update hints wiki
|
||||
- Update changelog with docs and hints
|
||||
|
||||
Main ToDo list can be found at [GitHub projects](https://github.com/users/vladmandic/projects)
|
||||
|
||||
## Pending
|
||||
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env node
|
||||
// script used to localize sdnext ui and hints to multiple languages using google gemini ai
|
||||
|
||||
const fs = require('fs');
|
||||
const process = require('process');
|
||||
const { GoogleGenerativeAI } = require('@google/generative-ai');
|
||||
|
||||
const api_key = process.env.GOOGLE_AI_API_KEY;
|
||||
const model = 'gemini-2.0-flash-exp';
|
||||
const prompt = `
|
||||
Translate attached JSON from English to {language} using following rules: fields id and label should be preserved from original, field localized should be a translated version of field label and field hint should be translated in-place.
|
||||
Every JSON entry should have id, label, localized and hint fields. Output should be pure JSON without any additional text. To better match translation, context of the text is related to Stable Diffusion and topic of Generative AI.`;
|
||||
const languages = {
|
||||
hr: 'Croatian',
|
||||
de: 'German',
|
||||
es: 'Spanish',
|
||||
fr: 'French',
|
||||
it: 'Italian',
|
||||
pt: 'Portuguese',
|
||||
zh: 'Chinese',
|
||||
ja: 'Japanese',
|
||||
ko: 'Korean',
|
||||
ru: 'Russian',
|
||||
};
|
||||
const chunkLines = 100;
|
||||
|
||||
async function localize() {
|
||||
if (!api_key || api_key.length < 10) {
|
||||
console.error('localize: set GOOGLE_AI_API_KEY env variable with your API key');
|
||||
process.exit();
|
||||
}
|
||||
const genAI = new GoogleGenerativeAI(api_key);
|
||||
const instance = genAI.getGenerativeModel({ model });
|
||||
const raw = fs.readFileSync('html/locale_en.json');
|
||||
const json = JSON.parse(raw);
|
||||
for (const locale of Object.keys(languages)) {
|
||||
const lang = languages[locale];
|
||||
const target = prompt.replace('{language}', lang).trim();
|
||||
const output = {};
|
||||
const fn = `html/locale_${locale}.json`;
|
||||
for (const section of Object.keys(json)) {
|
||||
const data = json[section];
|
||||
output[section] = [];
|
||||
for (let i = 0; i < data.length; i += chunkLines) {
|
||||
let markdown;
|
||||
try {
|
||||
const chunk = data.slice(i, i + chunkLines);
|
||||
const result = await instance.generateContent([target, JSON.stringify(chunk)]);
|
||||
markdown = result.response.text();
|
||||
const text = markdown.replaceAll('```', '').replace(/^.*\n/, '');
|
||||
const parsed = JSON.parse(text);
|
||||
output[section].push(...parsed);
|
||||
console.log(`localize: locale=${locale} lang=${lang} section=${section} chunk=${chunk.length} output=${output[section].length} fn=${fn}`);
|
||||
} catch (err) {
|
||||
console.error('localize:', err);
|
||||
console.error('localize input:', { target, section, i });
|
||||
console.error('localize output:', { markdown });
|
||||
}
|
||||
}
|
||||
const txt = JSON.stringify(output, null, 2);
|
||||
fs.writeFileSync(fn, txt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localize();
|
||||
@@ -7,7 +7,7 @@ from rich import print # pylint: disable=redefined-builtin
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.argv.pop(0)
|
||||
fn = sys.argv[0] if len(sys.argv) > 0 else 'locale_en.json'
|
||||
fn = sys.argv[0] if len(sys.argv) > 0 else 'html/locale_en.json'
|
||||
if not os.path.isfile(fn):
|
||||
print(f'File not found: {fn}')
|
||||
sys.exit(1)
|
||||
|
||||
+6872
File diff suppressed because it is too large
Load Diff
+1024
-955
File diff suppressed because it is too large
Load Diff
+6872
File diff suppressed because it is too large
Load Diff
+6872
File diff suppressed because it is too large
Load Diff
+1782
File diff suppressed because it is too large
Load Diff
+6872
File diff suppressed because it is too large
Load Diff
+6872
File diff suppressed because it is too large
Load Diff
+6872
-643
File diff suppressed because it is too large
Load Diff
+6872
File diff suppressed because it is too large
Load Diff
+6872
File diff suppressed because it is too large
Load Diff
+6872
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -4,6 +4,7 @@ import sys
|
||||
import json
|
||||
import time
|
||||
import shutil
|
||||
import locale
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
@@ -366,9 +367,8 @@ def git(arg: str, folder: str = None, ignore: bool = False, optional: bool = Fal
|
||||
t_start = time.time()
|
||||
if args.skip_git:
|
||||
return ''
|
||||
if optional:
|
||||
if 'google.colab' in sys.modules:
|
||||
return ''
|
||||
if 'google.colab' in sys.modules:
|
||||
return ''
|
||||
git_cmd = os.environ.get('GIT', "git")
|
||||
if git_cmd != "git":
|
||||
git_cmd = os.path.abspath(git_cmd)
|
||||
@@ -481,6 +481,7 @@ def get_platform():
|
||||
'system': platform.system(),
|
||||
'release': release,
|
||||
'python': platform.python_version(),
|
||||
'locale': locale.getlocale(),
|
||||
'docker': os.environ.get('SD_DOCKER', None) is not None,
|
||||
# 'host': platform.node(),
|
||||
# 'version': platform.version(),
|
||||
@@ -1305,7 +1306,7 @@ def check_venv():
|
||||
t_start = time.time()
|
||||
import site
|
||||
pkg_path = [try_relpath(p) for p in site.getsitepackages() if os.path.exists(p)]
|
||||
log.debug(f'Packages: venv={try_relpath(sys.prefix)} site={pkg_path}')
|
||||
log.debug(f'Packages: prefix={try_relpath(sys.prefix)} site={pkg_path}')
|
||||
for p in pkg_path:
|
||||
invalid = []
|
||||
for f in os.listdir(p):
|
||||
|
||||
@@ -132,6 +132,7 @@ div#extras_scale_to_tab div.form { flex-direction: row; }
|
||||
width: 22em; min-height: 1.3em; font-size: var(--text-xs); transition: opacity 0.2s ease-in; pointer-events: none; opacity: 0; z-index: 999; }
|
||||
.tooltip-show { opacity: 0.9; }
|
||||
.toolbutton-selected { background: var(--background-fill-primary) !important; }
|
||||
.locale { position: fixed; top: 1em; right: 1em; background-color: none; padding: 0.1em; width: 1.2em; height: 1.2em; cursor: pointer; font-size: 0.8em; font-weight: 800; font-family: monospace; }
|
||||
#txt2img_hdr_color_row > div { min-width: unset !important; max-width: unset !important; }
|
||||
#txt2img_advanced_options, #img2img_advanced_options, #control_advanced_options { min-width: 100%; }
|
||||
#txt2img_advanced_options .gradio-checkbox, #img2img_advanced_options .gradio-checkbox, #control_advanced_options .gradio-checkbox { min-width: unset !important; max-width: fit-content; }
|
||||
|
||||
+73
-21
@@ -1,17 +1,40 @@
|
||||
const allLocales = ['en', 'de', 'es', 'fr', 'it', 'ja', 'ko', 'pt', 'hr', 'ru', 'zh'];
|
||||
const localeData = {
|
||||
prev: null,
|
||||
locale: null,
|
||||
data: [],
|
||||
timeout: null,
|
||||
finished: false,
|
||||
type: 2,
|
||||
el: null,
|
||||
hint: null,
|
||||
btn: null,
|
||||
};
|
||||
|
||||
async function cycleLocale() {
|
||||
console.log('cycleLocale', localeData.prev, localeData.locale);
|
||||
const index = allLocales.indexOf(localeData.prev);
|
||||
localeData.locale = allLocales[(index + 1) % allLocales.length];
|
||||
localeData.btn.innerText = localeData.locale;
|
||||
localeData.btn.style.backgroundColor = localeData.locale !== 'en' ? 'var(--primary-500)' : '';
|
||||
localeData.finished = false;
|
||||
localeData.data = [];
|
||||
localeData.prev = localeData.locale;
|
||||
window.opts.ui_locale = localeData.locale;
|
||||
await setHints(); // eslint-disable-line no-use-before-define
|
||||
}
|
||||
|
||||
async function tooltipCreate() {
|
||||
localeData.el = document.createElement('div');
|
||||
localeData.el.className = 'tooltip';
|
||||
localeData.el.id = 'tooltip-container';
|
||||
localeData.el.innerText = 'this is a hint';
|
||||
gradioApp().appendChild(localeData.el);
|
||||
localeData.hint = document.createElement('div');
|
||||
localeData.hint.className = 'tooltip';
|
||||
localeData.hint.id = 'tooltip-container';
|
||||
localeData.hint.innerText = 'this is a hint';
|
||||
gradioApp().appendChild(localeData.hint);
|
||||
localeData.btn = document.createElement('div');
|
||||
localeData.btn.className = 'locale';
|
||||
localeData.btn.id = 'locale-container';
|
||||
localeData.btn.innerText = localeData.locale;
|
||||
localeData.btn.onclick = cycleLocale;
|
||||
gradioApp().appendChild(localeData.btn);
|
||||
if (window.opts.tooltips === 'None') localeData.type = 0;
|
||||
if (window.opts.tooltips === 'Browser default') localeData.type = 1;
|
||||
if (window.opts.tooltips === 'UI tooltips') localeData.type = 2;
|
||||
@@ -19,18 +42,18 @@ async function tooltipCreate() {
|
||||
|
||||
async function tooltipShow(e) {
|
||||
if (e.target.dataset.hint) {
|
||||
localeData.el.classList.add('tooltip-show');
|
||||
localeData.el.innerHTML = `<b>${e.target.textContent}</b><br>${e.target.dataset.hint}`;
|
||||
localeData.hint.classList.add('tooltip-show');
|
||||
localeData.hint.innerHTML = `<b>${e.target.textContent}</b><br>${e.target.dataset.hint}`;
|
||||
if (e.clientX > window.innerWidth / 2) {
|
||||
localeData.el.classList.add('tooltip-left');
|
||||
localeData.hint.classList.add('tooltip-left');
|
||||
} else {
|
||||
localeData.el.classList.remove('tooltip-left');
|
||||
localeData.hint.classList.remove('tooltip-left');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function tooltipHide(e) {
|
||||
localeData.el.classList.remove('tooltip-show');
|
||||
localeData.hint.classList.remove('tooltip-show');
|
||||
}
|
||||
|
||||
async function validateHints(json, elements) {
|
||||
@@ -90,29 +113,57 @@ async function replaceButtonText(el) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getLocaleData(desiredLocale = null) {
|
||||
if (desiredLocale) desiredLocale = desiredLocale.split(':')[0];
|
||||
if (desiredLocale === 'Auto') {
|
||||
try {
|
||||
localeData.locale = navigator.languages && navigator.languages.length ? navigator.languages[0] : navigator.language;
|
||||
localeData.locale = localeData.locale.split('-')[0];
|
||||
localeData.prev = localeData.locale;
|
||||
} catch (e) {
|
||||
localeData.locale = 'en';
|
||||
log('getLocale', e);
|
||||
}
|
||||
} else {
|
||||
localeData.locale = desiredLocale || 'en';
|
||||
localeData.prev = localeData.locale;
|
||||
}
|
||||
log('getLocale', desiredLocale, localeData.locale);
|
||||
let res = await fetch(`/file=html/locale_${localeData.locale}.json`);
|
||||
if (!res || !res.ok) {
|
||||
localeData.locale = 'en';
|
||||
res = await fetch(`/file=html/locale_${localeData.locale}.json`);
|
||||
}
|
||||
const json = await res.json();
|
||||
return json;
|
||||
}
|
||||
|
||||
async function setHints(analyze = false) {
|
||||
let json = {};
|
||||
if (localeData.finished) return;
|
||||
if (localeData.data.length === 0) {
|
||||
const res = await fetch('/file=html/locale_en.json');
|
||||
json = await res.json();
|
||||
localeData.data = Object.values(json).flat().filter((e) => e.hint.length > 0);
|
||||
for (const e of localeData.data) e.label = e.label.toLowerCase().trim();
|
||||
}
|
||||
if (Object.keys(opts).length === 0) return;
|
||||
const elements = [
|
||||
...Array.from(gradioApp().querySelectorAll('button')),
|
||||
...Array.from(gradioApp().querySelectorAll('label > span')),
|
||||
...Array.from(gradioApp().querySelectorAll('.label-wrap > span')),
|
||||
];
|
||||
if (elements.length === 0) return;
|
||||
if (Object.keys(opts).length === 0) return;
|
||||
if (!localeData.el) tooltipCreate();
|
||||
if (localeData.data.length === 0) {
|
||||
json = await getLocaleData(window.opts.ui_locale);
|
||||
localeData.data = Object.values(json).flat().filter((e) => e.hint.length > 0);
|
||||
for (const e of localeData.data) e.label = e.label.toLowerCase().trim();
|
||||
}
|
||||
if (!localeData.hint) tooltipCreate();
|
||||
let localized = 0;
|
||||
let hints = 0;
|
||||
localeData.finished = true;
|
||||
const t0 = performance.now();
|
||||
for (const el of elements) {
|
||||
const found = localeData.data.find((l) => l.label === el.textContent.toLowerCase().trim());
|
||||
let found;
|
||||
if (el.dataset.original) found = localeData.data.find((l) => l.label === el.dataset.original.toLowerCase().trim());
|
||||
else found = localeData.data.find((l) => l.label === el.textContent.toLowerCase().trim());
|
||||
if (found?.localized?.length > 0) {
|
||||
if (!el.dataset.original) el.dataset.original = el.textContent;
|
||||
localized++;
|
||||
el.textContent = found.localized;
|
||||
}
|
||||
@@ -131,7 +182,8 @@ async function setHints(analyze = false) {
|
||||
}
|
||||
}
|
||||
const t1 = performance.now();
|
||||
log('setHints', { type: localeData.type, elements: elements.length, localized, hints, data: localeData.data.length, time: t1 - t0 });
|
||||
localeData.btn.style.backgroundColor = localeData.locale !== 'en' ? 'var(--primary-500)' : '';
|
||||
log('setHints', { type: localeData.type, locale: localeData.locale, elements: elements.length, localized, hints, data: localeData.data.length, time: t1 - t0 });
|
||||
// sortUIElements();
|
||||
if (analyze) {
|
||||
const [missingHints, orphanedHints] = await validateHints(json, elements);
|
||||
|
||||
@@ -793,6 +793,7 @@ options_templates.update(options_section(('ui', "User Interface"), {
|
||||
"theme_type": OptionInfo("Standard", "Theme type", gr.Radio, {"choices": ["Modern", "Standard", "None"]}),
|
||||
"theme_style": OptionInfo("Auto", "Theme mode", gr.Radio, {"choices": ["Auto", "Dark", "Light"]}),
|
||||
"gradio_theme": OptionInfo("black-teal", "UI theme", gr.Dropdown, lambda: {"choices": theme.list_themes()}, refresh=theme.refresh_themes),
|
||||
"ui_locale": OptionInfo("Auto", "UI locale", gr.Dropdown, lambda: {"choices": theme.list_locales()}),
|
||||
"autolaunch": OptionInfo(False, "Autolaunch browser upon startup"),
|
||||
"font_size": OptionInfo(14, "Font size", gr.Slider, {"minimum": 8, "maximum": 32, "step": 1, "visible": True}),
|
||||
"aspect_ratios": OptionInfo("1:1, 4:3, 3:2, 16:9, 16:10, 21:9, 2:3, 3:4, 9:16, 10:16, 9:21", "Allowed aspect ratios"),
|
||||
|
||||
+5
-1
@@ -36,6 +36,10 @@ def refresh_themes(no_update=False):
|
||||
return res
|
||||
|
||||
|
||||
def list_locales():
|
||||
return ['Auto', 'en: English', 'hr: Croatian', 'de: German', 'es: Spanish', 'fr: French', 'it: Italian', 'pt: Portuguese', 'zh: Chinese', 'ja: Japanese', 'ko: Korean', 'ru: Russian']
|
||||
|
||||
|
||||
def list_themes():
|
||||
extensions = [e.name for e in modules.extensions.extensions if e.enabled]
|
||||
if 'sd-webui-lobe-theme' in extensions and modules.shared.opts.gradio_theme == 'lobe':
|
||||
@@ -99,8 +103,8 @@ def reload_gradio_theme():
|
||||
modules.shared.opts.theme_type = 'Standard'
|
||||
theme_name = 'black-teal'
|
||||
|
||||
|
||||
modules.shared.opts.data['gradio_theme'] = theme_name
|
||||
modules.shared.log.info(f'UI locale: name="{modules.shared.opts.ui_locale}"')
|
||||
|
||||
if theme_name.lower() in ['lobe', 'cozy-nest']:
|
||||
modules.shared.log.info(f'UI theme extension: name="{theme_name}"')
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"esbuild": "^0.18.15"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/generative-ai": "^0.21.0",
|
||||
"argparse": "^2.0.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
|
||||
Reference in New Issue
Block a user