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

-### Method 2
+#### Method 2
Clone to your extension directory manually.
@@ -47,6 +49,18 @@ In Settings - Bilingual Localization panel, select the loc

+## 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`でインストール

-### Method 2
+#### Method 2
手動でExtensionディレクトリにcloneする
@@ -48,6 +50,18 @@ In Settings - Bilingual Localizationパネルで、有効

+## 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 @@

-### 方式2
+#### 方式2
手动克隆到你的扩展目录里
@@ -44,6 +46,18 @@ git clone https://github.com/journey-ad/sd-webui-bilingual-localization extensio
在Settings - Bilingual Localization中选择要启用的本地化文件,依次点击Apply settings和Reload UI按钮

+## 正则表达式支持
+
+本地化语料支持正则表达式替换,语法规则`@@`,括号匹配变量`$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();