Upload 7 files
Browse files- .gitignore +1 -0
- LICENSE +21 -0
- README.md +80 -3
- README_JA.md +79 -0
- README_ZH.md +79 -0
- javascript/bilingual_localization.js +523 -0
- scripts/bilingual_localization_helper.py +33 -0
.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2023 Jad
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -1,3 +1,80 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[中文文档](README_ZH.md) / [日本語](README_JA.md)
|
| 2 |
+
|
| 3 |
+
<p align="center"><img src="https://count.getloli.com/get/@sd-webui-bilingual-localization.github" alt="sd-webui-bilingual-localization"></p>
|
| 4 |
+
|
| 5 |
+
# sd-webui-bilingual-localization
|
| 6 |
+
[Stable Diffusion web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) bilingual localization extensions.
|
| 7 |
+
|
| 8 |
+

|
| 9 |
+
|
| 10 |
+
## Features
|
| 11 |
+
- Bilingual translation, no need to worry about how to find the original button.
|
| 12 |
+
- Compatible with language pack extensions, no need to re-import.
|
| 13 |
+
- Support dynamic translation of title hints.
|
| 14 |
+
- Additional support Scoped and RegExp pattern, more flexible translation.
|
| 15 |
+
|
| 16 |
+
## Installation
|
| 17 |
+
|
| 18 |
+
Choose one of the following methods, Need to use webui with extension support <sup>(Versions after 2023)</sup>
|
| 19 |
+
|
| 20 |
+
#### Method 1
|
| 21 |
+
|
| 22 |
+
Use the `Install from URL` provided by webui to install
|
| 23 |
+
|
| 24 |
+
Click in order <kbd>Extensions</kbd> - <kbd>Install from URL</kbd>
|
| 25 |
+
|
| 26 |
+
Then fill in the first text box with `https://github.com/journey-ad/sd-webui-bilingual-localization`, click the <kbd>Install</kbd> button.
|
| 27 |
+
|
| 28 |
+

|
| 29 |
+
|
| 30 |
+
After that, switch to the <kbd>Installed</kbd> panel and click the <kbd>Apply and restart UI</kbd> button.
|
| 31 |
+
|
| 32 |
+

|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
#### Method 2
|
| 36 |
+
|
| 37 |
+
Clone to your extension directory manually.
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
git clone https://github.com/journey-ad/sd-webui-bilingual-localization extensions/sd-webui-bilingual-localization
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
## Usage
|
| 44 |
+
|
| 45 |
+
> **⚠️Important⚠️**
|
| 46 |
+
> Make sure <kbd>Settings</kbd> - <kbd>User interface</kbd> - <kbd>Localization</kbd> is set to `None`
|
| 47 |
+
|
| 48 |
+
In <kbd>Settings</kbd> - <kbd>Bilingual Localization</kbd> panel, select the localization file you want to enable and click on the <kbd>Apply settings</kbd> and <kbd>Reload UI</kbd> buttons in turn.
|
| 49 |
+
|
| 50 |
+

|
| 51 |
+
|
| 52 |
+
## Scoped
|
| 53 |
+
|
| 54 |
+
Localization supports scoped to prevent global impact. The syntax rule is `##<SCOPE ID>##<TEXT>`.
|
| 55 |
+
Scoped text is effective only when the ID of the ancestor element of the node matches the specified scope.
|
| 56 |
+
|
| 57 |
+
```json
|
| 58 |
+
{
|
| 59 |
+
...
|
| 60 |
+
"##tab_ti##Normal": "正态", // only `Normal` under the element with id="tab_ti" will be translated to `正态`.
|
| 61 |
+
"##tab_threedopenpose##Normal": "法线图", // only `Normal` under the element with id="tab_threedopenpose" will be translated to `法线图`.
|
| 62 |
+
...
|
| 63 |
+
}
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
## RegExp pattern
|
| 67 |
+
|
| 68 |
+
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)
|
| 69 |
+
```json
|
| 70 |
+
{
|
| 71 |
+
...
|
| 72 |
+
"@@/^(\\d+) images in this directory, divided into (\\d+) pages$/": "目录中有$1张图片,共$2页",
|
| 73 |
+
"@@/^Favorites path from settings: (.*)$/": "设置的收藏夹目录:$1",
|
| 74 |
+
...
|
| 75 |
+
}
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
## How to get localization file
|
| 79 |
+
|
| 80 |
+
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.
|
README_JA.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[English Version](README.md)
|
| 2 |
+
|
| 3 |
+
<p align="center"><img src="https://count.getloli.com/get/@sd-webui-bilingual-localization.github" alt="sd-webui-bilingual-localization"></p>
|
| 4 |
+
|
| 5 |
+
# sd-webui-bilingual-localization
|
| 6 |
+
[Stable Diffusion web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui)のバイリンガル対応拡張機能
|
| 7 |
+
|
| 8 |
+

|
| 9 |
+
|
| 10 |
+
## 特徴
|
| 11 |
+
- バイリンガル対応により、元のボタンを探す必要がありません。
|
| 12 |
+
- 日本語化拡張機能と互換性があり、ファイルを取り込み直す必要はありません。
|
| 13 |
+
- ツールチップの動的翻訳をサポートします。
|
| 14 |
+
- スコープと正規表現パターンによる柔軟な翻訳が可能です。
|
| 15 |
+
|
| 16 |
+
## インストール
|
| 17 |
+
|
| 18 |
+
以下の方法から選択します。
|
| 19 |
+
拡張機能に対応したWebUI<sup>(2023年以降のバージョン)</sup>が必要です。
|
| 20 |
+
|
| 21 |
+
#### 方法1
|
| 22 |
+
|
| 23 |
+
WebUIの`Install from URL`でインストールを行います。
|
| 24 |
+
|
| 25 |
+
<kbd>Extensions</kbd> - <kbd>Install from URL</kbd>を順にクリックします。
|
| 26 |
+
|
| 27 |
+
1個目のテキストボックスに`https://github.com/journey-ad/sd-webui-bilingual-localization`を入力し、<kbd>Install</kbd>ボタンをクリックします。
|
| 28 |
+
|
| 29 |
+

|
| 30 |
+
|
| 31 |
+
その後、<kbd>Installed</kbd>パネルに切り替え、<kbd>Apply and restart UI</kbd>ボタンをクリックします。
|
| 32 |
+
|
| 33 |
+

