Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
864e0651b1 | ||
|
|
65d328eb38 | ||
|
|
731d360323 | ||
|
|
c4ccdba268 | ||
|
|
4f00492e49 | ||
|
|
abcf2baad6 | ||
|
|
49a7698993 | ||
|
|
8d2548acaf | ||
|
|
251deb5886 | ||
|
|
7a15bdeadc | ||
|
|
1e59d57764 | ||
|
|
12b3768598 | ||
|
|
3abe5b98d0 | ||
|
|
ad004105c3 | ||
|
|
f70266197e |
2
.env
2
.env
@@ -2,7 +2,7 @@ GENERATE_SOURCEMAP=false
|
||||
|
||||
REACT_APP_NAME=KISS Translator
|
||||
REACT_APP_NAME_CN=简约翻译
|
||||
REACT_APP_VERSION=2.0.9
|
||||
REACT_APP_VERSION=2.0.10
|
||||
|
||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||
|
||||
|
||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -7,15 +7,15 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
version: 9.14.4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "25.1.0"
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
- run: pnpm install
|
||||
- run: pnpm build+zip
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
path: build
|
||||
deploy-web:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
folder: build/web
|
||||
create-release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||
steps:
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
client: ["chrome", "edge", "firefox", "userscript", "thunderbird"]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
|
||||
1
.pnpm-version
Normal file
1
.pnpm-version
Normal file
@@ -0,0 +1 @@
|
||||
9.14.4
|
||||
@@ -1,6 +1,6 @@
|
||||
# KISS Translator
|
||||
|
||||
English | [简体中文](README.md)
|
||||
[English](README.en.md) | [中文](README.md) | [日本語](README.ja.md) | [한국어](README.ko.md)
|
||||
|
||||
A simple, open source [bilingual translation extension & Greasemonkey script](https://github.com/fishjar/kiss-translator).
|
||||
|
||||
|
||||
177
README.ja.md
Normal file
177
README.ja.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# KISS Translator シンプル翻訳
|
||||
|
||||
[English](README.en.md) | [中文](README.md) | [日本語](README.ja.md) | [한국어](README.ko.md)
|
||||
|
||||
シンプルでオープンソースの [バイリンガル対照翻訳拡張機能&ユーザースクリプト](https://github.com/fishjar/kiss-translator)です。
|
||||
|
||||
[kiss-translator.webm](https://github.com/fishjar/kiss-translator/assets/1157624/f7ba8a5c-e4a8-4d5a-823a-5c5c67a0a47f)
|
||||
|
||||
## 特徴
|
||||
|
||||
- [x] シンプルさを維持
|
||||
- [x] オープンソース
|
||||
- [x] 主要なブラウザに対応
|
||||
- [x] Chrome/Edge
|
||||
- [x] Firefox
|
||||
- [x] Kiwi (Android)
|
||||
- [x] Orion (iOS)
|
||||
- [x] Safari
|
||||
- [x] Thunderbird
|
||||
- [x] 複数の翻訳サービスをサポート
|
||||
- [x] Google/Microsoft
|
||||
- [x] Tencent/Volcengine
|
||||
- [x] OpenAI/Gemini/Claude/Ollama/DeepSeek/OpenRouter
|
||||
- [x] DeepL/DeepLX/NiuTrans
|
||||
- [x] AzureAI/CloudflareAI
|
||||
- [x] Chromeブラウザ内蔵AI翻訳(BuiltinAI)
|
||||
- [x] 一般的な翻訳シナリオをカバー
|
||||
- [x] Webページのバイリンガル対照翻訳
|
||||
- [x] 入力ボックス翻訳
|
||||
- ショートカットキーで入力ボックス内のテキストを即座に他言語に翻訳
|
||||
- [x] テキスト選択翻訳
|
||||
- [x] 任意のページで翻訳ボックスを開き、複数の翻訳サービスで比較翻訳が可能
|
||||
- [x] 英語辞書翻訳
|
||||
- [x] 単語のブックマーク
|
||||
- [x] マウスオーバー翻訳
|
||||
- [x] YouTube 字幕翻訳
|
||||
- 任意の翻訳サービスを使用してビデオ字幕を翻訳し、バイリンガル表示をサポート
|
||||
- 基本的な字幕結合・改行アルゴリズムを内蔵し、翻訳品質を向上
|
||||
- AIによる改行機能をサポートし、翻訳品質をさらに向上
|
||||
- 字幕スタイルのカスタマイズ
|
||||
- [x] 多様な翻訳効果をサポート
|
||||
- [x] テキスト自動認識と手動ルールの2つのモードをサポート
|
||||
- テキスト自動認識モードにより、ほとんどのWebサイトでルールを記述しなくても完全な翻訳が可能
|
||||
- 手動ルールモードで、特定のWebサイトに合わせた最適な最適化が可能
|
||||
- [x] 翻訳テキストスタイルのカスタマイズ
|
||||
- [x] リッチテキストの翻訳と表示をサポートし、原文のリンクやその他のテキストスタイルを可能な限り保持
|
||||
- [x] 翻訳文のみの表示(原文を非表示)をサポート
|
||||
- [x] 翻訳APIの高度な機能
|
||||
- [x] カスタムAPIにより、理論上あらゆる翻訳インターフェースをサポート
|
||||
- [x] 翻訳テキストの統合バッチ送信
|
||||
- [x] AIコンテキスト(会話メモリ)機能をサポートし、翻訳品質を向上
|
||||
- [x] カスタムAI用語集
|
||||
- [x] すべてのインターフェースがフックやカスタムパラメータなどの高度な機能をサポート
|
||||
- [x] クライアント間のデータ同期
|
||||
- [x] KISS-Worker(cloudflare/docker)
|
||||
- [x] WebDAV
|
||||
- [x] カスタム翻訳ルール
|
||||
- [x] ルールの購読/ルール共有
|
||||
- [x] カスタム専門用語
|
||||
- [x] カスタムショートカットキー
|
||||
- `Alt+Q` 翻訳をオン
|
||||
- `Alt+C` スタイル切り替え
|
||||
- `Alt+K` 設定ポップアップを開く
|
||||
- `Alt+S` 翻訳ポップアップを開く/選択テキストを翻訳
|
||||
- `Alt+O` 設定ページを開く
|
||||
- `Alt+I` 入力ボックス翻訳
|
||||
|
||||
## インストール
|
||||
|
||||
> 注:以下の理由により、ブラウザ拡張機能の使用を優先することをお勧めします
|
||||
>
|
||||
> - ブラウザ拡張機能の方が機能が完全です(ローカル言語認識、右クリックメニューなど)
|
||||
> - ユーザースクリプトはより多くの問題(クロスドメイン問題、スクリプトの競合など)に遭遇する可能性があります
|
||||
|
||||
- [x] ブラウザ拡張機能
|
||||
- [x] Chrome [インストール](https://chrome.google.com/webstore/detail/kiss-translator/bdiifdefkgmcblbcghdlonllpjhhjgof?hl=zh-CN)
|
||||
- [x] Kiwi (Android)
|
||||
- [x] Orion (iOS)
|
||||
- [x] Edge [インストール](https://microsoftedge.microsoft.com/addons/detail/%E7%AE%80%E7%BA%A6%E7%BF%BB%E8%AF%91/jemckldkclkinpjighnoilpbldbdmmlh?hl=zh-CN)
|
||||
- [x] Firefox [インストール](https://addons.mozilla.org/zh-CN/firefox/addon/kiss-translator/)
|
||||
- [ ] Safari
|
||||
- [ ] Safari (Mac)
|
||||
- [ ] Safari (iOS)
|
||||
- [x] Thunderbird [ダウンロード](https://github.com/fishjar/kiss-translator/releases)
|
||||
- [x] ユーザースクリプト
|
||||
- [x] Chrome/Edge/Firefox ([Tampermonkey](https://www.tampermonkey.net/)/[Violentmonkey](https://violentmonkey.github.io/)) [インストールリンク](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)
|
||||
- [Greasy Fork](https://greasyfork.org/zh-CN/scripts/472840-kiss-translator)
|
||||
- [x] iOS Safari ([Userscripts Safari](https://github.com/quoid/userscripts)) [インストールリンク](https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js)
|
||||
|
||||
## 関連プロジェクト
|
||||
|
||||
- データ同期サービス: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker)
|
||||
- 本プロジェクトのデータ同期サービスとして使用できます。
|
||||
- 個人のプライベートなルールリストの共有にも使用できます。
|
||||
- セルフホスト、セルフマネジメント、データはプライベート。
|
||||
- コミュニティ購読ルール: [https://github.com/fishjar/kiss-rules](https://github.com/fishjar/kiss-rules)
|
||||
- コミュニティによってメンテナンスされた、最新かつ最も完全な購読ルールリストを提供します。
|
||||
- ルール関連の問題についての助けを求める。
|
||||
|
||||
## よくある質問(FAQ)
|
||||
|
||||
### ショートカットキーの設定方法
|
||||
|
||||
拡張機能の管理ページで設定します。例:
|
||||
|
||||
- chrome [chrome://extensions/shortcuts](chrome://extensions/shortcuts)
|
||||
- firefox [about:addons](about:addons)
|
||||
|
||||
### ルール設定の優先順位は?
|
||||
|
||||
個人ルール > 購読ルール > グローバルルール
|
||||
|
||||
グローバルルールの優先順位は最も低いですが、フォールバックルールとして非常に重要です。
|
||||
|
||||
### API(Ollamaなど)のテストに失敗する
|
||||
|
||||
APIテストの失敗には、一般的に以下の原因が考えられます:
|
||||
|
||||
- アドレスが間違っている:
|
||||
- 例えば `Ollama` にはネイティブAPIアドレスと `Openai` 互換のアドレスがありますが、本プラグインは現在、`Openai` 互換アドレスをサポートしており、`Ollama` ネイティブAPIアドレスはサポートしていません
|
||||
- 一部のAIモデルが統合翻訳をサポートしていない:
|
||||
- この場合、統合翻訳を無効にするか、カスタムAPIを使用して対応できます。
|
||||
- または、カスタムAPIを使用して対応します。詳細は[カスタムAPIサンプルドキュメント](https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md)を参照してください
|
||||
- 一部のAIモデルでパラメータが一致しない:
|
||||
- 例えば `Gemini` のネイティブAPIはパラメータの不一致が大きく、一部のバージョンのモデルが特定のパラメータをサポートしていないためエラーが返されることがあります。
|
||||
- この場合、`Hook` を使用してリクエスト `body` を変更するか、`Gemini2` (`Openai` 互換アドレス) に切り替えることができます
|
||||
- サーバーのクロスドメイン制限によりアクセスが拒否され、403エラーが返される:
|
||||
- 例えば `Ollama` を起動する際に、環境変数 `OLLAMA_ORIGINS=*` を追加する必要があります。参考:https://github.com/fishjar/kiss-translator/issues/174
|
||||
|
||||
### 入力したAPIがユーザースクリプトで使用できない
|
||||
|
||||
ユーザースクリプトは、リクエストを送信するためにドメインのホワイトリストを追加する必要があります。
|
||||
|
||||
### カスタムAPIのhook関数の設定方法
|
||||
|
||||
カスタムAPI機能は非常に強力で柔軟性があり、理論的にはどんな翻訳APIにも接続できます。
|
||||
|
||||
サンプル参照: [custom-api_v2.md](https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md)
|
||||
|
||||
### ユーザースクリプトの設定ページに直接アクセスする方法
|
||||
|
||||
設定ページアドレス: https://fishjar.github.io/kiss-translator/options.html
|
||||
|
||||
### 字幕翻訳のヒント
|
||||
|
||||
KTボタンがオンの状態(青地に白文字)であれば、何度もクリックする必要はありません。Youtubeプレーヤーの字幕ボタンをクリックしてオンにするだけで、バイリンガル字幕が自動的に表示されるのを待つだけです。
|
||||
|
||||
## 今後の計画
|
||||
|
||||
本プロジェクトは余暇に開発しており、厳密なタイムスケジュールはありません。コミュニティの共同構築を歓迎します。以下は初期段階の機能の方向性です:
|
||||
|
||||
- [x] **テキストの統合送信**:リクエスト戦略を最適化し、翻訳APIの呼び出し回数を減らし、パフォーマンスを向上させます。
|
||||
- [x] **リッチテキスト翻訳の強化**:より複雑なページ構造やリッチテキストコンテンツの正確な翻訳をサポートします。
|
||||
- [x] **カスタム/AI APIの強化**:コンテキストメモリ、複数ラウンドの対話など、高度なAI機能をサポートします。
|
||||
- [x] **英語辞書のフォールバックメカニズム**:翻訳サービスが利用できない場合、他の辞書に切り替えるか、ローカル辞書での検索にフォールバックします。
|
||||
- [x] **YouTube字幕サポートの最適化**:ストリーミング字幕の結合と翻訳体験を改善し、途切れを減らします。
|
||||
- [ ] **ルール共同構築メカニズムのアップグレード**:より柔軟なルールの共有、バージョン管理、コミュニティレビュープロセスを導入します。
|
||||
|
||||
特定の方向に興味がある場合は、[Issues](https://github.com/fishjar/kiss-translator/issues) で議論したり、PRを送信したりすることを歓迎します!
|
||||
|
||||
## 開発ガイド
|
||||
|
||||
```sh
|
||||
git clone [https://github.com/fishjar/kiss-translator.git](https://github.com/fishjar/kiss-translator.git)
|
||||
cd kiss-translator
|
||||
git checkout dev # PRを送信する場合はdevブランチにプッシュすることをお勧めします
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## コミュニケーション
|
||||
|
||||
- [Telegram グループ](https://t.me/+RRCu_4oNwrM2NmFl)に参加
|
||||
|
||||
## 寄付
|
||||
|
||||

|
||||
178
README.ko.md
Normal file
178
README.ko.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# KISS Translator 심플 번역
|
||||
|
||||
[English](README.en.md) | [中文](README.md) | [日本語](README.ja.md) | [한국어](README.ko.md)
|
||||
|
||||
심플하고 오픈 소스인 [이중 언어 대조 번역 확장 프로그램 & 유저 스크립트](https://github.com/fishjar/kiss-translator)입니다.
|
||||
|
||||
[kiss-translator.webm](https://github.com/fishjar/kiss-translator/assets/1157624/f7ba8a5c-e4a8-4d5a-823a-5c5c67a0a47f)
|
||||
|
||||
## 특징
|
||||
|
||||
- [x] 심플함 유지
|
||||
- [x] 오픈 소스
|
||||
- [x] 주요 브라우저 지원
|
||||
- [x] Chrome/Edge
|
||||
- [x] Firefox
|
||||
- [x] Kiwi (Android)
|
||||
- [x] Orion (iOS)
|
||||
- [x] Safari
|
||||
- [x] Thunderbird
|
||||
- [x] 다양한 번역 서비스 지원
|
||||
- [x] Google/Microsoft
|
||||
- [x] Tencent/Volcengine
|
||||
- [x] OpenAI/Gemini/Claude/Ollama/DeepSeek/OpenRouter
|
||||
- [x] DeepL/DeepLX/NiuTrans
|
||||
- [x] AzureAI/CloudflareAI
|
||||
- [x] Chrome 브라우저 내장 AI 번역(BuiltinAI)
|
||||
- [x] 일반적인 번역 시나리오 지원
|
||||
- [x] 웹페이지 이중 언어 대조 번역
|
||||
- [x] 입력창 번역
|
||||
- 단축키를 통해 입력창 내 텍스트를 즉시 다른 언어로 번역
|
||||
- [x] 텍스트 선택 번역
|
||||
- [x] 모든 페이지에서 번역창을 열어 여러 번역 서비스로 비교 번역 가능
|
||||
- [x] 영어 사전 번역
|
||||
- [x] 단어 즐겨찾기
|
||||
- [x] 마우스오버 번역
|
||||
- [x] YouTube 자막 번역
|
||||
- 모든 번역 서비스를 사용하여 비디오 자막을 번역하고 이중 언어로 표시 지원
|
||||
- 기본적인 자막 병합 및 줄 바꿈 알고리즘 내장으로 번역 품질 향상
|
||||
- AI 줄 바꿈 기능 지원으로 번역 품질 추가 향상
|
||||
- 사용자 정의 자막 스타일
|
||||
- [x] 다양한 번역 효과 지원
|
||||
- [x] 자동 텍스트 인식 및 수동 규칙 두 가지 모드 지원
|
||||
- 자동 텍스트 인식 모드는 대부분의 웹사이트에서 규칙 작성 없이도 완벽한 번역 가능
|
||||
- 수동 규칙 모드로 특정 웹사이트에 대한 최적의 최적화 가능
|
||||
- [x] 번역문 스타일 사용자 정의
|
||||
- [x] 리치 텍스트 번역 및 표시 지원, 원문의 링크 및 기타 텍스트 스타일 최대한 보존
|
||||
- [x] 번역문만 표시 (원문 숨기기) 지원
|
||||
- [x] 번역 인터페이스 고급 기능
|
||||
- [x] 사용자 정의 인터페이스를 통해 이론상 모든 번역 인터페이스 지원
|
||||
- [x] 번역 텍스트 일괄 통합 전송
|
||||
- [x] AI 컨텍스트 (대화 기억) 기능 지원으로 번역 품질 향상
|
||||
- [x] 사용자 정의 AI 용어 사전
|
||||
- [x] 모든 인터페이스는 후크 및 사용자 정의 파라미터 등 고급 기능 지원
|
||||
- [x] 클라이언트 간 데이터 동기화
|
||||
- [x] KISS-Worker (cloudflare/docker)
|
||||
- [x] WebDAV
|
||||
- [x] 사용자 정의 번역 규칙
|
||||
- [x] 규칙 구독 / 규칙 공유
|
||||
- [x] 사용자 정의 전문 용어
|
||||
- [x] 사용자 정의 단축키
|
||||
- `Alt+Q` 번역 켜기
|
||||
- `Alt+C` 스타일 전환
|
||||
- `Alt+K` 설정 팝업 열기
|
||||
- `Alt+S` 번역 팝업 열기 / 선택한 텍스트 번역
|
||||
- `Alt+O` 설정 페이지 열기
|
||||
- `Alt+I` 입력창 번역
|
||||
|
||||
## 설치
|
||||
|
||||
> 참고: 다음과 같은 이유로 브라우저 확장 프로그램 사용을 우선적으로 권장합니다.
|
||||
>
|
||||
> - 브라우저 확장 프로그램의 기능이 더 완전합니다 (로컬 언어 인식, 우클릭 메뉴 등).
|
||||
> - 유저 스크립트는 사용상 더 많은 문제 (크로스 도메인 문제, 스크립트 충돌 등)를 겪을 수 있습니다.
|
||||
|
||||
- [x] 브라우저 확장 프로그램
|
||||
- [x] Chrome [설치 주소](https://chrome.google.com/webstore/detail/kiss-translator/bdiifdefkgmcblbcghdlonllpjhhjgof?hl=zh-CN)
|
||||
- [x] Kiwi (Android)
|
||||
- [x] Orion (iOS)
|
||||
- [x] Edge [설치 주소](https://microsoftedge.microsoft.com/addons/detail/%E7%AE%80%E7%BA%A6%E7%BF%BB%E8%AF%91/jemckldkclkinpjighnoilpbldbdmmlh?hl=zh-CN)
|
||||
- [x] Firefox [설치 주소](https://addons.mozilla.org/zh-CN/firefox/addon/kiss-translator/)
|
||||
- [ ] Safari
|
||||
- [ ] Safari (Mac)
|
||||
- [ ] Safari (iOS)
|
||||
- [x] Thunderbird [다운로드 주소](https://github.com/fishjar/kiss-translator/releases)
|
||||
- [x] 유저 스크립트
|
||||
- [x] Chrome/Edge/Firefox ([Tampermonkey](https://www.tampermonkey.net/)/[Violentmonkey](https://violentmonkey.github.io/)) [설치 링크](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)
|
||||
- [Greasy Fork](https://greasyfork.org/zh-CN/scripts/472840-kiss-translator)
|
||||
- [x] iOS Safari ([Userscripts Safari](https://github.com/quoid/userscripts)) [설치 링크](https://fishjar.github.io/kiss-translator/kiss-translator-ios-safari.user.js)
|
||||
|
||||
## 관련 프로젝트
|
||||
|
||||
- 데이터 동기화 서비스: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker)
|
||||
- 본 프로젝트의 데이터 동기화 서비스로 사용할 수 있습니다.
|
||||
- 개인의 비공개 규칙 목록을 공유하는 데에도 사용할 수 있습니다.
|
||||
- 직접 배포, 직접 관리, 데이터 비공개.
|
||||
- 커뮤니티 구독 규칙: [https://github.com/fishjar/kiss-rules](https://github.com/fishjar/kiss-rules)
|
||||
- 커뮤니티에서 유지 관리하는 최신의 가장 완벽한 구독 규칙 목록을 제공합니다.
|
||||
- 규칙 관련 문제에 대한 도움 요청.
|
||||
|
||||
## 자주 묻는 질문 (FAQ)
|
||||
|
||||
### 단축키는 어떻게 설정하나요?
|
||||
|
||||
플러그인 관리 페이지에서 설정합니다. 예:
|
||||
|
||||
- chrome [chrome://extensions/shortcuts](chrome://extensions/shortcuts)
|
||||
- firefox [about:addons](about:addons)
|
||||
|
||||
### 규칙 설정의 우선순위는 어떻게 되나요?
|
||||
|
||||
개인 규칙 > 구독 규칙 > 전역 규칙
|
||||
|
||||
그중 전역 규칙은 우선순위가 가장 낮지만, 예비 규칙으로서 매우 중요합니다.
|
||||
|
||||
### 인터페이스 (Ollama 등) 테스트 실패
|
||||
|
||||
일반적으로 인터페이스 테스트 실패는 다음과 같은 몇 가지 원인이 있습니다:
|
||||
|
||||
- 주소를 잘못 입력한 경우:
|
||||
- 예를 들어 `Ollama`는 네이티브 인터페이스 주소와 `Openai` 호환 주소가 있습니다. 본 플러그인은 현재 `Openai` 호환 주소를 통일되게 지원하며, `Ollama` 네이티브 인터페이스 주소는 지원하지 않습니다.
|
||||
- 일부 AI 모델이 통합 번역을 지원하지 않는 경우:
|
||||
- 이 경우 통합 번역을 비활성화하거나 사용자 정의 인터페이스 방식을 통해 사용할 수 있습니다.
|
||||
- 또는 사용자 정의 인터페이스 방식을 통해 사용합니다. 자세한 내용은 [사용자 정의 인터페이스 예시 문서](https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md)를 참조하세요.
|
||||
- 일부 AI 모델의 파라미터가 일치하지 않는 경우:
|
||||
- 예를 들어 `Gemini` 네이티브 인터페이스 파라미터는 매우 불일치하며, 일부 버전의 모델은 특정 파라미터를 지원하지 않아 오류를 반환할 수 있습니다.
|
||||
- 이 경우 `Hook`을 사용하여 요청 `body`를 수정하거나, `Gemini2` (`Openai` 호환 주소)로 변경할 수 있습니다.
|
||||
- 서버의 크로스 도메인 접근 제한으로 403 오류가 반환되는 경우:
|
||||
- 예를 들어 `Ollama` 시작 시 환경 변수 `OLLAMA_ORIGINS=*`를 추가해야 합니다. 참고: https://github.com/fishjar/kiss-translator/issues/174
|
||||
|
||||
### 입력한 인터페이스를 유저 스크립트에서 사용할 수 없습니다
|
||||
|
||||
유저 스크립트는 도메인 화이트리스트를 추가해야 요청을 보낼 수 있습니다.
|
||||
|
||||
### 사용자 정의 인터페이스의 hook 함수는 어떻게 설정하나요?
|
||||
|
||||
사용자 정의 인터페이스 기능은 매우 강력하고 유연하며, 이론적으로 어떤 번역 인터페이스든 연결할 수 있습니다.
|
||||
|
||||
예시 참고: [custom-api_v2.md](https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md)
|
||||
|
||||
### 유저 스크립트 설정 페이지로 바로 이동하는 방법
|
||||
|
||||
설정 페이지 주소: https://fishjar.github.io/kiss-translator/options.html
|
||||
|
||||
### 자막 번역 팁
|
||||
|
||||
KT 버튼이 켜진 상태(파란 바탕에 흰 글씨)이기만 하면, 여러 번 클릭할 필요 없이 Youtube 플레이어의 원래 자막 버튼을 클릭하여 켜기만 하면 이중 언어 자막이 자동으로 나타날 때까지 기다리면 됩니다.
|
||||
|
||||
## 향후 계획
|
||||
|
||||
본 프로젝트는 여가 시간에 개발되며, 엄격한 시간표는 없습니다. 커뮤니티의 공동 구축을 환영합니다. 다음은 초기 구상 중인 기능 방향입니다:
|
||||
|
||||
- [x] **텍스트 통합 전송**: 요청 전략을 최적화하여 번역 인터페이스 호출 횟수를 줄이고 성능을 향상시킵니다.
|
||||
- [x] **리치 텍스트 번역 강화**: 더 복잡한 페이지 구조와 리치 텍스트 콘텐츠의 정확한 번역을 지원합니다.
|
||||
- [x] **사용자 정의/AI 인터페이스 강화**: 컨텍스트 기억, 다중 턴 대화 등 고급 AI 기능을 지원합니다.
|
||||
- [x] **영어 사전 예비 메커니즘**: 번역 서비스가 실패할 경우 다른 사전으로 전환하거나 로컬 사전 조회로 대체합니다.
|
||||
- [x] **YouTube 자막 지원 최적화**: 스트리밍 자막의 병합 및 번역 경험을 개선하고, 끊김을 줄입니다.
|
||||
- [ ] **규칙 공동 구축 메커니즘 업그레이드**: 더 유연한 규칙 공유, 버전 관리 및 커뮤니티 검토 프로세스를 도입합니다.
|
||||
|
||||
특정 방향에 관심이 있다면, [Issues](https://github.com/fishjar/kiss-translator/issues)에서 토론하거나 PR을 제출해 주세요!
|
||||
|
||||
## 개발 가이드
|
||||
|
||||
```sh
|
||||
git clone [https://github.com/fishjar/kiss-translator.git](https://github.com/fishjar/kiss-translator.git)
|
||||
cd kiss-translator
|
||||
git checkout dev # PR 제출 시 dev 브랜치로 푸시하는 것을 권장합니다
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 커뮤니티
|
||||
|
||||
- [Telegram 그룹](https://t.me/+RRCu_4oNwrM2NmFl) 가입
|
||||
|
||||
## 후원
|
||||
|
||||

|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 简约翻译
|
||||
# KISS Translator 简约翻译
|
||||
|
||||
[English](README.en.md) | 简体中文
|
||||
[English](README.en.md) | [中文](README.md) | [日本語](README.ja.md) | [한국어](README.ko.md)
|
||||
|
||||
一个简约、开源的 [双语对照翻译扩展 & 油猴脚本](https://github.com/fishjar/kiss-translator)。
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kiss-translator",
|
||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||
"version": "2.0.9",
|
||||
"version": "2.0.10",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "2.0.9",
|
||||
"version": "2.0.10",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "2.0.9",
|
||||
"version": "2.0.10",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "2.0.9",
|
||||
"version": "2.0.10",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -121,7 +121,7 @@ export async function run(isUserscript = false) {
|
||||
// if (document?.documentElement?.tagName?.toUpperCase() !== "HTML") {
|
||||
// return;
|
||||
// }
|
||||
if (!document?.contentType?.includes("html")) {
|
||||
if (!document?.contentType?.includes("text")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -672,18 +672,18 @@ export const I18N = {
|
||||
ko: `1. BuiltinAI는 브라우저 내장 AI 번역으로, 현재 Chrome 138 이상 버전에서만 지원됩니다.`,
|
||||
},
|
||||
about_api_2: {
|
||||
zh: `2、大部分AI接口都与OpenAI兼容,因此选择添加OpenAI类型即可。It should be noted that Prompt has two types: batch translation and nobatch translation. Not all interfaces support batch translation.`,
|
||||
en: `2. Most AI interfaces are compatible with OpenAI, so just choose to add the OpenAI type.`,
|
||||
zh_TW: `2.大部分AI介面都與OpenAI相容,因此選擇新增OpenAI類型即可。要注意的是Prompt分聚合翻譯和非聚合翻譯兩種,不是所有介面都支援聚合翻譯。`,
|
||||
ja: `2. ほとんどのAIインターフェースはOpenAIと互換性があるため、OpenAIタイプを追加するだけで十分です。`,
|
||||
ko: `2. 대부분의 AI 인터페이스는 OpenAI와 호환되므로 OpenAI 유형을 추가하기만 하면 됩니다.`,
|
||||
zh: `2、大部分AI接口都与OpenAI兼容,因此选择OpenAI类型即可。“是否聚合发送翻译请求”所对应的 Prompt 并不相同,并且不是所有接口都支持聚合翻译。`,
|
||||
en: `2. Most AI interfaces are compatible with OpenAI, so you can simply select the OpenAI type. The prompts corresponding to “Whether to aggregate translation requests” are different, and not all interfaces support aggregated translation.`,
|
||||
zh_TW: `2. 大部分的 AI 介面都與 OpenAI 相容,因此選擇 OpenAI 類型即可。「是否聚合發送翻譯請求」所對應的 Prompt 並不相同,並且不是所有介面都支援聚合翻譯。`,
|
||||
ja: `2. ほとんどの AI インターフェースは OpenAI と互換性があるため、OpenAI タイプを選択すれば問題ありません。「翻訳リクエストをまとめて送信するかどうか」に対応するプロンプトは異なり、すべてのインターフェースが集約翻訳をサポートしているわけではありません。`,
|
||||
ko: `2. 대부분의 AI 인터페이스는 OpenAI와 호환되므로 OpenAI 유형을 선택하면 됩니다. “번역 요청을 집합적으로 보낼지 여부”에 대응하는 프롬프트는 서로 다르며, 모든 인터페이스가 집합 번역을 지원하는 것은 아닙니다.`,
|
||||
},
|
||||
about_api_3: {
|
||||
zh: `3、暂未列出的接口,理论上都可以通过自定义接口 (Custom) 的形式支持。`,
|
||||
en: `3. Interfaces that have not yet been launched can theoretically be supported through custom interfaces.`,
|
||||
zh_TW: `3.暫未列出的介面,理論上都可透過自訂介面 (Custom) 的形式支援。`,
|
||||
ja: `3. まだリストされていないインターフェースも、理論上はカスタムインターフェース (Custom) を通じてサポート可能です。`,
|
||||
ko: `3. 아직 등록되지 않은 인터페이스도 이론적으로는 사용자 정의 인터페이스 (Custom)를 통해 지원될 수 있습니다.`,
|
||||
zh: `3、理论上,所有翻译接口,都可以通过自定义接口 (Custom) 的形式使用。`,
|
||||
en: `3. In theory, all translation interfaces can be used by configuring them as a custom interface.`,
|
||||
zh_TW: `3. 理論上,所有翻譯介面都可以透過自訂介面(Custom)的方式來使用。`,
|
||||
ja: `3. 理論的には、すべての翻訳インターフェースはカスタム(Custom)インターフェースとして設定することで利用できます。`,
|
||||
ko: `3. 이론적으로 모든 번역 인터페이스는 커스텀(Custom) 인터페이스로 설정하여 사용할 수 있습니다.`,
|
||||
},
|
||||
about_api_proxy: {
|
||||
zh: `查看自建一个翻译接口代理`,
|
||||
@@ -2577,6 +2577,20 @@ export const I18N = {
|
||||
ja: `原字幕を表示`,
|
||||
ko: `원본 자막 표시`,
|
||||
},
|
||||
subtitle_same_lang: {
|
||||
zh: `原语言与目标语言相同,字幕不予处理`,
|
||||
en: `The source language is the same as the target language, subtitles will not be processed`,
|
||||
zh_TW: `原語言與目標語言相同時,字幕不予處理`,
|
||||
ja: `原言語と目標言語が同じ場合、字幕は処理されません`,
|
||||
ko: `원본 언어와 대상 언어가 동일한 경우, 자막은 처리되지 않습니다`,
|
||||
},
|
||||
plain_text_translate: {
|
||||
zh: `纯文本翻译`,
|
||||
en: `Plain text translation`,
|
||||
zh_TW: `純文字翻譯`,
|
||||
ja: `プレーンテキスト翻訳`,
|
||||
ko: `순수 텍스트 번역`,
|
||||
},
|
||||
};
|
||||
|
||||
export const newI18n = (lang) => (key) => I18N[key]?.[lang] || "";
|
||||
|
||||
@@ -13,5 +13,11 @@ export function useDebouncedCallback(callback, delay) {
|
||||
[delay]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
debouncedCallback.cancel();
|
||||
};
|
||||
}, [debouncedCallback]);
|
||||
|
||||
return debouncedCallback;
|
||||
}
|
||||
|
||||
@@ -25,13 +25,17 @@ const SettingContext = createContext({
|
||||
reloadSetting: () => {},
|
||||
});
|
||||
|
||||
export function SettingProvider({ children }) {
|
||||
export function SettingProvider({ children, isSettingPage }) {
|
||||
const {
|
||||
data: setting,
|
||||
isLoading,
|
||||
update,
|
||||
reload,
|
||||
} = useStorage(STOKEY_SETTING, DEFAULT_SETTING, KV_SETTING_KEY);
|
||||
} = useStorage(
|
||||
STOKEY_SETTING,
|
||||
DEFAULT_SETTING,
|
||||
isSettingPage ? KV_SETTING_KEY : ""
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof setting?.darkMode === "boolean") {
|
||||
@@ -43,6 +47,8 @@ export function SettingProvider({ children }) {
|
||||
}, [setting?.darkMode, update]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSettingPage) return;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
logger.setLevel(setting?.logLevel);
|
||||
@@ -53,7 +59,7 @@ export function SettingProvider({ children }) {
|
||||
logger.error("Failed to fetch log level, using default.", error);
|
||||
}
|
||||
})();
|
||||
}, [setting]);
|
||||
}, [isSettingPage, setting?.logLevel]);
|
||||
|
||||
const updateSetting = useCallback(
|
||||
(objOrFn) => {
|
||||
|
||||
@@ -62,10 +62,12 @@ class Logger {
|
||||
return;
|
||||
}
|
||||
|
||||
this.config.level = newLevelObject;
|
||||
console.log(
|
||||
`[${this.config.prefix}] Log level dynamically set to ${this.config.level.name}`
|
||||
);
|
||||
if (this.config.level.value !== newLevelObject.value) {
|
||||
this.config.level = newLevelObject;
|
||||
console.log(
|
||||
`[${this.config.prefix}] Log level dynamically set to ${this.config.level.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -104,7 +104,7 @@ export default class ShadowDomManager {
|
||||
this.#hostElement = host;
|
||||
const shadowContainer = host.attachShadow({ mode: "open" });
|
||||
const appRoot = document.createElement("div");
|
||||
appRoot.className = `${this._id}_wrapper`;
|
||||
appRoot.className = `${this._id}_wrapper notranslate`;
|
||||
shadowContainer.appendChild(appRoot);
|
||||
|
||||
const cache = createCache({
|
||||
|
||||
@@ -26,7 +26,7 @@ async function set(key, val) {
|
||||
} else if (isGm) {
|
||||
await (window.KISS_GM || GM).setValue(key, val);
|
||||
} else {
|
||||
window?.localStorage.setItem(key, val);
|
||||
window.localStorage.setItem(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ async function get(key) {
|
||||
const val = await (window.KISS_GM || GM).getValue(key);
|
||||
return val;
|
||||
}
|
||||
return window?.localStorage.getItem(key);
|
||||
return window.localStorage.getItem(key);
|
||||
}
|
||||
|
||||
async function del(key) {
|
||||
@@ -47,7 +47,7 @@ async function del(key) {
|
||||
} else if (isGm) {
|
||||
await (window.KISS_GM || GM).deleteValue(key);
|
||||
} else {
|
||||
window?.localStorage.removeItem(key);
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -274,8 +274,7 @@ export class Translator {
|
||||
data, datalist, embed, head, iframe, input, noscript, map,
|
||||
object, option, param, picture, progress,
|
||||
select, script, style, track, textarea, template,
|
||||
video, wbr, .notranslate, [contenteditable], [translate='no'],
|
||||
${Translator.KISS_IGNORE_SELECTOR}`;
|
||||
video, wbr, .notranslate, [contenteditable='true'], [translate='no']`;
|
||||
|
||||
#setting; // 设置选项
|
||||
#rule; // 规则
|
||||
@@ -322,11 +321,15 @@ export class Translator {
|
||||
|
||||
// 忽略元素
|
||||
get #ignoreSelector() {
|
||||
if (this.#rule.isPlainText) {
|
||||
return Translator.KISS_IGNORE_SELECTOR;
|
||||
}
|
||||
|
||||
if (this.#rule.autoScan === "false") {
|
||||
return `${Translator.KISS_IGNORE_SELECTOR}, ${this.#rule.ignoreSelector}`;
|
||||
}
|
||||
|
||||
return `${Translator.BUILTIN_IGNORE_SELECTOR}, ${this.#rule.ignoreSelector}`;
|
||||
return `${Translator.KISS_IGNORE_SELECTOR}, ${Translator.BUILTIN_IGNORE_SELECTOR}, ${this.#rule.ignoreSelector}`;
|
||||
}
|
||||
|
||||
// 接口参数
|
||||
@@ -353,7 +356,7 @@ export class Translator {
|
||||
|
||||
constructor({ rule = {}, setting = {}, favWords = [] }) {
|
||||
this.#setting = { ...Translator.DEFAULT_OPTIONS, ...setting };
|
||||
this.#rule = { ...Translator.DEFAULT_RULE, ...rule };
|
||||
this.#rule = { ...Translator.DEFAULT_RULE, ...rule, isPlainText: false };
|
||||
this.#favWords = favWords;
|
||||
this.#apisMap = new Map(
|
||||
this.#setting.transApis.map((api) => [api.apiSlug, api])
|
||||
@@ -413,6 +416,19 @@ export class Translator {
|
||||
// 注入JS/CSS
|
||||
this.#initInjector();
|
||||
|
||||
// 纯文本预处理
|
||||
if (this.#rule.isPlainText) {
|
||||
document
|
||||
.querySelectorAll("pre")
|
||||
.forEach(
|
||||
(pre) =>
|
||||
(pre.innerHTML = pre.innerHTML?.replace(
|
||||
/(?:\r\n|\r|\n)/g,
|
||||
"<br />"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
// 查找根节点并扫描
|
||||
document
|
||||
.querySelectorAll(this.#rule.rootsSelector || "body")
|
||||
@@ -1786,7 +1802,11 @@ export class Translator {
|
||||
this.#rule[key] !== newRule[key]
|
||||
) {
|
||||
this.#rule[key] = newRule[key];
|
||||
if (key === "autoScan" || key === "hasShadowroot") {
|
||||
if (
|
||||
key === "autoScan" ||
|
||||
key === "hasShadowroot" ||
|
||||
key === "isPlainText"
|
||||
) {
|
||||
needsRescan = true;
|
||||
} else {
|
||||
hasChanged = true;
|
||||
|
||||
@@ -246,6 +246,8 @@ export default class TranslatorManager {
|
||||
}
|
||||
|
||||
#processActions({ action, args } = {}, fromExt = false) {
|
||||
if (!action) return;
|
||||
|
||||
if (!fromExt) {
|
||||
sendIframeMsg(action, args);
|
||||
}
|
||||
|
||||
@@ -59,14 +59,21 @@ export const sleep = (delay) =>
|
||||
*/
|
||||
export const debounce = (func, delay = 200) => {
|
||||
let timer = null;
|
||||
return (...args) => {
|
||||
|
||||
const debouncedFunc = (...args) => {
|
||||
timer && clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
func(...args);
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}, delay);
|
||||
};
|
||||
|
||||
debouncedFunc.cancel = () => {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
};
|
||||
|
||||
return debouncedFunc;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
OPT_TRANS_MICROSOFT,
|
||||
MSG_MENUS_PROGRESSED,
|
||||
MSG_MENUS_UPDATEFORM,
|
||||
OPT_LANGS_SPEC_DEFAULT,
|
||||
} from "../config";
|
||||
import { sleep, genEventName, downloadBlobFile } from "../libs/utils.js";
|
||||
import { createLogoSVG } from "../libs/svg.js";
|
||||
@@ -414,6 +415,20 @@ class YouTubeCaptionProvider {
|
||||
return [];
|
||||
}
|
||||
|
||||
#getFromLang(lang) {
|
||||
if (lang === "zh") {
|
||||
return "zh-CN";
|
||||
}
|
||||
|
||||
return (
|
||||
OPT_LANGS_SPEC_DEFAULT.get(lang) ||
|
||||
OPT_LANGS_SPEC_DEFAULT.get(lang.slice(0, 2)) ||
|
||||
OPT_LANGS_TO_CODE[OPT_TRANS_MICROSOFT].get(lang) ||
|
||||
OPT_LANGS_TO_CODE[OPT_TRANS_MICROSOFT].get(lang.slice(0, 2)) ||
|
||||
"auto"
|
||||
);
|
||||
}
|
||||
|
||||
async #handleInterceptedRequest(url, responseText) {
|
||||
const videoId = this.#videoId;
|
||||
if (!videoId) {
|
||||
@@ -462,16 +477,14 @@ class YouTubeCaptionProvider {
|
||||
}
|
||||
|
||||
const lang = potUrl.searchParams.get("lang");
|
||||
const fromLang =
|
||||
OPT_LANGS_TO_CODE[OPT_TRANS_MICROSOFT].get(lang) ||
|
||||
OPT_LANGS_TO_CODE[OPT_TRANS_MICROSOFT].get(lang.slice(0, 2)) ||
|
||||
"auto";
|
||||
const fromLang = this.#getFromLang(lang);
|
||||
|
||||
logger.debug(
|
||||
`Youtube Provider: fromLang: ${fromLang}, toLang: ${toLang}`
|
||||
`Youtube Provider: lang: ${lang}, fromLang: ${fromLang}, toLang: ${toLang}`
|
||||
);
|
||||
if (this.#isSameLang(fromLang, toLang)) {
|
||||
logger.debug("Youtube Provider: skip same lang", fromLang, toLang);
|
||||
this.#showNotification(this.#i18n("subtitle_same_lang"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -673,8 +686,13 @@ class YouTubeCaptionProvider {
|
||||
|
||||
if (noSpaceLanguages.some((l) => lang?.startsWith(l))) {
|
||||
const subtitles = [];
|
||||
|
||||
if (this.#isQualityPoor(flatEvents, 5, 0.5)) {
|
||||
return flatEvents;
|
||||
}
|
||||
|
||||
let currentLine = null;
|
||||
const MAX_LENGTH = 100;
|
||||
const MAX_LENGTH = 30;
|
||||
|
||||
for (const segment of flatEvents) {
|
||||
if (segment.text) {
|
||||
|
||||
@@ -99,7 +99,7 @@ export default function Options() {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingProvider>
|
||||
<SettingProvider isSettingPage={true}>
|
||||
<ThemeProvider>
|
||||
<AlertProvider>
|
||||
<ConfirmProvider>
|
||||
|
||||
@@ -112,7 +112,10 @@ export default function PopupCont({
|
||||
|
||||
const handleChange = async (e) => {
|
||||
try {
|
||||
const { name, value } = e.target;
|
||||
let { name, value, checked } = e.target;
|
||||
if (name === "isPlainText") {
|
||||
value = checked;
|
||||
}
|
||||
setRule((pre) => ({ ...pre, [name]: value }));
|
||||
|
||||
if (!processActions) {
|
||||
@@ -204,6 +207,7 @@ export default function PopupCont({
|
||||
transOnly,
|
||||
hasRichText,
|
||||
hasShadowroot,
|
||||
isPlainText = false,
|
||||
} = rule;
|
||||
|
||||
return (
|
||||
@@ -322,75 +326,97 @@ export default function PopupCont({
|
||||
label={i18n("input_translate")}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
size="small"
|
||||
name="isPlainText"
|
||||
value={!isPlainText}
|
||||
checked={isPlainText}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
}
|
||||
label={i18n("plain_text_translate")}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
size="small"
|
||||
value={apiSlug}
|
||||
name="apiSlug"
|
||||
label={i18n("translate_service")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{optApis.map(({ key, name }) => (
|
||||
<MenuItem key={key} value={key}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
size="small"
|
||||
value={fromLang}
|
||||
name="fromLang"
|
||||
label={i18n("from_lang")}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
>
|
||||
{OPT_LANGS_FROM.map(([lang, name]) => (
|
||||
<MenuItem key={lang} value={lang}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
size="small"
|
||||
value={fromLang}
|
||||
name="fromLang"
|
||||
label={i18n("from_lang")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{OPT_LANGS_FROM.map(([lang, name]) => (
|
||||
<MenuItem key={lang} value={lang}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
size="small"
|
||||
value={toLang}
|
||||
name="toLang"
|
||||
label={i18n("to_lang")}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
>
|
||||
{OPT_LANGS_TO.map(([lang, name]) => (
|
||||
<MenuItem key={lang} value={lang}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Stack>
|
||||
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
size="small"
|
||||
value={toLang}
|
||||
name="toLang"
|
||||
label={i18n("to_lang")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{OPT_LANGS_TO.map(([lang, name]) => (
|
||||
<MenuItem key={lang} value={lang}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
size="small"
|
||||
value={apiSlug}
|
||||
name="apiSlug"
|
||||
label={i18n("translate_service")}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
>
|
||||
{optApis.map(({ key, name }) => (
|
||||
<MenuItem key={key} value={key}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
size="small"
|
||||
value={textStyle}
|
||||
name="textStyle"
|
||||
label={
|
||||
commands["toggleStyle"]
|
||||
? `${i18n("text_style_alt")}(${commands["toggleStyle"]})`
|
||||
: i18n("text_style_alt")
|
||||
}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{allTextStyles.map((item) => (
|
||||
<MenuItem key={item.styleSlug} value={item.styleSlug}>
|
||||
{item.styleName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
size="small"
|
||||
value={textStyle}
|
||||
name="textStyle"
|
||||
label={
|
||||
commands["toggleStyle"]
|
||||
? `${i18n("text_style_alt")}(${commands["toggleStyle"]})`
|
||||
: i18n("text_style_alt")
|
||||
}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
>
|
||||
{allTextStyles.map((item) => (
|
||||
<MenuItem key={item.styleSlug} value={item.styleSlug}>
|
||||
{item.styleName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
|
||||
Reference in New Issue
Block a user