Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Localization support #6355

Closed
wants to merge 14 commits into from
Closed
8 changes: 7 additions & 1 deletion docs/06 - Session Tab.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Here you can restart the UI with new settings.
* **Available extensions**: shows a list of extensions available under `text-generation-webui/extensions`.
* **Boolean command-line flags**: shows command-line flags of bool (true/false) type.

After selecting your desired flags and extensions, you can restart the UI by clicking on **Apply flags/extensions and restart**.
After selecting your desired flags and extensions, you can restart the UI by clicking on **Apply flags/extensions/localization and restart**.

## Install or update an extension

Expand All @@ -30,3 +30,9 @@ If you used the one-click installer, this command should be executed in the term
The **Save UI defaults to settings.yaml** button gathers the visible values in the UI and saves them to settings.yaml so that your settings will persist across multiple restarts of the UI.

Note that preset parameters like temperature are not individually saved, so you need to first save your preset and select it in the preset menu before saving the defaults.

## Localization

You can get a localization file from community, put the json file under localization folder, and then click on **Apply flags/extensions/localization and restart** button to apply the localization you have selected.

If there's no localization in your language, you can make your own! Just press the **Download localization template** button, change the values, and save the file in the localization folder. Then you can select your localization from the dropdown menu. Remember to rename localization.json to your language code (e.g. en.json, de.json, etc.) so others can know which language it is.
212 changes: 212 additions & 0 deletions js/localization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// From https://github.com/AUTOMATIC1111/stable-diffusion-webui

// localization = {} -- the dict with translations is created by the backend

var ignore_ids_for_localization = {
// elem_id: 'PARENTTYPE',
};

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 gradioApp() {
const elems = document.getElementsByTagName('gradio-app');
const elem = elems.length == 0 ? document : elems[0];

if (elem !== document) {
elem.getElementById = function(id) {
return document.getElementById(id);
};
}
return elem.shadowRoot ? elem.shadowRoot : elem;
}

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; // already dumped
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);
}

function apply_localization() {
if (!hasLocalization()) {
return;
}

// Apply new translations if there are mutations using MutationObserver
const observer = new MutationObserver((mutations, observer) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
processNode(node);
}
}
});

observer.observe(gradioApp(), {childList: true, subtree: true});

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});
}
}

if (document.readyState !== 'loading') {
apply_localization();
} else {
document.addEventListener('DOMContentLoaded', function() {
apply_localization();
});
}
6 changes: 3 additions & 3 deletions js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ document.querySelector(".header_bar").addEventListener("click", function(event)
if (event.target.tagName !== "BUTTON") return;

const buttonText = event.target.textContent.trim();
const extensionsVisible = ["Chat", "Default", "Notebook"].includes(buttonText);
const chatVisible = buttonText === "Chat";
const extensionsVisible = [localization["Chat"], localization["Default"], localization["Notebook"]].includes(buttonText);
const chatVisible = buttonText === localization["Chat"];
const showControlsChecked = document.querySelector("#show-controls input").checked;
const extensions = document.querySelector("#extensions");

Expand Down Expand Up @@ -592,7 +592,7 @@ function restoreOriginalPosition() {
headerBar.addEventListener("click", (e) => {
if (e.target.tagName === "BUTTON") {
const tabName = e.target.textContent.trim();
if (tabName === "Chat") {
if (tabName === localization["Chat"]) {
moveToChatTab();
} else {
restoreOriginalPosition();
Expand Down
Empty file.
Loading