feat: Support RegExp translation (#11)
parent
dba18cb956
commit
28c16a04b5
18
README.md
18
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 <sup>(Versions after 2023)</sup>
|
||||
|
||||
### Method 1
|
||||
#### Method 1
|
||||
|
||||
Use the `Install from URL` provided by webui to install
|
||||
|
||||
|
|
@ -30,7 +32,7 @@ After that, switch to the <kbd>Installed</kbd> panel and click the <kbd>Apply an
|
|||

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

|
||||
|
||||
## RegExp pattern
|
||||
|
||||
Localization support RegExp pattern, syntax rule is `@@<REGEXP>`, 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.
|
||||
|
|
|
|||
20
README_JA.md
20
README_JA.md
|
|
@ -9,14 +9,16 @@
|
|||
|
||||
## 特徴
|
||||
- バイリンガル対応により、元のボタンを探す必要がありません。
|
||||
- 言語パックの拡張機能に対応するので、再インストールが不要
|
||||
- 言語パックの拡張機能に対応するので、再インストールが不要。
|
||||
- 動的なタイトルヒントの翻訳をサポートします。
|
||||
- 正規表現パターンによる柔軟な翻訳が可能です。
|
||||
|
||||
## Installation
|
||||
|
||||
以下の方法から選択します。
|
||||
拡張機能に対応したWebUIが必要です。<sup>(Versions after 2023)</sup>
|
||||
|
||||
### 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 <kbd>Settings</kbd> - <kbd>Bilingual Localization</kbd>パネルで、有効
|
|||
|
||||

|
||||
|
||||
## RegExp pattern
|
||||
|
||||
言語対応の正規表現パターンで、構文ルールは`@@<REGEXP>`、キャプチャグループは`$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)のように設定してください。
|
||||
18
README_ZH.md
18
README_ZH.md
|
|
@ -10,12 +10,14 @@
|
|||
## 功能
|
||||
- 全新实现的双语对照翻译功能,不必再担心切换翻译后找不到原始功能
|
||||
- 兼容原生语言包扩展,无需重新导入多语言语料
|
||||
- 支持动态title提示的翻译
|
||||
- 额外支持正则表达式替换,翻译更加灵活
|
||||
|
||||
## 安装
|
||||
|
||||
以下方式选择其一,需要使用支持扩展功能的 webui <sup>(2023年之后的版本)</sup>
|
||||
|
||||
### 方式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
|
|||
在<kbd>Settings</kbd> - <kbd>Bilingual Localization</kbd>中选择要启用的本地化文件,依次点击<kbd>Apply settings</kbd>和<kbd>Reload UI</kbd>按钮
|
||||

|
||||
|
||||
## 正则表达式支持
|
||||
|
||||
本地化语料支持正则表达式替换,语法规则`@@<REGEXP>`,括号匹配变量`$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",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## 获取本地化文件
|
||||
|
||||
本地化文件不再随插件提供,请安装第三方语言包并按照本文[使用](#使用)部分的方式设置使用
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue