UI optimization, more language support.

Former-commit-id: 3cbb50cd2d08d175609a91aad7e5aaa79e896b46
This commit is contained in:
Physton
2023-05-11 19:22:32 +08:00
parent fa245c5252
commit 1a570974a2
16 changed files with 1003 additions and 77 deletions

4
.gitignore vendored
View File

@@ -3,4 +3,6 @@
/index.php
/src/node_modules
/src/package-lock.json
/storage
/storage
/tests/tested.json
__pycache__

View File

@@ -1 +1 @@
5ad28c8d02d5d6fdd61554711bd952779427686b
611e2b7444b13bf5b7d8c0b33f5ebdf29c8acbe6

View File

@@ -1 +1 @@
a2f08f76a93bc09fe85ebfed51247a1f37ecd9f9
384f7026f90087a5f0e604c3aef95cd9f8bd9525

View File

@@ -1 +1 @@
23b01f3cc69f5cc760cde3fc373c354f95e9b721
c1262f4231e67498db0dec618bed72d30c2b2c24

View File

@@ -311,11 +311,13 @@ def translate(text, from_lang, to_lang, api, api_config = {}):
result['message'] = 'translate_api_not_support'
return result
result['translated_text'] = result['translated_text'].strip()
caches[cache_name] = result['translated_text']
result['success'] = True
return result
except Exception as e:
print(e)
# print(e)
result['message'] = str(e)
return result

View File

