diff --git a/README.md b/README.md index 2caf946..98ecb52 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,14 @@ ## Features - Bilingual translation, no need to worry about how to find the original button. - Compatible with language pack extensions, no need to re-import. +- Support dynamic translation of title hints. +- Additional support RegExp pattern, more flexible translation. ## Installation Choose one of the following methods, Need to use webui with extension support (Versions after 2023) -### Method 1 +#### Method 1 Use the `Install from URL` provided by webui to install @@ -30,7 +32,7 @@ After that, switch to the Installed panel and click the Apply an ![Snipaste_2023-02-28_00-29-14](https://user-images.githubusercontent.com/16256221/221625345-9e656f25-89dd-4361-8ee5-f4ab39d18ca4.png) -### Method 2 +#### Method 2 Clone to your extension directory manually. @@ -47,6 +49,18 @@ In Settings - Bilingual Localization panel, select the loc ![Snipaste_2023-02-28_00-04-21](https://user-images.githubusercontent.com/16256221/221625729-73519629-8c1f-4eb5-99db-a1d3f4b58a87.png) +## RegExp pattern + +Localization support RegExp pattern, syntax rule is `@@`, capturing group is `$n`, doc: [String.prototype.replace()](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/replace) +```json +{ + ... + "@@/^(\\d+) images in this directory, divided into (\\d+) pages$/": "目录中有$1张图片,共$2页", + "@@/^Favorites path from settings: (.*)$/": "设置的收藏夹目录:$1", + ... +} +``` + ## How to get localization file Localization files are no longer provided with the plugin, please install a third-party language extensions and set-up as described in the [Usage](#usage) section of this article. diff --git a/README_JA.md b/README_JA.md index 6db6cc8..6f13f0e 100644 --- a/README_JA.md +++ b/README_JA.md @@ -9,14 +9,16 @@ ## 特徴 - バイリンガル対応により、元のボタンを探す必要がありません。 -- 言語パックの拡張機能に対応するので、再インストールが不要 +- 言語パックの拡張機能に対応するので、再インストールが不要。 +- 動的なタイトルヒントの翻訳をサポートします。 +- 正規表現パターンによる柔軟な翻訳が可能です。 ## Installation 以下の方法から選択します。 拡張機能に対応したWebUIが必要です。(Versions after 2023) -### Method 1 +#### Method 1 webuiの`Install from URL`でインストール @@ -31,7 +33,7 @@ webuiの`Install from URL`でインストール ![Snipaste_2023-02-28_00-29-14](https://user-images.githubusercontent.com/16256221/221625345-9e656f25-89dd-4361-8ee5-f4ab39d18ca4.png) -### Method 2 +#### Method 2 手動でExtensionディレクトリにcloneする @@ -48,6 +50,18 @@ In Settings - Bilingual Localizationパネルで、有効 ![Snipaste_2023-02-28_00-04-21](https://user-images.githubusercontent.com/16256221/221625729-73519629-8c1f-4eb5-99db-a1d3f4b58a87.png) +## RegExp pattern + +言語対応の正規表現パターンで、構文ルールは`@@`、キャプチャグループは`$n`です。ドキュメント:[String.prototype.replace()](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/replace)。 +```json +{ + ... + "@@/^(\\d+) images in this directory, divided into (\\d+) pages$/": "このディレクトリには$1枚の画像、$2ページ", + "@@/^Favorites path from settings: (.*)$/": "お気に入りのディレクトリパス:$1", + ... +} +``` + ## 言語ファイルの取得 内蔵の言語ファイルは提供されなくなりましたので、サードパーティの拡張機能をインストールし、[Usage](#usage)のように設定してください。 \ No newline at end of file diff --git a/README_ZH.md b/README_ZH.md index 974694d..d9cfc2b 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -10,12 +10,14 @@ ## 功能 - 全新实现的双语对照翻译功能,不必再担心切换翻译后找不到原始功能 - 兼容原生语言包扩展,无需重新导入多语言语料 +- 支持动态title提示的翻译 +- 额外支持正则表达式替换,翻译更加灵活 ## 安装 以下方式选择其一,需要使用支持扩展功能的 webui (2023年之后的版本) -### 方式1 +#### 方式1 使用 webui 提供的`Install from URL`功能安装 @@ -28,7 +30,7 @@ ![Snipaste_2023-02-28_00-29-14](https://user-images.githubusercontent.com/16256221/221625345-9e656f25-89dd-4361-8ee5-f4ab39d18ca4.png) -### 方式2 +#### 方式2 手动克隆到你的扩展目录里 @@ -44,6 +46,18 @@ git clone https://github.com/journey-ad/sd-webui-bilingual-localization extensio 在Settings - Bilingual Localization中选择要启用的本地化文件,依次点击Apply settingsReload UI按钮 ![Snipaste_2023-02-28_00-04-21](https://user-images.githubusercontent.com/16256221/221625729-73519629-8c1f-4eb5-99db-a1d3f4b58a87.png) +## 正则表达式支持 + +本地化语料支持正则表达式替换,语法规则`@@`,括号匹配变量`$n`,参考[String.prototype.replace()](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/replace) +```json +{ + ... + "@@/^(\\d+) images in this directory, divided into (\\d+) pages$/": "目录中有$1张图片,共$2页", + "@@/^Favorites path from settings: (.*)$/": "设置的收藏夹目录:$1", + ... +} +``` + ## 获取本地化文件 本地化文件不再随插件提供,请安装第三方语言包并按照本文[使用](#使用)部分的方式设置使用 diff --git a/javascript/bilingual_localization.js b/javascript/bilingual_localization.js index c903333..5d22c67 100644 --- a/javascript/bilingual_localization.js +++ b/javascript/bilingual_localization.js @@ -21,6 +21,8 @@ #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; } @@ -28,7 +30,10 @@ #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-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; } @@ -51,9 +56,15 @@ 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, config = null; + let i18n = null, i18nRegex = {}, config = null; // First load function setup() { @@ -75,7 +86,19 @@ logger.log('Bilingual Localization initialized.') // Load localization file - i18n = JSON.parse(readFile(dirs[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() } @@ -90,7 +113,7 @@ "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 - ".output-html:not(#footer)", // output html exclude footer + "#modelmerger_interp_description .output-html", // model merger description "#lightboxModal span" // image preview lightbox ]) .forEach(el => translateEl(el, { deep: true })) @@ -98,6 +121,8 @@ 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 })) @@ -128,8 +153,6 @@ if (el.tagName === 'OPTION') { doTranslate(el, el.textContent, 'option') - } else { - doTranslate(el, el.textContent, 'element') } if (deep || rich) { @@ -147,6 +170,8 @@ translateEl(node, { deep, rich }) } }) + } else { + doTranslate(el, el.textContent, 'element') } } @@ -158,9 +183,21 @@ source = source.trim() if (!source) return if (re_num.test(source)) return - if (re_emoji.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 @@ -222,6 +259,25 @@ .replace(/"/g, '"').replace(/'/g, ''') } + // get regex object from string + function getRegex(regex) { + try { + regex = regex.trim(); + let parts = regex.split('/'); + if (regex[0] !== '/' || parts.length < 3) { + regex = regex.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); //escap common string + return new RegExp(regex); + } + + const option = parts[parts.length - 1]; + const lastIndex = regex.lastIndexOf('/'); + regex = regex.substring(1, lastIndex); + return new RegExp(regex, option); + } catch (e) { + return null + } + } + // Load file function readFile(filePath) { let request = new XMLHttpRequest();