|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
#### 方法2
|
| 37 |
+
|
| 38 |
+
拡張機能のディレクトリに手動でcloneします。
|
| 39 |
+
|
| 40 |
+
```bash
|
| 41 |
+
git clone https://github.com/journey-ad/sd-webui-bilingual-localization extensions/sd-webui-bilingual-localization
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
## 使用方法
|
| 45 |
+
|
| 46 |
+
> **⚠️重要⚠️**
|
| 47 |
+
> <kbd>Settings</kbd> - <kbd>User interface</kbd> - <kbd>Localization</kbd>が`None`に設定されていることを確認してください。
|
| 48 |
+
|
| 49 |
+
<kbd>Settings</kbd> - <kbd>Bilingual Localization</kbd>パネルで、有効にしたい言語ファイル名を選択し、<kbd>Apply settings</kbd>ボタンと<kbd>Reload UI</kbd>ボタンを順にクリックします。
|
| 50 |
+
|
| 51 |
+

|
| 52 |
+
|
| 53 |
+
## スコープ
|
| 54 |
+
|
| 55 |
+
ローカライゼーションは、スコープ化されており、グローバルな影響を防止することができます。構文ルールは`##<SCOPE ID>##<TEXT>`です。
|
| 56 |
+
スコープを指定するIDが祖先要素のIDと一致する場合にのみ、スコープ化されたテキストが有効になります。
|
| 57 |
+
|
| 58 |
+
```json
|
| 59 |
+
...
|
| 60 |
+
"##tab_ti##Normal": "正常", // id="tab_ti"要素の下の`Normal`のみが`正常`として変換されます
|
| 61 |
+
"##tab_threedopenpose##Normal": "法線マップ", // id="tab_threedopenpose"要素の下の`Normal`のみが `法線マップ`として変換されます
|
| 62 |
+
...
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
## 正規表現パターン
|
| 66 |
+
|
| 67 |
+
正規表現を使った日本語化が可能です。構文ルールは`@@<REGEXP>`、キャプチャグループは`$n`です。ドキュメント:[String.prototype.replace()](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/replace)。
|
| 68 |
+
```json
|
| 69 |
+
{
|
| 70 |
+
...
|
| 71 |
+
"@@/^(\\d+) images in this directory, divided into (\\d+) pages$/": "このディレクトリには$1枚の画像、$2ページ",
|
| 72 |
+
"@@/^Favorites path from settings: (.*)$/": "お気に入りのディレクトリパス:$1",
|
| 73 |
+
...
|
| 74 |
+
}
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
## 日本語化ファイルの取得
|
| 78 |
+
|
| 79 |
+
内蔵の日本語化ファイルは提供されなくなりました。サードパーティーの日本語化拡張機能をインストールし、当ページの[使用方法](#使用方法)に記載されている方法でセットアップしてください。
|
README_ZH.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[English Version](README.md)
|
| 2 |
+
|
| 3 |
+
<p align="center"><img src="https://count.getloli.com/get/@sd-webui-bilingual-localization.github" alt="sd-webui-bilingual-localization"></p>
|
| 4 |
+
|
| 5 |
+
# sd-webui-bilingual-localization
|
| 6 |
+
[Stable Diffusion web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) 双语对照翻译插件
|
| 7 |
+
|
| 8 |
+

|
| 9 |
+
|
| 10 |
+
## 功能
|
| 11 |
+
- 全新实现的双语对照翻译功能,不必再担心切换翻译后找不到原始功能
|
| 12 |
+
- 兼容原生语言包扩展,无需重新导入多语言语料
|
| 13 |
+
- 支持动态title提示的翻译
|
| 14 |
+
- 额外支持作用域和正则表达式替换,翻译更加灵活
|
| 15 |
+
|
| 16 |
+
## 安装
|
| 17 |
+
|
| 18 |
+
以下方式选择其一,需要使用支持扩展功能的 webui <sup>(2023年之后的版本)</sup>
|
| 19 |
+
|
| 20 |
+
#### 方式1
|
| 21 |
+
|
| 22 |
+
使用 webui 提供的`Install from URL`功能安装
|
| 23 |
+
|
| 24 |
+
按下图所示,依次点击<kbd>Extensions</kbd> - <kbd>Install from URL</kbd>
|
| 25 |
+
|
| 26 |
+
然后在第一个文本框内填入`https://github.com/journey-ad/sd-webui-bilingual-localization`,点击<kbd>Install</kbd>按钮
|
| 27 |
+

|
| 28 |
+
|
| 29 |
+
之后切换到<kbd>Installed</kbd>面板,点击<kbd>Apply and restart UI</kbd>按钮
|
| 30 |
+

|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
#### 方式2
|
| 34 |
+
|
| 35 |
+
手动克隆到你的扩展目录里
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
git clone https://github.com/journey-ad/sd-webui-bilingual-localization extensions/sd-webui-bilingual-localization
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
## 使用
|
| 42 |
+
|
| 43 |
+
> **⚠️重要⚠️**
|
| 44 |
+
> 确保<kbd>Settings</kbd> - <kbd>User interface</kbd> - <kbd>Localization</kbd> 已设置为了 `None`
|
| 45 |
+
|
| 46 |
+
在<kbd>Settings</kbd> - <kbd>Bilingual Localization</kbd>中选择要启用的本地化文件,依次点击<kbd>Apply settings</kbd>和<kbd>Reload UI</kbd>按钮
|
| 47 |
+

|
| 48 |
+
|
| 49 |
+
## 作用域支持
|
| 50 |
+
|
| 51 |
+
本地化语料支持限定作用域,防止影响全局翻译,语法规则`##<SCOPE ID>##<TEXT>`
|
| 52 |
+
具有作用域的语料仅当节点祖先元素的ID匹配指定的作用域时才会生效
|
| 53 |
+
|
| 54 |
+
```json
|
| 55 |
+
{
|
| 56 |
+
...
|
| 57 |
+
"##tab_ti##Normal": "正态", // 仅id="tab_ti"元素下的`Normal`会被翻译为`正态`
|
| 58 |
+
"##tab_threedopenpose##Normal": "法线图", // 仅id="tab_threedopenpose"元素下的`Normal`会被翻译为`法线图`
|
| 59 |
+
...
|
| 60 |
+
}
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
## 正则表达式支持
|
| 64 |
+
|
| 65 |
+
本地化语料支持正则表达式替换,语法规则`@@<REGEXP>`,括号匹配变量`$n`,参考[String.prototype.replace()](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/replace)
|
| 66 |
+
```json
|
| 67 |
+
{
|
| 68 |
+
...
|
| 69 |
+
"@@/^(\\d+) images in this directory, divided into (\\d+) pages$/": "目录中有$1张图片,共$2页",
|
| 70 |
+
"@@/^Favorites path from settings: (.*)$/": "设置的收藏夹目录:$1",
|
| 71 |
+
...
|
| 72 |
+
}
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
## 获取本地化文件
|
| 76 |
+
|
| 77 |
+
本地化文件不再随插件提供,请安装第三方语言包并按照本文[使用](#使用)部分的方式设置使用
|
| 78 |
+
|
| 79 |
+
*预览图片中的语言包可以在这里找到 https://gist.github.com/journey-ad/d98ed173321658be6e51f752d6e6163c*
|
javascript/bilingual_localization.js
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(function () {
|
| 2 |
+
const customCSS = `
|
| 3 |
+
.bilingual__trans_wrapper {
|
| 4 |
+
display: inline-flex;
|
| 5 |
+
flex-direction: column;
|
| 6 |
+
align-items: center;
|
| 7 |
+
font-size: 13px;
|
| 8 |
+
line-height: 1;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
.bilingual__trans_wrapper em {
|
| 12 |
+
font-style: normal;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
#txtimg_hr_finalres .bilingual__trans_wrapper em,
|
| 16 |
+
#tab_ti .output-html .bilingual__trans_wrapper em,
|
| 17 |
+
#tab_ti .gradio-html .bilingual__trans_wrapper em,
|
| 18 |
+
#sddp-dynamic-prompting .gradio-html .bilingual__trans_wrapper em,
|
| 19 |
+
#available_extensions .extension-tag .bilingual__trans_wrapper em,
|
| 20 |
+
#available_extensions .date_added .bilingual__trans_wrapper em,
|
| 21 |
+
#available_extensions+p>.bilingual__trans_wrapper em,
|
| 22 |
+
.gradio-image div[data-testid="image"] .bilingual__trans_wrapper em {
|
| 23 |
+
display: none;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
#settings .bilingual__trans_wrapper:not(#settings .tabitem .bilingual__trans_wrapper),
|
| 27 |
+
label>span>.bilingual__trans_wrapper,
|
| 28 |
+
fieldset>span>.bilingual__trans_wrapper,
|
| 29 |
+
.label-wrap>span>.bilingual__trans_wrapper,
|
| 30 |
+
.w-full>span>.bilingual__trans_wrapper,
|
| 31 |
+
.context-menu-items .bilingual__trans_wrapper,
|
| 32 |
+
.single-select .bilingual__trans_wrapper, ul.options .inner-item + .bilingual__trans_wrapper,
|
| 33 |
+
.output-html .bilingual__trans_wrapper:not(th .bilingual__trans_wrapper),
|
| 34 |
+
.gradio-html .bilingual__trans_wrapper:not(th .bilingual__trans_wrapper, .posex_cont .bilingual__trans_wrapper),
|
| 35 |
+
.output-markdown .bilingual__trans_wrapper,
|
| 36 |
+
.gradio-markdown .bilingual__trans_wrapper,
|
| 37 |
+
.gradio-image>div.float .bilingual__trans_wrapper,
|
| 38 |
+
.gradio-file>div.float .bilingual__trans_wrapper,
|
| 39 |
+
.gradio-code>div.float .bilingual__trans_wrapper,
|
| 40 |
+
.posex_setting_cont .bilingual__trans_wrapper:not(.posex_bg .bilingual__trans_wrapper), /* Posex extension */
|
| 41 |
+
#dynamic-prompting .bilingual__trans_wrapper
|
| 42 |
+
{
|
| 43 |
+
font-size: 12px;
|
| 44 |
+
align-items: flex-start;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
#extensions label .bilingual__trans_wrapper,
|
| 48 |
+
#available_extensions td .bilingual__trans_wrapper,
|
| 49 |
+
.label-wrap>span>.bilingual__trans_wrapper {
|
| 50 |
+
font-size: inherit;
|
| 51 |
+
line-height: inherit;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.label-wrap>span:first-of-type {
|
| 55 |
+
font-size: 13px;
|
| 56 |
+
line-height: 1;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
#txt2img_script_container > div {
|
| 60 |
+
margin-top: var(--layout-gap, 12px);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
textarea::placeholder {
|
| 64 |
+
line-height: 1;
|
| 65 |
+
padding: 4px 0;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
label>span {
|
| 69 |
+
line-height: 1;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
div[data-testid="image"] .start-prompt {
|
| 73 |
+
background-color: rgba(255, 255, 255, .6);
|
| 74 |
+
color: #222;
|
| 75 |
+
transition: opacity .2s ease-in-out;
|
| 76 |
+
}
|
| 77 |
+
div[data-testid="image"]:hover .start-prompt {
|
| 78 |
+
opacity: 0;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.label-wrap > span.icon {
|
| 82 |
+
width: 1em;
|
| 83 |
+
height: 1em;
|
| 84 |
+
transform-origin: center center;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.gradio-dropdown ul.options li.item {
|
| 88 |
+
padding: 0.3em 0.4em !important;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
/* Posex extension */
|
| 92 |
+
.posex_bg {
|
| 93 |
+
white-space: nowrap;
|
| 94 |
+
}
|
| 95 |
+
`
|
| 96 |
+
|
| 97 |
+
let i18n = null, i18nRegex = {}, i18nScope = {}, scopedSource = {}, config = null;
|
| 98 |
+
|
| 99 |
+
// First load
|
| 100 |
+
function setup() {
|
| 101 |
+
config = {
|
| 102 |
+
enabled: opts["bilingual_localization_enabled"],
|
| 103 |
+
file: opts["bilingual_localization_file"],
|
| 104 |
+
dirs: opts["bilingual_localization_dirs"],
|
| 105 |
+
order: opts["bilingual_localization_order"],
|
| 106 |
+
enableLogger: opts["bilingual_localization_logger"]
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
let { enabled, file, dirs, enableLogger } = config
|
| 110 |
+
|
| 111 |
+
if (!enabled || file === "None" || dirs === "None") return
|
| 112 |
+
|
| 113 |
+
dirs = JSON.parse(dirs)
|
| 114 |
+
|
| 115 |
+
enableLogger && logger.init('Bilingual')
|
| 116 |
+
logger.log('Bilingual Localization initialized.')
|
| 117 |
+
|
| 118 |
+
// Load localization file
|
| 119 |
+
const regex_scope = /^##(?<scope>\S+)##(?<skey>\S+)$/ // ##scope##.skey
|
| 120 |
+
i18n = JSON.parse(readFile(dirs[file]), (key, value) => {
|
| 121 |
+
// parse regex translations
|
| 122 |
+
if (key.startsWith('@@')) {
|
| 123 |
+
i18nRegex[key.slice(2)] = value
|
| 124 |
+
} else if (regex_scope.test(key)) {
|
| 125 |
+
// parse scope translations
|
| 126 |
+
const { scope, skey } = key.match(regex_scope).groups
|
| 127 |
+
i18nScope[scope] ||= {}
|
| 128 |
+
i18nScope[scope][skey] = value
|
| 129 |
+
|
| 130 |
+
scopedSource[skey] ||= []
|
| 131 |
+
scopedSource[skey].push(scope)
|
| 132 |
+
} else {
|
| 133 |
+
return value
|
| 134 |
+
}
|
| 135 |
+
})
|
| 136 |
+
|
| 137 |
+
logger.group('Localization file loaded.')
|
| 138 |
+
logger.log('i18n', i18n)
|
| 139 |
+
logger.log('i18nRegex', i18nRegex)
|
| 140 |
+
logger.log('i18nScope', i18nScope)
|
| 141 |
+
logger.groupEnd()
|
| 142 |
+
|
| 143 |
+
translatePage()
|
| 144 |
+
handleDropdown()
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
function handleDropdown() {
|
| 148 |
+
// process gradio dropdown menu
|
| 149 |
+
delegateEvent(gradioApp(), 'mousedown', 'ul.options .item', function (event) {
|
| 150 |
+
const { target } = event
|
| 151 |
+
|
| 152 |
+
if (!target.classList.contains('item')) {
|
| 153 |
+
// simulate click menu item
|
| 154 |
+
target.closest('.item').dispatchEvent(new Event('mousedown', { bubbles: true }))
|
| 155 |
+
return
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
const source = target.dataset.value
|
| 159 |
+
const $labelEl = target?.closest('.wrap')?.querySelector('.wrap-inner .single-select') // the label element
|
| 160 |
+
|
| 161 |
+
if (source && $labelEl) {
|
| 162 |
+
$labelEl.title = titles?.[source] || '' // set title from hints.js
|
| 163 |
+
$labelEl.textContent = "__biligual__will_be_replaced__" // marked as will be replaced
|
| 164 |
+
doTranslate($labelEl, source, 'element') // translate the label element
|
| 165 |
+
}
|
| 166 |
+
});
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
// Translate page
|
| 170 |
+
function translatePage() {
|
| 171 |
+
if (!i18n) return
|
| 172 |
+
|
| 173 |
+
logger.time('Full Page')
|
| 174 |
+
querySelectorAll([
|
| 175 |
+
"label span, fieldset span, button", // major label and button description text
|
| 176 |
+
"textarea[placeholder], select, option", // text box placeholder and select element
|
| 177 |
+
".transition > div > span:not([class])", ".label-wrap > span", // collapse panel added by extension
|
| 178 |
+
".gradio-image>div.float", // image upload description
|
| 179 |
+
".gradio-file>div.float", // file upload description
|
| 180 |
+
".gradio-code>div.float", // code editor description
|
| 181 |
+
"#modelmerger_interp_description .output-html", // model merger description
|
| 182 |
+
"#modelmerger_interp_description .gradio-html", // model merger description
|
| 183 |
+
"#lightboxModal span" // image preview lightbox
|
| 184 |
+
])
|
| 185 |
+
.forEach(el => translateEl(el, { deep: true }))
|
| 186 |
+
|
| 187 |
+
querySelectorAll([
|
| 188 |
+
'div[data-testid="image"] > div > div', // description of image upload panel
|
| 189 |
+
'#extras_image_batch > div', // description of extras image batch file upload panel
|
| 190 |
+
".output-html:not(#footer), .gradio-html:not(#footer), .output-markdown, .gradio-markdown", // output html exclude footer
|
| 191 |
+
'#dynamic-prompting' // dynamic-prompting extension
|
| 192 |
+
])
|
| 193 |
+
.forEach(el => translateEl(el, { rich: true }))
|
| 194 |
+
|
| 195 |
+
logger.timeEnd('Full Page')
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
const ignore_selector = [
|
| 199 |
+
'.bilingual__trans_wrapper', // self
|
| 200 |
+
'.resultsFlexContainer', // tag-autocomplete
|
| 201 |
+
'#setting_sd_model_checkpoint select', // model checkpoint
|
| 202 |
+
'#setting_sd_vae select', // vae model
|
| 203 |
+
'#txt2img_styles, #img2txt_styles', // styles select
|
| 204 |
+
'.extra-network-cards .card .actions .name', // extra network cards name
|
| 205 |
+
'script, style, svg, g, path', // script / style / svg elements
|
| 206 |
+
]
|
| 207 |
+
// Translate element
|
| 208 |
+
function translateEl(el, { deep = false, rich = false } = {}) {
|
| 209 |
+
if (!i18n) return // translation not ready.
|
| 210 |
+
if (el.matches?.(ignore_selector)) return // ignore some elements.
|
| 211 |
+
|
| 212 |
+
if (el.title) {
|
| 213 |
+
doTranslate(el, el.title, 'title')
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
if (el.placeholder) {
|
| 217 |
+
doTranslate(el, el.placeholder, 'placeholder')
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
if (el.tagName === 'OPTION') {
|
| 221 |
+
doTranslate(el, el.textContent, 'option')
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
if (deep || rich) {
|
| 225 |
+
Array.from(el.childNodes).forEach(node => {
|
| 226 |
+
if (node.nodeName === '#text') {
|
| 227 |
+
if (rich) {
|
| 228 |
+
doTranslate(node, node.textContent, 'text')
|
| 229 |
+
return
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
if (deep) {
|
| 233 |
+
doTranslate(node, node.textContent, 'element')
|
| 234 |
+
}
|
| 235 |
+
} else if (node.childNodes.length > 0) {
|
| 236 |
+
translateEl(node, { deep, rich })
|
| 237 |
+
}
|
| 238 |
+
})
|
| 239 |
+
} else {
|
| 240 |
+
doTranslate(el, el.textContent, 'element')
|
| 241 |
+
}
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
function checkRegex(source) {
|
| 245 |
+
for (let regex in i18nRegex) {
|
| 246 |
+
regex = getRegex(regex)
|
| 247 |
+
if (regex && regex.test(source)) {
|
| 248 |
+
logger.log('regex', regex, source)
|
| 249 |
+
return source.replace(regex, i18nRegex[regex])
|
| 250 |
+
}
|
| 251 |
+
}
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
const re_num = /^[\.\d]+$/,
|
| 255 |
+
re_emoji = /[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\u{1F9B0}-\u{1F9B3}]/u
|
| 256 |
+
|
| 257 |
+
function doTranslate(el, source, type) {
|
| 258 |
+
if (!i18n) return // translation not ready.
|
| 259 |
+
source = source.trim()
|
| 260 |
+
if (!source) return
|
| 261 |
+
if (re_num.test(source)) return
|
| 262 |
+
// if (re_emoji.test(source)) return
|
| 263 |
+
|
| 264 |
+
let translation = i18n[source] || checkRegex(source),
|
| 265 |
+
scopes = scopedSource[source]
|
| 266 |
+
|
| 267 |
+
if (scopes) {
|
| 268 |
+
console.log('scope', el, source);
|
| 269 |
+
for (let scope of scopes) {
|
| 270 |
+
if (el.parentElement.closest(`#${scope}`)) {
|
| 271 |
+
translation = i18nScope[scope][source]
|
| 272 |
+
break
|
| 273 |
+
}
|
| 274 |
+
}
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
if (!translation || source === translation) {
|
| 278 |
+
if (el.textContent === '__biligual__will_be_replaced__') el.textContent = source // restore original text if translation not exist
|
| 279 |
+
if (el.nextSibling?.className === 'bilingual__trans_wrapper') el.nextSibling.remove() // remove exist translation if translation not exist
|
| 280 |
+
return
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
if (config.order === "Original First") {
|
| 284 |
+
[source, translation] = [translation, source]
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
switch (type) {
|
| 288 |
+
case 'text':
|
| 289 |
+
el.textContent = translation
|
| 290 |
+
break;
|
| 291 |
+
|
| 292 |
+
case 'element':
|
| 293 |
+
const htmlStr = `<div class="bilingual__trans_wrapper">${htmlEncode(translation)}<em>${htmlEncode(source)}</em></div>`
|
| 294 |
+
const htmlEl = parseHtmlStringToElement(htmlStr)
|
| 295 |
+
if (el.hasChildNodes()) {
|
| 296 |
+
const textNode = Array.from(el.childNodes).find(node =>
|
| 297 |
+
node.nodeName === '#text' &&
|
| 298 |
+
(node.textContent.trim() === source || node.textContent.trim() === '__biligual__will_be_replaced__')
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
if (textNode) {
|
| 302 |
+
textNode.textContent = ''
|
| 303 |
+
if (textNode.nextSibling?.className === 'bilingual__trans_wrapper') textNode.nextSibling.remove()
|
| 304 |
+
textNode.parentNode.insertBefore(htmlEl, textNode.nextSibling)
|
| 305 |
+
}
|
| 306 |
+
} else {
|
| 307 |
+
el.textContent = ''
|
| 308 |
+
if (el.nextSibling?.className === 'bilingual__trans_wrapper') el.nextSibling.remove()
|
| 309 |
+
el.parentNode.insertBefore(htmlEl, el.nextSibling)
|
| 310 |
+
}
|
| 311 |
+
break;
|
| 312 |
+
|
| 313 |
+
case 'option':
|
| 314 |
+
el.textContent = `${translation} (${source})`
|
| 315 |
+
break;
|
| 316 |
+
|
| 317 |
+
case 'title':
|
| 318 |
+
el.title = `${translation}\n${source}`
|
| 319 |
+
break;
|
| 320 |
+
|
| 321 |
+
case 'placeholder':
|
| 322 |
+
el.placeholder = `${translation}\n\n${source}`
|
| 323 |
+
break;
|
| 324 |
+
|
| 325 |
+
default:
|
| 326 |
+
return translation
|
| 327 |
+
}
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
function gradioApp() {
|
| 331 |
+
const elems = document.getElementsByTagName('gradio-app')
|
| 332 |
+
const elem = elems.length == 0 ? document : elems[0]
|
| 333 |
+
|
| 334 |
+
if (elem !== document) elem.getElementById = function (id) { return document.getElementById(id) }
|
| 335 |
+
return elem.shadowRoot ? elem.shadowRoot : elem
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
function querySelector(...args) {
|
| 339 |
+
return gradioApp()?.querySelector(...args)
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
function querySelectorAll(...args) {
|
| 343 |
+
return gradioApp()?.querySelectorAll(...args)
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
function delegateEvent(parent, eventType, selector, handler) {
|
| 347 |
+
parent.addEventListener(eventType, function (event) {
|
| 348 |
+
var target = event.target;
|
| 349 |
+
while (target !== parent) {
|
| 350 |
+
if (target.matches(selector)) {
|
| 351 |
+
handler.call(target, event);
|
| 352 |
+
}
|
| 353 |
+
target = target.parentNode;
|
| 354 |
+
}
|
| 355 |
+
});
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
function parseHtmlStringToElement(htmlStr) {
|
| 359 |
+
const template = document.createElement('template')
|
| 360 |
+
template.insertAdjacentHTML('afterbegin', htmlStr)
|
| 361 |
+
return template.firstElementChild
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
function htmlEncode(htmlStr) {
|
| 365 |
+
return htmlStr.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
| 366 |
+
.replace(/"/g, '"').replace(/'/g, ''')
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
// get regex object from string
|
| 370 |
+
function getRegex(regex) {
|
| 371 |
+
try {
|
| 372 |
+
regex = regex.trim();
|
| 373 |
+
let parts = regex.split('/');
|
| 374 |
+
if (regex[0] !== '/' || parts.length < 3) {
|
| 375 |
+
regex = regex.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); //escap common string
|
| 376 |
+
return new RegExp(regex);
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
const option = parts[parts.length - 1];
|
| 380 |
+
const lastIndex = regex.lastIndexOf('/');
|
| 381 |
+
regex = regex.substring(1, lastIndex);
|
| 382 |
+
return new RegExp(regex, option);
|
| 383 |
+
} catch (e) {
|
| 384 |
+
return null
|
| 385 |
+
}
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
// Load file
|
| 389 |
+
function readFile(filePath) {
|
| 390 |
+
let request = new XMLHttpRequest();
|
| 391 |
+
request.open("GET", `file=${filePath}`, false);
|
| 392 |
+
request.send(null);
|
| 393 |
+
return request.responseText;
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
const logger = (function () {
|
| 397 |
+
const loggerTimerMap = new Map()
|
| 398 |
+
const loggerConf = { badge: true, label: 'Logger', enable: false }
|
| 399 |
+
return new Proxy(console, {
|
| 400 |
+
get: (target, propKey) => {
|
| 401 |
+
if (propKey === 'init') {
|
| 402 |
+
return (label) => {
|
| 403 |
+
loggerConf.label = label
|
| 404 |
+
loggerConf.enable = true
|
| 405 |
+
}
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
if (!(propKey in target)) return undefined
|
| 409 |
+
|
| 410 |
+
return (...args) => {
|
| 411 |
+
if (!loggerConf.enable) return
|
| 412 |
+
|
| 413 |
+
let color = ['#39cfe1', '#006cab']
|
| 414 |
+
|
| 415 |
+
let label, start
|
| 416 |
+
switch (propKey) {
|
| 417 |
+
case 'error':
|
| 418 |
+
color = ['#f70000', '#a70000']
|
| 419 |
+
break;
|
| 420 |
+
case 'warn':
|
| 421 |
+
color = ['#f7b500', '#b58400']
|
| 422 |
+
break;
|
| 423 |
+
case 'time':
|
| 424 |
+
label = args[0]
|
| 425 |
+
if (loggerTimerMap.has(label)) {
|
| 426 |
+
logger.warn(`Timer '${label}' already exisits`)
|
| 427 |
+
} else {
|
| 428 |
+
loggerTimerMap.set(label, performance.now())
|
| 429 |
+
}
|
| 430 |
+
return
|
| 431 |
+
case 'timeEnd':
|
| 432 |
+
label = args[0], start = loggerTimerMap.get(label)
|
| 433 |
+
if (start === undefined) {
|
| 434 |
+
logger.warn(`Timer '${label}' does not exist`)
|
| 435 |
+
} else {
|
| 436 |
+
loggerTimerMap.delete(label)
|
| 437 |
+
logger.log(`${label}: ${performance.now() - start} ms`)
|
| 438 |
+
}
|
| 439 |
+
return
|
| 440 |
+
case 'groupEnd':
|
| 441 |
+
loggerConf.badge = true
|
| 442 |
+
break
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
const badge = loggerConf.badge ? [`%c${loggerConf.label}`, `color: #fff; background: linear-gradient(180deg, ${color[0]}, ${color[1]}); text-shadow: 0px 0px 1px #0003; padding: 3px 5px; border-radius: 4px;`] : []
|
| 446 |
+
|
| 447 |
+
target[propKey](...badge, ...args)
|
| 448 |
+
|
| 449 |
+
if (propKey === 'group' || propKey === 'groupCollapsed') {
|
| 450 |
+
loggerConf.badge = false
|
| 451 |
+
}
|
| 452 |
+
}
|
| 453 |
+
}
|
| 454 |
+
})
|
| 455 |
+
}())
|
| 456 |
+
|
| 457 |
+
function init() {
|
| 458 |
+
// Add style to dom
|
| 459 |
+
let $styleEL = document.createElement('style');
|
| 460 |
+
|
| 461 |
+
if ($styleEL.styleSheet) {
|
| 462 |
+
$styleEL.styleSheet.cssText = customCSS;
|
| 463 |
+
} else {
|
| 464 |
+
$styleEL.appendChild(document.createTextNode(customCSS));
|
| 465 |
+
}
|
| 466 |
+
gradioApp().appendChild($styleEL);
|
| 467 |
+
|
| 468 |
+
let loaded = false
|
| 469 |
+
let _count = 0
|
| 470 |
+
|
| 471 |
+
const observer = new MutationObserver(mutations => {
|
| 472 |
+
if (Object.keys(localization).length) return; // disabled if original translation enabled
|
| 473 |
+
if (Object.keys(opts).length === 0) return; // does nothing if opts is not loaded
|
| 474 |
+
|
| 475 |
+
let _nodesCount = 0, _now = performance.now()
|
| 476 |
+
|
| 477 |
+
for (const mutation of mutations) {
|
| 478 |
+
if (mutation.type === 'characterData') {
|
| 479 |
+
if (mutation.target?.parentElement?.parentElement?.tagName === 'LABEL') {
|
| 480 |
+
translateEl(mutation.target)
|
| 481 |
+
}
|
| 482 |
+
} else if (mutation.type === 'attributes') {
|
| 483 |
+
_nodesCount++
|
| 484 |
+
translateEl(mutation.target)
|
| 485 |
+
} else {
|
| 486 |
+
mutation.addedNodes.forEach(node => {
|
| 487 |
+
if (node.className === 'bilingual__trans_wrapper') return
|
| 488 |
+
|
| 489 |
+
_nodesCount++
|
| 490 |
+
if (node.nodeType === 1 && /(output|gradio)-(html|markdown)/.test(node.className)) {
|
| 491 |
+
translateEl(node, { rich: true })
|
| 492 |
+
} else if (node.nodeType === 3) {
|
| 493 |
+
doTranslate(node, node.textContent, 'text')
|
| 494 |
+
} else {
|
| 495 |
+
translateEl(node, { deep: true })
|
| 496 |
+
}
|
| 497 |
+
})
|
| 498 |
+
}
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
if (_nodesCount > 0) {
|
| 502 |
+
logger.info(`UI Update #${_count++}: ${performance.now() - _now} ms, ${_nodesCount} nodes`, mutations)
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
+
if (loaded) return;
|
| 506 |
+
if (i18n) return;
|
| 507 |
+
|
| 508 |
+
loaded = true
|
| 509 |
+
setup()
|
| 510 |
+
})
|
| 511 |
+
|
| 512 |
+
observer.observe(gradioApp(), {
|
| 513 |
+
characterData: true,
|
| 514 |
+
childList: true,
|
| 515 |
+
subtree: true,
|
| 516 |
+
attributes: true,
|
| 517 |
+
attributeFilter: ['title', 'placeholder']
|
| 518 |
+
})
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
// Init after page loaded
|
| 522 |
+
document.addEventListener('DOMContentLoaded', init)
|
| 523 |
+
})();
|
scripts/bilingual_localization_helper.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This helper script loads the list of localization files and
|
| 2 |
+
# exposes the current localization file name and path to the javascript side
|
| 3 |
+
|
| 4 |
+
import gradio as gr
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from modules import localization, script_callbacks, shared
|
| 7 |
+
import json
|
| 8 |
+
|
| 9 |
+
# Webui root path
|
| 10 |
+
ROOT_DIR = Path().absolute()
|
| 11 |
+
|
| 12 |
+
# The localization files
|
| 13 |
+
I18N_DIRS = { k: str(Path(v).relative_to(ROOT_DIR).as_posix()) for k, v in localization.localizations.items() }
|
| 14 |
+
|
| 15 |
+
# Register extension options
|
| 16 |
+
def on_ui_settings():
|
| 17 |
+
BL_SECTION = ("bl", "Bilingual Localization")
|
| 18 |
+
# enable in settings
|
| 19 |
+
shared.opts.add_option("bilingual_localization_enabled", shared.OptionInfo(True, "Enable Bilingual Localization", section=BL_SECTION))
|
| 20 |
+
|
| 21 |
+
# enable devtools log
|
| 22 |
+
shared.opts.add_option("bilingual_localization_logger", shared.OptionInfo(False, "Enable Devtools Log", section=BL_SECTION))
|
| 23 |
+
|
| 24 |
+
# current localization file
|
| 25 |
+
shared.opts.add_option("bilingual_localization_file", shared.OptionInfo("None", "Localization file (Please leave `User interface` - `Localization` as None)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(shared.cmd_opts.localizations_dir), section=BL_SECTION))
|
| 26 |
+
|
| 27 |
+
# translation order
|
| 28 |
+
shared.opts.add_option("bilingual_localization_order", shared.OptionInfo("Translation First", "Translation display order", gr.Radio, {"choices": ["Translation First", "Original First"]}, section=BL_SECTION))
|
| 29 |
+
|
| 30 |
+
# all localization files path in hidden option
|
| 31 |
+
shared.opts.add_option("bilingual_localization_dirs", shared.OptionInfo(json.dumps(I18N_DIRS), "Localization dirs", section=BL_SECTION, component_args={"visible": False}))
|
| 32 |
+
|
| 33 |
+
script_callbacks.on_ui_settings(on_ui_settings)
|