@@ -12,11 +12,16 @@
v-model:enable-tooltip="enableTooltip"
v-model:translate-api="translateApi"
:translate-api-config="translateApiConfig"
@click:translate-api="onTranslateApiClick"></physton-prompt>
@click:translate-api="onTranslateApiClick"
@click:select-language="onSelectLanguageClick"></physton-prompt>
</block>
<translate-setting ref="translateSetting" v-model:language-code="languageCode"
:translate-apis="translateApis" :languages="languages"
v-model:translate-api="translateApi"></translate-setting>
<select-language ref="selectLanguage" v-model:language-code="languageCode"
:translate-apis="translateApis"
:languages="languages"
v-model:translate-api="translateApi"></select-language>
<div class="physton-paste-popup" v-if="showPastePopup" @click="closePastePopup">
<div class="paste-popup-main" @click.stop>
@@ -42,10 +47,12 @@ import TranslateSetting from "@/components/translateSetting.vue";
import common from "@/utils/common";
import IconClose from "@/components/icons/iconClose.vue";
import IconLoading from "@/components/icons/iconLoading.vue";
import SelectLanguage from "@/components/selectLanguage.vue";
export default {
name: 'App',
components: {
SelectLanguage,
IconLoading,
IconClose,
TranslateSetting,
@@ -136,6 +143,8 @@ export default {
startWatchSave: false,
showSelectLanguage: false,
pasteBtn: null,
showPastePopup: false,
pasteTitle: '',
@@ -319,6 +328,9 @@ export default {
this.translateApiConfig = config
})
},
onSelectLanguageClick(e) {
this.$refs.selectLanguage.open(e)
},
onTranslateApiClick() {
this.$refs.translateSetting.open(this.translateApi)
},

View File

@@ -0,0 +1,27 @@
<template>
<div class="icon-svg" :style="{width: width + 'px', height: height + 'px'}">
<svg t="1683802752737" viewBox="0 0 1279 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="43331" :width="width" :height="height" :style="{fill: color}">
<path d="M663.736741 915.27126l56.30961 51.702461a32.864337 32.864337 0 0 1-44.382211 48.426266l-143.333555-131.457346 135.706162-141.285933a32.864337 32.864337 0 0 1 47.402454 45.457213l-59.02271 61.479857h258.358733a265.883744 265.883744 0 0 0 265.525411-265.576601 265.730173 265.730173 0 0 0-232.353931-263.477789l-23.342893-2.917861-4.760722-23.03575a287.58854 287.58854 0 0 0-280.882577-228.719401 287.076634 287.076634 0 0 0-286.513538 297.519507l1.330954 37.72744-37.625058-3.890483a227.030113 227.030113 0 0 0-250.526578 225.545587 227.030113 227.030113 0 0 0 226.77416 226.77416h79.038218a32.864337 32.864337 0 0 1 0 65.728673H292.451643A292.707595 292.707595 0 0 1 0 622.819618 292.809977 292.809977 0 0 1 287.179015 330.367975a352.446974 352.446974 0 0 1 691.584403-71.359634 331.202893 331.202893 0 0 1-63.988194 656.262919h-251.038483z m-360.483891-250.321815l105.964449-275.917093h39.365537l112.977556 275.917093h-41.617921l-32.19886-83.542986H372.308909l-30.304809 83.594176h-38.75125z m79.652504-113.284699h93.525145L447.661406 475.237247c-8.804776-23.189322-15.357167-42.283399-19.605982-57.231041-3.583339 17.916694-8.548823 35.526245-14.845261 52.726272l-30.304809 80.881077z m207.833655 113.335889V388.981161h104.070399c18.326219 0 32.352431 0.870239 41.976255 2.661909 13.565497 2.252384 24.9298 6.552391 34.09291 12.90002 9.163109 6.296438 16.534549 15.203595 22.16551 26.619089 5.630961 11.773828 8.497632 24.622657 8.344061 37.676248 0 23.445274-7.473821 43.30721-22.421463 59.534616-14.896452 16.278597-41.873874 24.417895-80.881078 24.417895h-70.847728v112.209697h-36.498866z m36.498866-144.76689h71.308443c23.598846 0 40.338158-4.402388 50.269126-13.207163 9.930968-8.753585 14.845261-21.141699 14.896451-37.061962a49.961982 49.961982 0 0 0-8.702394-29.690523 40.952444 40.952444 0 0 0-23.086941-16.227406c-6.142867-1.638098-17.50717-2.457147-34.09291-2.457146h-70.591775v98.6442z m226.876541 144.76689V388.981161h36.498866v276.019474h-36.498866z" p-id="43332"></path>
</svg>
</div>
</template>
<script>
export default {
name: 'IconApi',
props: {
width: {
type: Number,
default: 16,
},
height: {
type: Number,
default: 16,
},
color: {
type: String,
default: '#02b7fd',
},
},
}
</script>

View File

@@ -0,0 +1,27 @@
<template>
<div class="icon-svg" :style="{width: width + 'px', height: height + 'px'}">
<svg t="1683798392851" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="38484" :width="width" :height="height" :style="{fill: color}">
<path d="M496 0C222.048 0 0 222.048 0 496s222.048 496 496 496 496-222.048 496-496S769.952 0 496 0z m431.2 480H735.328c-1.76-70.88-14.368-138.592-36.736-200.576a522.688 522.688 0 0 0 119.552-70.304A429.856 429.856 0 0 1 927.2 480zM478.304 927.104c-53.184-44.288-97.792-101.792-130.432-168.576A495.904 495.904 0 0 1 480 736.8v190.368l-1.696-0.064z m35.328-862.208c60.352 50.24 109.6 117.536 142.912 196.032-45.632 15.584-94.112 24.64-144.576 26.24V64.8l1.664 0.096z m53.312 5.44a430.816 430.816 0 0 1 229.248 115.648 489.504 489.504 0 0 1-109.696 63.936c-29.12-69.024-69.984-130.048-119.552-179.584zM480 64.8v222.368a495.744 495.744 0 0 1-144.576-26.24c33.312-78.496 82.56-145.792 142.912-196.032L480 64.8zM305.472 249.952a490.72 490.72 0 0 1-109.664-63.936A430.72 430.72 0 0 1 425.056 70.368c-49.6 49.504-90.432 110.528-119.584 179.584z m17.888 40.48c49.344 17.12 101.92 27.104 156.64 28.768V480H288.672c1.728-67.008 13.6-131.04 34.688-189.568zM480 512v192.8a525.152 525.152 0 0 0-145.248 24.608A601.12 601.12 0 0 1 288.672 512H480z m-54.944 409.664A429.792 429.792 0 0 1 215.616 824a486.368 486.368 0 0 1 102.016-54.4 554.144 554.144 0 0 0 107.424 152.064zM512 927.2V736.832c45.824 1.472 90.24 8.64 132.128 21.728-32.672 66.784-77.248 124.288-130.432 168.576a31.2 31.2 0 0 0-1.696 0.064z m162.368-157.6A490.24 490.24 0 0 1 776.384 824a430.08 430.08 0 0 1-209.44 97.664A554.816 554.816 0 0 0 674.368 769.6z m-17.12-40.192A525.568 525.568 0 0 0 512 704.8V512h191.328a601.792 601.792 0 0 1-46.08 217.408zM512 480V319.2a526.08 526.08 0 0 0 156.64-28.736c21.056 58.528 32.928 122.56 34.688 189.568H512zM173.888 209.12a523.2 523.2 0 0 0 119.52 70.304C271.04 341.408 258.432 409.12 256.672 480H64.8a429.728 429.728 0 0 1 109.088-270.88zM64.8 512h191.872a631.168 631.168 0 0 0 48.096 228.384 519.36 519.36 0 0 0-113.216 61.792A430.816 430.816 0 0 1 64.8 512z m735.648 290.144A520.192 520.192 0 0 0 687.2 740.352a631.2 631.2 0 0 0 48.128-228.384H927.2a430.848 430.848 0 0 1-126.752 290.176z" p-id="38485"></path>
</svg>
</div>
</template>
<script>
export default {
name: 'IconI18n',
props: {
width: {
type: Number,
default: 16,
},
height: {
type: Number,
default: 16,
},
color: {
type: String,
default: '#02b7fd',
},
},
}
</script>

View File

@@ -5,82 +5,62 @@
<div class="prompt-header-title">{{ neg ? getLang('negative_prompt') : getLang('prompt') }}</div>
<div class="prompt-header-counter" v-show="counterText">({{ counterText }})</div>
<div class="prompt-header-extend">
<div class="extend-title">{{ getLang('local_language') }}{{ isEnglish ? '' : '/Language' }}:</div>
<div class="extend-content">
<select @change="$emit('update:languageCode', $event.target.value)" :value="languageCode">
<option v-for="item in languages" :key="item.code" :value="item.code"
:selected="item.code == languageCode"><!--{{ item.code }} - -->{{ item.name }}
</option>
</select>
<div class="extend-btn-group">
<div class="extend-btn-item" v-tooltip="'Language: ' + langName"
@click="$emit('click:selectLanguage', $event)">
<icon-i18n class="hover-scale-120" width="18" height="18" color="#d81e06"/>
</div>
<div v-if="translateApiItem.name && !isEnglish" class="extend-btn-item"
v-tooltip="getLang('translate_api') + ': ' + translateApiItem.name"
@click="$emit('click:translateApi', $event)">
<icon-api class="hover-scale-120" width="18" height="18" color="#d81e06"/>
</div>
</div>
</div>
</div>
<div class="prompt-header-extend" v-show="!isEnglish">
<div class="extend-title">{{ getLang('translate_api') }}:</div>
<div class="extend-content">
<div class="current-translate-api" v-if="translateApiItem.name" @click="$emit('click:translateApi')">{{ translateApiItem.name }}</div>
</div>
</div>
<div class="prompt-header-break"></div>
<!--<div class="prompt-header-break"></div>-->
<div class="prompt-header-extend">
<div class="extend-content">
<button type="button" class="lg secondary gradio-button tool svelte-1ipelgc hover-scale-120"
v-tooltip="getLang('copy_keywords_to_clipboard')" @click="onCopyAllTagsClick">
<icon-copy width="18" height="18" color="var(--body-text-color)"/>
</button>
</div>
</div>
<div class="prompt-header-extend">
<div class="extend-content">
<button type="button" class="lg secondary gradio-button tool svelte-1ipelgc hover-scale-120"
ref="historyButton"
v-tooltip="getLang('history')" @click.stop="onShowHistoryClick">
<icon-history width="18" height="18"/>
</button>
</div>
</div>
<div class="prompt-header-extend">
<div class="extend-content">
<button type="button" class="lg secondary gradio-button tool svelte-1ipelgc hover-scale-120"
ref="favoriteButton"
v-tooltip="getLang('favorite')" @click.stop="onShowFavoriteClick">
<icon-favorite width="18" height="18"/>
</button>
<div class="extend-btn-group">
<div class="extend-btn-item" ref="historyButton" v-tooltip="getLang('history')" @click.stop="onShowHistoryClick">
<icon-history class="hover-scale-120" width="18" height="18"/>
</div>
<div class="extend-btn-item" ref="favoriteButton"
v-tooltip="getLang('favorite')" @click.stop="onShowFavoriteClick">
<icon-favorite class="hover-scale-120" width="18" height="18"/>
</div>
</div>
</div>
</div>
<div class="prompt-header-extend" v-show="!isEnglish">
<div class="extend-content">
<button type="button" class="lg secondary gradio-button tool svelte-1ipelgc hover-scale-120"
v-tooltip="getLang('translate_keywords_to_local_language')"
@click="onTranslatesToLocalClick">
<icon-translate v-if="!loading['all_local']" width="18" height="18" color="#ff9900"/>
<icon-loading v-if="loading['all_local']" width="18" height="18"/>
</button>
</div>
</div>
<div class="prompt-header-extend" v-show="!isEnglish">
<div class="extend-content">
<button type="button" class="lg secondary gradio-button tool svelte-1ipelgc hover-scale-120"
v-tooltip="getLang('translate_all_keywords_to_english')"
@click="onTranslatesToEnglishClick">
<icon-english v-if="!loading['all_en']" width="18" height="18" color="#ff9900"/>
<icon-loading v-if="loading['all_en']" width="18" height="18"/>
</button>
<div class="extend-btn-group">
<div class="extend-btn-item" v-tooltip="getLang('translate_keywords_to_local_language')"
@click="onTranslatesToLocalClick">
<icon-translate class="hover-scale-120" v-if="!loading['all_local']" width="18" height="18" color="#ad6800"/>
<icon-loading class="hover-scale-120" v-if="loading['all_local']" width="18" height="18"/>
</div>
<div class="extend-btn-item" v-tooltip="getLang('translate_all_keywords_to_english')"
@click="onTranslatesToEnglishClick">
<icon-english class="hover-scale-120" v-if="!loading['all_en']" width="18" height="18" color="#ad6800"/>
<icon-loading class="hover-scale-120" v-if="loading['all_en']" width="18" height="18"/>
</div>
</div>
</div>
</div>
<div class="prompt-header-extend">
<div class="extend-content">
<button type="button" class="lg secondary gradio-button tool svelte-1ipelgc hover-scale-120"
v-tooltip="getLang('delete_all_keywords')"
@click="onDeleteAllTagsClick">
<icon-remove width="18" height="18" color="#d81e06"/>
</button>
</div>
</div>
<div class="prompt-header-extend">
<div class="extend-content">
<a href="https://github.com/Physton/sd-webui-prompt-all-in-one" target="_blank" class="lg secondary gradio-button tool svelte-1ipelgc hover-scale-120">
<icon-github width="18" height="18" color="#000"/>
</a>
<div class="extend-btn-group">
<div class="extend-btn-item" v-tooltip="getLang('copy_keywords_to_clipboard')"
@click="onCopyAllTagsClick">
<icon-copy class="hover-scale-120" width="18" height="18" color="var(--body-text-color)"/>
</div>
<div class="extend-btn-item" v-tooltip="getLang('delete_all_keywords')"
@click="onDeleteAllTagsClick">
<icon-remove class="hover-scale-120" width="18" height="18" color="#d81e06"/>
</div>
</div>
</div>
</div>
<div class="prompt-header-extend" v-show="!isEnglish">
@@ -250,10 +230,14 @@ import IconInput from "@/components/icons/iconInput.vue";
import IconRemove from "@/components/icons/iconRemove.vue";
import IconTooltip from "@/components/icons/iconTooltip.vue";
import IconGithub from "@/components/icons/iconGithub.vue";
import IconI18n from "@/components/icons/iconI18n.vue";
import IconApi from "@/components/icons/iconApi.vue";
export default {
name: 'PhystonPrompt',
components: {
IconApi,
IconI18n,
IconGithub,
IconTooltip,
IconRemove,
@@ -307,7 +291,7 @@ export default {
default: '',
},
},
emits: ['update:languageCode', 'update:autoTranslateToEnglish', 'update:autoTranslateToLocal', 'update:hideDefaultInput', 'update:enableTooltip', 'update:translateApi', 'click:translateApi'],
emits: ['update:languageCode', 'update:autoTranslateToEnglish', 'update:autoTranslateToLocal', 'update:hideDefaultInput', 'update:enableTooltip', 'update:translateApi', 'click:translateApi', 'click:selectLanguage'],
data() {
return {
prompt: '',
@@ -889,7 +873,7 @@ export default {
}
.extend-content {
select, .current-translate-api {
select, .select-btn {
padding: 0 10px 0 5px;
font-size: 0.8rem;
appearance: auto;
@@ -904,7 +888,7 @@ export default {
}
}
.current-translate-api {
.select-btn {
cursor: pointer;
padding: 0 10px;
@@ -914,6 +898,32 @@ export default {
}
}
.extend-btn-group{
display: flex;
justify-content: center;
align-items: center;
color: var(--button-secondary-text-color);
background: var(--button-secondary-background-fill);
border: 1px solid var(--button-secondary-border-color);
padding: 0;
border-radius: 4px;
.extend-btn-item {
cursor: pointer;
border-left: 1px solid var(--button-secondary-border-color);
height: 26px;
width: 30px;
display: flex;
justify-content: center;
align-items: center;
&:first-child {
border-left: 0;
margin-left: 0;
}
}
}
.gradio-button, a {
height: 26px !important;
min-height: 26px !important;

View File

@@ -0,0 +1,126 @@
<template>
<div class="physton-prompt-select-language" v-if="isOpen" @click="close">
<div class="language-main" @click.stop>
<div class="language-close" @click="close">
<icon-close width="24" height="24" />
</div>
<div class="language-list" @click.stop>
<div v-for="item in languages" :key="item.code"
:class="['language-item', item.code == languageCode ? 'selected' : '']" ref="items"
@click="onLanguageClick(item)">
{{ item.code }} - {{ item.name }}
</div>
</div>
</div>
</div>
</template>
<script>
import LanguageMixin from "@/mixins/languageMixin";
import IconClose from "@/components/icons/iconClose.vue";
export default {
name: 'SelectLanguage',
components: {IconClose},
mixins: [LanguageMixin],
props: {},
data() {
return {
isOpen: false,
}
},
computed: {},
mounted() {
},
methods: {
open() {
this.isOpen = true
this.$nextTick(() => {
this.scrollToSelectedItem()
})
},
close() {
this.isOpen = false
},
onLanguageClick(item) {
this.$emit('update:languageCode', item.code)
this.close()
},
scrollToSelectedItem() {
const items = this.$refs.items
for (let i = 0; i < items.length; i++) {
if (items[i].classList.contains('selected')) {
items[i].scrollIntoView({
behavior: 'smooth',
block: 'center'
})
break
}
}
}
},
}
</script>
<style lang="less">
.physton-prompt-select-language {
position: fixed;
z-index: 2000;
margin-top: 5px;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.5);
.language-main {
height: 70%;
position: relative;
.language-close {
display: block;
padding: 4px;
position: absolute;
right: -14px;
top: -14px;
background: #ffffffe6;
border-radius: 50%;
box-shadow: 0px 1px 5px 0px #d81e06;
cursor: pointer;
z-index: 1;
&:hover {
background: #d81e06;
}
}
.language-list {
height: 100%;
overflow: hidden;
overflow-y: scroll;
display: block;
position: relative;
box-shadow: 0 0 3px #4a54ff;
border-radius: 6px 6px 4px 4px;
background-color: #1e1e1ee6;
transition: height .1s ease-in-out, width .1s ease-in-out;
position: relative;
.language-item {
font-size: 14px;
color: #fff;
font-size: 14px;
padding: 10px;
cursor: pointer;
&:hover, &.selected {
background: center center #4A54FF;
background-image: linear-gradient(315deg, #6772FF 0, #00F9E5 100%);
background-size: 104% 104%;
}
}
}
}
}
</style>

View File

@@ -19,8 +19,9 @@ onUiLoaded(() => {
app.mixin(CommonMixin)
app.directive('tooltip', {
mounted(el, binding) {
app.config.globalProperties.$tippyList.push(tippy(el, {
content: binding.value,
// data-tippy-content
el.setAttribute('data-tippy-content', binding.value)
const instance = tippy(el, {
placement: 'bottom',
theme: 'light',
allowHTML: true,
@@ -30,8 +31,14 @@ onUiLoaded(() => {
instance.disable()
}
},
}))
})
el.$tippyInstance = instance
app.config.globalProperties.$tippyList.push(instance)
},
updated(el, binding) {
el.setAttribute('data-tippy-content', binding.value)
el.$tippyInstance.setContent(binding.value)
}
})
app.mount('#physton-prompt-all-in-one')

View File

@@ -26,6 +26,17 @@ export default {
data() {
return {}
},
computed: {
langName() {
for (const key in this.languages) {
const item = this.languages[key]
if (item.code === this.languageCode) {
return item.name
}
}
return item.name
}
},
methods: {
getLang(key) {
return common.getLang(key, this.languageCode, this.languages)

File diff suppressed because one or more lines are too long

82
tests/translate.py Normal file
View File

@@ -0,0 +1,82 @@
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import time
import json
from scripts.translate import translate
from scripts.get_i18n import get_i18n
from scripts.get_translate_apis import get_translate_apis
from scripts.storage import storage
i18n = get_i18n()
st = storage()
text = 'Hello World, I am a boy'
tested_file = os.path.join(os.path.dirname(__file__), 'tested.json')
tested = []
if os.path.exists(tested_file):
with open(tested_file, 'r') as f:
tested = json.load(f)
def is_tested(api_key, from_lang, to_lang):
for item in tested:
if item['api'] == api_key and item['from'] == from_lang and item['to'] == to_lang:
return item['translated_text']
return False
def add_tested(api_key, from_lang, to_lang, translated_text):
tested.append({
'api': api_key,
'from': from_lang,
'to': to_lang,
'translated_text': translated_text
})
with open(tested_file, 'w') as f:
json.dump(tested, f, indent=4, ensure_ascii=False)
def test_api(api):
print(f"开始测试 {api['name']}")
config_name = 'translate_api.' + api['key']
config = st.get(config_name)
if not config:
config = {}
for lang_code in api['support']:
if lang_code == 'en_US' or lang_code == 'en_GB':
continue
if not api['support'][lang_code]:
continue
if api['key'] == 'openai' or api['key'] == 'deepl':
continue
translated_text = is_tested(api['key'], 'en_US', lang_code)
if not translated_text:
print(f" 测试 en_US -> {lang_code}", end=' ')
result = translate(text, from_lang='en_US', to_lang=lang_code, api=api['key'],api_config=config)
if not result['success']:
print(f"失败: {result['message']}")
time.sleep(0.5)
# raise Exception(f"测试 {api['name']} 失败:{result['message']}")
continue
add_tested(api['key'], 'en_US', lang_code, result['translated_text'])
translated_text = result['translated_text']
print(f" 结果: {translated_text}")
time.sleep(0.5)
if not is_tested(api['key'], lang_code, 'en_US'):
print(f" 测试 {lang_code} -> en_US", end=' ')
result = translate(translated_text, from_lang=lang_code, to_lang='en_US', api=api['key'],api_config=config)
if not result['success']:
print(f"失败: {result['message']}")
time.sleep(0.5)
# raise Exception(f"测试 {api['name']} 失败:{result['message']}")
continue
translated_text = result['translated_text']
add_tested(api['key'], lang_code, 'en_US', translated_text)
print(f" 结果: {translated_text}")
time.sleep(0.5)
apis = get_translate_apis()
for group in apis['apis']:
for api in group['children']:
test_api(api)

620
translate_apis.backup.json Normal file
View File

@@ -0,0 +1,620 @@
[
{
"key": "deepl_free",
"name": "[Free] DeepL, Germany",
"type": "translators",
"translator": "deepl",
"support": {
"bg_BG": "bg",
"cs_CZ": "cs",
"da_DK": "da",
"de_DE": "de",
"el_GR": "el",
"en_GB": "en",
"en_US": "en",
"es_ES": "es",
"et_EE": "et",
"fi_FI": "fi",
"fr_FR": "fr",
"hu_HU": "hu",
"id_ID": "id",
"it_IT": "it",
"ja_JP": "ja",
"ko_KR": "ko",
"lt_LT": "lt",
"lv_LV": "lv",
"no_NO": "nb",
"nl_NL": "nl",
"pl_PL": "pl",
"pt_BR": "pt",
"pt_PT": "pt",
"ro_RO": "ro",
"ru_RU": "ru",
"sk_SK": "sk",
"sl_SI": "sl",
"sv_SE": "sv",
"tr_TR": "tr",
"uk_UA": "uk",
"zh_CN": "zh"
},
"help": [
{
"title": "DeepL - Deepl, Germany",
"url": "https://www.deepl.com/translator"
},
{
"title": "[github]translators",
"url": "https://github.com/UlionTse/translators"
}
]
},
{
"key": "myMemory_free",
"name": "[Free] MyMemory, Italy",
"type": "translators",
"translator": "myMemory",
"support": {
"zh_CN": "zh-CN",
"zh_HK": "zh-TW",
"zh_TW": "zh-TW",
"en_US": "en-GB",
"en_GB": "en-GB",
"af_ZA": "af-ZA",
"sq_AL": "sq-AL",
"am_ET": "am-ET",
"ar_SA": "ar-SA",
"hy_AM": "hy-AM",
"as_IN": false,
"az_Latn_AZ": "az-AZ",
"bn_BD": "bn-IN",
"ba_RU": false,
"eu_ES": "eu-ES",
"bs_Latn_BA": "bs-BA",
"bg_BG": "bg-BG",
"ca_ES": "ca-ES",
"hr_HR": "hr-HR",
"cs_CZ": "cs-CZ",
"da_DK": "da-DK",
"prs_AF": false,
"dv_MV": "dv-MV",
"nl_NL": "nl-NL",
"et_EE": "et-EE",
"fo_FO": "fo-FO",
"fj_FJ": false,
"fil_PH": false,
"fi_FI": "fi-FI",
"fr_FR": "fr-FR",
"fr_CA": "fr-FR",
"gl_ES": "gl-ES",
"ka_GE": "ka-GE",
"de_DE": "de-DE",
"el_GR": "el-GR",
"gu_IN": "gu-IN",
"ht_HT": "ht-HT",
"he_IL": "he-IL",
"hi_IN": "hi-IN",
"mww_Latn_US": false,
"hu_HU": "hu-HU",
"is_IS": "is-IS",
"id_ID": "id-ID",
"ikt_CA": false,
"iu_CA": false,
"iu_Latn_CA": false,
"ga_IE": "ga-IE",
"it_IT": "it-IT",
"ja_JP": "ja-JP",
"kn_IN": "kn-IN",
"kk_KZ": "kk-KZ",
"km_KH": "km-KM",
"tlh_Latn": false,
"tlh_Piqd": false,
"ko_KR": "ko-KR",
"ku_Arab_IQ": "ckb-IQ",
"ku_Latn_TR": "ku-TR",
"ky_KG": "ky-KG",
"lo_LA": "lo-LA",
"lv_LV": "lv-LV",
"lt_LT": "lt-LT",
"mk_MK": "mk-MK",
"mg_MG": "mg-MG",
"ms_Latn_MY": "ms-MY",
"ml_IN": false,
"mt_MT": "mt-MT",
"mi_NZ": "mi-NZ",
"mr_IN": false,
"mn_Cyrl_MN": "mn-MN",
"mn_Mong_CN": "mn-MN",
"my_MM": "my-MM",
"ne_NP": "ne-NP",
"no_NO": "no-NO",
"or_IN": false,
"ps_AF": "ps-PK",
"fa_IR": "fa-IR",
"pl_PL": "pl-PL",
"pt_BR": "pt-PT",
"pt_PT": "pt-PT",
"pa_Guru_IN": "pa-IN",
"otq_Latn_MX": false,
"ro_RO": "ro-RO",
"ru_RU": "ru-RU",
"sm_Latn_WS": "sm-WS",
"sr_Cyrl_RS": "sr-RS",
"sr_Latn_RS": "sr-RS",
"sk_SK": "sk-SK",
"sl_SI": "sl-SI",
"so_SO": "so-SO",
"es_ES": "es-ES",
"sw_KE": "sw-SZ",
"sv_SE": "sv-SE",
"ty_PF": false,
"ta_IN": "ta-LK",
"tt_Latn_RU": false,
"te_IN": "te-IN",
"th_TH": "th-TH",
"bo_CN": "bo-CN",
"ti_ET": "ti-TI",
"to_TO": "to-TO",
"tr_TR": "tr-TR",
"uk_UA": "uk-UA",
"ur_PK": "ur-PK",
"ug_Arab_CN": false,
"uz_Latn_UZ": "uz-UZ",
"vi_VN": "vi-VN",
"cy_GB": "cy-GB",
"yua_MX": false,
"zu_ZA": "zu-ZA"
},
"help": [
{
"title": "MyMemory - Translated, Italy",
"url": "https://mymemory.translated.net/"
},
{
"title": "[github]translators",
"url": "https://github.com/UlionTse/translators"
}
]
},
{
"key": "yandex_free",
"name": "[Free] Yandex, Russia",
"type": "translators",
"translator": "yandex",
"support": {
"zh_CN": "zh",
"zh_HK": false,
"zh_TW": false,
"en_US": "en",
"en_GB": "en",
"af_ZA": "af",
"sq_AL": "sq",
"am_ET": "am",
"ar_SA": "ar",
"hy_AM": "hy",
"as_IN": false,
"az_Latn_AZ": "az",
"bn_BD": "bn",
"ba_RU": "ba",
"eu_ES": "eu",
"bs_Latn_BA": "bs",
"bg_BG": "bg",
"ca_ES": "ca",
"hr_HR": "hr",
"cs_CZ": "cs",
"da_DK": "da",
"prs_AF": false,
"dv_MV": false,
"nl_NL": "nl",
"et_EE": "et",
"fo_FO": false,
"fj_FJ": false,
"fil_PH": false,
"fi_FI": "fi",
"fr_FR": "fr",
"fr_CA": false,
"gl_ES": "gl",
"ka_GE": "ka",
"de_DE": "de",
"el_GR": "el",
"gu_IN": "gu",
"ht_HT": "ht",
"he_IL": "he",
"hi_IN": "hi",
"mww_Latn_US": false,
"hu_HU": "hu",
"is_IS": "is",
"id_ID": "id",
"ikt_CA": false,
"iu_CA": false,
"iu_Latn_CA": false,
"ga_IE": "ga",
"it_IT": "it",
"ja_JP": "ja",
"kn_IN": "kn",
"kk_KZ": "kk",
"km_KH": "km",
"tlh_Latn": false,
"tlh_Piqd": false,
"ko_KR": "ko",
"ku_Arab_IQ": false,
"ku_Latn_TR": false,
"ky_KG": "ky",
"lo_LA": "lo",
"lv_LV": "lv",
"lt_LT": "lt",
"mk_MK": "mk",
"mg_MG": "mg",
"ms_Latn_MY": "ms",
"ml_IN": "ml",
"mt_MT": "mt",
"mi_NZ": "mi",
"mr_IN": "mr",
"mn_Cyrl_MN": "mn",
"mn_Mong_CN": "mn",
"my_MM": "my",
"ne_NP": "ne",
"no_NO": "no",
"or_IN": false,
"ps_AF": false,
"fa_IR": "fa",
"pl_PL": "pl",
"pt_BR": "pt-BR",
"pt_PT": "pt",
"pa_Guru_IN": "pa",
"otq_Latn_MX": false,
"ro_RO": "ro",
"ru_RU": "ru",
"sm_Latn_WS": false,
"sr_Cyrl_RS": "sr",
"sr_Latn_RS": "sr-Latn",
"sk_SK": "sk",
"sl_SI": "sl",
"so_SO": false,
"es_ES": "es",
"sw_KE": "sw",
"sv_SE": "sv",
"ty_PF": false,
"ta_IN": "ta",
"tt_Latn_RU": "tt",
"te_IN": "te",
"th_TH": "th",
"bo_CN": false,
"ti_ET": false,
"to_TO": false,
"tr_TR": "tr",
"uk_UA": "uk",
"ur_PK": "ur",
"ug_Arab_CN": false,
"uz_Latn_UZ": "uz",
"vi_VN": "vi",
"cy_GB": "cy",
"yua_MX": false,
"zu_ZA": "zu"
},
"help": [
{
"title": "Yandex - Yandex, Russia",
"url": "https://translate.yandex.com/"
},
{
"title": "[github]translators",
"url": "https://github.com/UlionTse/translators"
}
]
},
{
"key": "niutrans_free",
"name": "[Free] Niutrans / 小牛翻译, China",
"type": "translators",
"translator": "niutrans",
"support": {
"zh_CN": "zh",
"zh_HK": "cht",
"zh_TW": "cht",
"en_US": "en",
"en_GB": "en",
"af_ZA": "af",
"sq_AL": "sq",
"am_ET": "am",
"ar_SA": "ar",
"hy_AM": "hy",
"as_IN": false,
"az_Latn_AZ": "az",
"bn_BD": "bn",
"ba_RU": "ba",
"eu_ES": "eu",
"bs_Latn_BA": "bs",
"bg_BG": "bg",
"ca_ES": "ca",
"hr_HR": "hr",
"cs_CZ": "cs",
"da_DK": "da",
"prs_AF": false,
"dv_MV": "dv",
"nl_NL": "nl",
"et_EE": "et",
"fo_FO": "fo",
"fj_FJ": "fj",
"fil_PH": "fil",
"fi_FI": "fi",
"fr_FR": "fr",
"fr_CA": "fr",
"gl_ES": "gl",
"ka_GE": "ka",
"de_DE": "de",
"el_GR": "el",
"gu_IN": "gu",
"ht_HT": "ht",
"he_IL": "he",
"hi_IN": "hi",
"mww_Latn_US": "mww",
"hu_HU": "hu",
"is_IS": "is",
"id_ID": "id",
"ikt_CA": false,
"iu_CA": false,
"iu_Latn_CA": false,
"ga_IE": "ga",
"it_IT": "it",
"ja_JP": "ja",
"kn_IN": "kn",
"kk_KZ": "kk",
"km_KH": "km",
"tlh_Latn": false,
"tlh_Piqd": false,
"ko_KR": "ko",
"ku_Arab_IQ": "ku",
"ku_Latn_TR": false,
"ky_KG": "ky",
"lo_LA": "lo",
"lv_LV": "lv",
"lt_LT": "lt",
"mk_MK": "mk",
"mg_MG": "mg",
"ms_Latn_MY": "ms",
"ml_IN": "ml",
"mt_MT": "mt",
"mi_NZ": "mi",
"mr_IN": "mr",
"mn_Cyrl_MN": "mn",
"mn_Mong_CN": "mn",
"my_MM": "my",
"ne_NP": "ne",
"no_NO": "no",
"or_IN": "or",
"ps_AF": "ps",
"fa_IR": "fa",
"pl_PL": "pl",
"pt_BR": "pt",
"pt_PT": "pt",
"pa_Guru_IN": "pa",
"otq_Latn_MX": "otq",
"ro_RO": "ro",
"ru_RU": "ru",
"sm_Latn_WS": "sm",
"sr_Cyrl_RS": "sr",
"sr_Latn_RS": "sr",
"sk_SK": "sk",
"sl_SI": "sl",
"so_SO": "so",
"es_ES": "es",
"sw_KE": "sw",
"sv_SE": "sv",
"ty_PF": "ty",
"ta_IN": "ta",
"tt_Latn_RU": "tt",
"te_IN": "te",
"th_TH": "th",
"bo_CN": false,
"ti_ET": "ti",
"to_TO": "to",
"tr_TR": "tr",
"uk_UA": "uk",
"ur_PK": "ur",
"ug_Arab_CN": false,
"uz_Latn_UZ": "uz",
"vi_VN": "vi",
"cy_GB": "cy",
"yua_MX": "yua",
"zu_ZA": "zu"
},
"help": [
{
"title": "Niutrans/小牛翻译 - Northeastern University / Niutrans, China",
"url": "https://niutrans.com/trans"
},
{
"title": "[github]translators",
"url": "https://github.com/UlionTse/translators"
}
]
},
{
"key": "qqFanyi_free",
"name": "[Free] QQFanyi / 腾讯翻译君, China",
"type": "translators",
"translator": "qqFanyi",
"support": {
"zh_CN": "zh",
"fr_FR": "fr",
"es_ES": "es",
"it_IT": "it",
"de_DE": "de",
"tr_TR": "tr",
"ru_RU": "ru",
"pt_PT": "pt",
"vi_VN": "vi",
"id_ID": "id",
"th_TH": "th",
"ms_Latn_MY": "ms",
"ar_SA": "ar",
"hi_IN": "hi"
},
"help": [
{
"title": "QQFanyi/腾讯翻译君 - Tencent, China",
"url": "https://fanyi.qq.com/"
},
{
"title": "[github]translators",
"url": "https://github.com/UlionTse/translators"
}
]
},
{
"key": "volcEngine_free",
"name": "[Free] VolcEngine / 火山翻译, China",
"type": "translators",
"translator": "volcEngine",
"support": {
"zh_CN": "zh",
"zh_HK": "zh-Hant",
"zh_TW": "zh-Hant",
"en_US": "en",
"en_GB": "en",
"af_ZA": "af",
"sq_AL": "sq",
"am_ET": "am",
"ar_SA": "ar",
"hy_AM": "hy",
"as_IN": false,
"az_Latn_AZ": "az",
"bn_BD": "bn",
"ba_RU": "ba",
"eu_ES": false,
"bs_Latn_BA": "bs",
"bg_BG": "bg",
"ca_ES": "ca",
"hr_HR": "hr",
"cs_CZ": "cs",
"da_DK": "da",
"prs_AF": false,
"dv_MV": false,
"nl_NL": "nl",
"et_EE": "et",
"fo_FO": false,
"fj_FJ": "fj",
"fil_PH": false,
"fi_FI": "fi",
"fr_FR": "fr",
"fr_CA": "fr",
"gl_ES": "gl",
"ka_GE": "ka",
"de_DE": "de",
"el_GR": "el",
"gu_IN": "gu",
"ht_HT": "ht",
"he_IL": "he",
"hi_IN": "hi",
"mww_Latn_US": false,
"hu_HU": "hu",
"is_IS": false,
"id_ID": "id",
"ikt_CA": false,
"iu_CA": "iu",
"iu_Latn_CA": "iu",
"ga_IE": false,
"it_IT": "it",
"ja_JP": "ja",
"kn_IN": "kn",
"kk_KZ": false,
"km_KH": "km",
"tlh_Latn": false,
"tlh_Piqd": false,
"ko_KR": "ko",
"ku_Arab_IQ": false,
"ku_Latn_TR": "kmr",
"ky_KG": false,
"lo_LA": "lo",
"lv_LV": "lv",
"lt_LT": "lt",
"mk_MK": "mk",
"mg_MG": false,
"ms_Latn_MY": "ms",
"ml_IN": "ml",
"mt_MT": false,
"mi_NZ": false,
"mr_IN": "mr",
"mn_Cyrl_MN": "mn",
"mn_Mong_CN": "mn",
"my_MM": "my",
"ne_NP": false,
"no_NO": "no",
"or_IN": false,
"ps_AF": false,
"fa_IR": "fa",
"pl_PL": "pl",
"pt_BR": "pt",
"pt_PT": "pt",
"pa_Guru_IN": "pa",
"otq_Latn_MX": false,
"ro_RO": "ro",
"ru_RU": "ru",
"sm_Latn_WS": "sm",
"sr_Cyrl_RS": "sr",
"sr_Latn_RS": "sr",
"sk_SK": "sk",
"sl_SI": "sl",
"so_SO": "so",
"es_ES": "es",
"sw_KE": "sw",
"sv_SE": "sv",
"ty_PF": "ty",
"ta_IN": "ta",
"tt_Latn_RU": "tt",
"te_IN": "te",
"th_TH": "th",
"bo_CN": false,
"ti_ET": "ti",
"to_TO": "to",
"tr_TR": "tr",
"uk_UA": "uk",
"ur_PK": "ur",
"ug_Arab_CN": false,
"uz_Latn_UZ": false,
"vi_VN": "vi",
"cy_GB": "cy",
"yua_MX": false,
"zu_ZA": "zu"
},
"help": [
{
"title": "VolcEngine/火山翻译 - Bytedance, China",
"url": "https://translate.volcengine.com/"
},
{
"title": "[github]translators",
"url": "https://github.com/UlionTse/translators"
}
]
},
{
"key": "yeekit_free",
"name": "[Free] Yeekit / 中译语通, China",
"type": "translators",
"translator": "yeekit",
"support": {
"ar_SA": "ar",
"ru_RU": "ru",
"de_DE": "de",
"fr_FR": "fr",
"ko_KR": "ko",
"pt_BR": "pt",
"pt_PT": "pt",
"ja_JP": "ja",
"es_ES": "es",
"en_US": "en",
"zh_CN": "zh"
},
"help": [
{
"title": "Yeekit/中译语通 - CTC, China",
"url": "https://www.yeekit.com/site/translate"
},
{
"title": "[github]translators",
"url": "https://github.com/UlionTse/translators"
}
]
}
]

View File

@@ -1 +1 @@
acb730813049605c549d0f45ab286a9edd81abca
200755004fe8e6c7507b787f0e82266539689145