翻译bug修复
This commit is contained in:
@@ -133,7 +133,7 @@ function currentEditEl(): HTMLInputElement | null {
|
|||||||
return Array.isArray(raw) ? (raw[0] ?? null) : raw;
|
return Array.isArray(raw) ? (raw[0] ?? null) : raw;
|
||||||
}
|
}
|
||||||
const priorityStyle = ref<'{}' | '()' | '[]' | '<>' | 'suffix'>('{}');
|
const priorityStyle = ref<'{}' | '()' | '[]' | '<>' | 'suffix'>('{}');
|
||||||
const priorityStep = ref(1);
|
const priorityStep = ref(0.1);
|
||||||
function splitTokensLocal(txt: string): string[] {
|
function splitTokensLocal(txt: string): string[] {
|
||||||
return splitTokens(txt);
|
return splitTokens(txt);
|
||||||
}
|
}
|
||||||
@@ -176,9 +176,12 @@ function adjustWeight(core: string, delta: number): string {
|
|||||||
const decimals = stepStr.includes('.') ? stepStr.split('.')[1]!.length : 0;
|
const decimals = stepStr.includes('.') ? stepStr.split('.')[1]!.length : 0;
|
||||||
const cur = w == null ? 1.0 : w;
|
const cur = w == null ? 1.0 : w;
|
||||||
let nw = cur + delta;
|
let nw = cur + delta;
|
||||||
if (delta < 0 && nw <= 1.0) return base;
|
|
||||||
nw = Math.min(2.0, Math.max(0.1, nw));
|
|
||||||
nw = roundToDecimals(nw, decimals);
|
nw = roundToDecimals(nw, decimals);
|
||||||
|
|
||||||
|
// If weight is 1, return base without suffix
|
||||||
|
if (nw === 1) return base;
|
||||||
|
|
||||||
return base + ':' + nw;
|
return base + ':' + nw;
|
||||||
}
|
}
|
||||||
const text = ref('');
|
const text = ref('');
|
||||||
@@ -773,9 +776,15 @@ function applyEditSuggestion(s: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const unmappedTokens = computed(() => {
|
const unmappedTokens = computed(() => {
|
||||||
return tokens.value.filter(k => displayTrans(k) === k);
|
return tokens.value.filter(k => isUnmapped(k));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function isUnmapped(key: string): boolean {
|
||||||
|
const { core } = parseDetailedToken(key);
|
||||||
|
const tag = store.getTagByKey(core);
|
||||||
|
return !tag || !tag.translation?.[selectedLang.value];
|
||||||
|
}
|
||||||
|
|
||||||
function handleApplyTranslation(results: { key: string; trans: string }[]) {
|
function handleApplyTranslation(results: { key: string; trans: string }[]) {
|
||||||
results.forEach(({ key, trans }) => {
|
results.forEach(({ key, trans }) => {
|
||||||
store.addMapping(key, selectedLang.value, trans);
|
store.addMapping(key, selectedLang.value, trans);
|
||||||
@@ -792,9 +801,11 @@ async function autoTranslateSingle() {
|
|||||||
let target = selectedLang.value as string;
|
let target = selectedLang.value as string;
|
||||||
if (target === 'zh_CN') target = 'zh';
|
if (target === 'zh_CN') target = 'zh';
|
||||||
|
|
||||||
// 移除包裹层和下划线
|
// 移除包裹层和下划线,提取核心词
|
||||||
const { core } = store.parseTokenWrappers(key);
|
const { core } = parseDetailedToken(key);
|
||||||
const cleanText = core.replace(/_/g, ' ');
|
// 再次清理可能残留的括号(针对复杂嵌套或未闭合情况)
|
||||||
|
const cleanCore = core.replace(/^[\(\[\{<]+/, '').replace(/[\)\]\}>]+$/, '');
|
||||||
|
const cleanText = cleanCore.replace(/_/g, ' ');
|
||||||
|
|
||||||
const url = `https://sywb.top/api/translate2?text=${encodeURIComponent(cleanText)}&sourceLang=auto&targetLang=${target}`;
|
const url = `https://sywb.top/api/translate2?text=${encodeURIComponent(cleanText)}&sourceLang=auto&targetLang=${target}`;
|
||||||
|
|
||||||
@@ -812,23 +823,11 @@ async function autoTranslateSingle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function displayTrans(key: string): string {
|
function displayTrans(key: string): string {
|
||||||
const { core, weight, wrappers } = parseDetailedToken(key);
|
const { core, weight, wrappers, prefix, suffix } = parseDetailedToken(key);
|
||||||
const tag = store.getTagByKey(core);
|
const tag = store.getTagByKey(core);
|
||||||
const translatedCore = tag?.translation?.[selectedLang.value] ?? tag?.key ?? core;
|
const translatedCore = tag?.translation?.[selectedLang.value] ?? tag?.key ?? core;
|
||||||
|
|
||||||
let result = translatedCore;
|
return constructToken(translatedCore, weight, wrappers, prefix, suffix);
|
||||||
let currentWrappers = [...wrappers];
|
|
||||||
|
|
||||||
if (weight !== undefined && weight !== 1) {
|
|
||||||
const lastWrapper = currentWrappers[currentWrappers.length - 1];
|
|
||||||
if (lastWrapper === '()') {
|
|
||||||
currentWrappers.pop();
|
|
||||||
}
|
|
||||||
const wStr = Number.isInteger(weight) ? weight.toString() : weight.toFixed(2).replace(/\.?0+$/, '');
|
|
||||||
result = `(${result}:${wStr})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return store.wrapToken(result, currentWrappers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRemoveDisabled(token: string): boolean {
|
function isRemoveDisabled(token: string): boolean {
|
||||||
@@ -1066,7 +1065,7 @@ function isRemoveDisabled(token: string): boolean {
|
|||||||
<div v-else class="pe-token-content">
|
<div v-else class="pe-token-content">
|
||||||
<span class="pe-key-compact">{{ k }}</span>
|
<span class="pe-key-compact">{{ k }}</span>
|
||||||
<span class="pe-arrow-compact">→</span>
|
<span class="pe-arrow-compact">→</span>
|
||||||
<span class="pe-trans-compact" :class="{ unmapped: displayTrans(k) === k }">
|
<span class="pe-trans-compact" :class="{ unmapped: isUnmapped(k) }">
|
||||||
{{ displayTrans(k) }}
|
{{ displayTrans(k) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1118,10 +1117,10 @@ function isRemoveDisabled(token: string): boolean {
|
|||||||
<div class="pe-token-main" @dblclick="beginEdit(i)">
|
<div class="pe-token-main" @dblclick="beginEdit(i)">
|
||||||
<span class="pe-key-detail">{{ k }}</span>
|
<span class="pe-key-detail">{{ k }}</span>
|
||||||
<span class="pe-arrow-detail">→</span>
|
<span class="pe-arrow-detail">→</span>
|
||||||
<span class="pe-trans-detail" :class="{ unmapped: displayTrans(k) === k }">{{ displayTrans(k) }}</span>
|
<span class="pe-trans-detail" :class="{ unmapped: isUnmapped(k) }">{{ displayTrans(k) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pe-token-controls">
|
<div class="pe-token-controls">
|
||||||
<button v-if="displayTrans(k) === k" class="pe-add-map-btn" @click="showAddMap(i)" title="添加映射">
|
<button v-if="isUnmapped(k)" class="pe-add-map-btn" @click="showAddMap(i)" title="添加映射">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<line x1="12" y1="5" x2="12" y2="19" stroke="currentColor" stroke-width="2"/>
|
<line x1="12" y1="5" x2="12" y2="19" stroke="currentColor" stroke-width="2"/>
|
||||||
<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2"/>
|
<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2"/>
|
||||||
|
|||||||
@@ -41,9 +41,9 @@
|
|||||||
@change="toggleSelect(token)"
|
@change="toggleSelect(token)"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div class="tp-item-key" :title="token">{{ token }}</div>
|
<div class="tp-item-key" :title="token">{{ parseDetailedToken(token).core }}</div>
|
||||||
<div class="tp-item-trans">
|
<div class="tp-item-trans">
|
||||||
<div v-if="results[token]" class="tp-trans-result">
|
<div v-if="results[token] !== undefined" class="tp-trans-result">
|
||||||
<input
|
<input
|
||||||
v-model="results[token]"
|
v-model="results[token]"
|
||||||
class="tp-trans-input"
|
class="tp-trans-input"
|
||||||
@@ -71,6 +71,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, reactive } from 'vue';
|
import { ref, computed, watch, reactive } from 'vue';
|
||||||
|
import { parseDetailedToken } from '../stores/promptStore';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -151,8 +152,13 @@ async function startTranslation() {
|
|||||||
let target = props.targetLang;
|
let target = props.targetLang;
|
||||||
if (target === 'zh_CN') target = 'zh';
|
if (target === 'zh_CN') target = 'zh';
|
||||||
|
|
||||||
// 预处理:移除下划线
|
// 预处理:移除包裹层和下划线,提取核心词
|
||||||
const cleanTokens = batch.map(t => t.replace(/_/g, ' '));
|
const cleanTokens = batch.map(t => {
|
||||||
|
const { core } = parseDetailedToken(t);
|
||||||
|
// 再次清理可能残留的括号(针对复杂嵌套或未闭合情况)
|
||||||
|
const cleanCore = core.replace(/^[\(\[\{<]+/, '').replace(/[\)\]\}>]+$/, '');
|
||||||
|
return cleanCore.replace(/_/g, ' ');
|
||||||
|
});
|
||||||
const text = cleanTokens.join(SEPARATOR);
|
const text = cleanTokens.join(SEPARATOR);
|
||||||
|
|
||||||
const url = `https://sywb.top/api/translate2?text=${encodeURIComponent(text)}&sourceLang=auto&targetLang=${target}`;
|
const url = `https://sywb.top/api/translate2?text=${encodeURIComponent(text)}&sourceLang=auto&targetLang=${target}`;
|
||||||
@@ -165,6 +171,10 @@ async function startTranslation() {
|
|||||||
batch.forEach((token, idx) => {
|
batch.forEach((token, idx) => {
|
||||||
const trans = translations[idx] ? translations[idx].trim() : '';
|
const trans = translations[idx] ? translations[idx].trim() : '';
|
||||||
if (trans) {
|
if (trans) {
|
||||||
|
// 保存时,如果 token 是纯词(无包裹/权重),直接保存翻译结果
|
||||||
|
// 如果 token 有包裹/权重,我们应该只保存核心词的映射,而不是将整个 token 作为 key
|
||||||
|
// 但是这里的 results 是按 token 索引的显示结果。
|
||||||
|
// 用户希望在这里看到的是核心词的翻译结果,而不是带符号的。
|
||||||
results[token] = trans;
|
results[token] = trans;
|
||||||
cache[token] = trans;
|
cache[token] = trans;
|
||||||
}
|
}
|
||||||
@@ -184,7 +194,8 @@ function apply() {
|
|||||||
const list: { key: string; trans: string }[] = [];
|
const list: { key: string; trans: string }[] = [];
|
||||||
for (const key of selected) {
|
for (const key of selected) {
|
||||||
if (results[key]) {
|
if (results[key]) {
|
||||||
list.push({ key, trans: results[key] });
|
const { core } = parseDetailedToken(key);
|
||||||
|
list.push({ key: core, trans: results[key] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit('apply', list);
|
emit('apply', list);
|
||||||
|
|||||||
+32
-58
@@ -336,44 +336,9 @@ export const usePromptStore = defineStore('promptStore', {
|
|||||||
unifyPriorityStyle() {
|
unifyPriorityStyle() {
|
||||||
const tokens = splitTokens(this.promptText);
|
const tokens = splitTokens(this.promptText);
|
||||||
const processed = tokens.map(token => {
|
const processed = tokens.map(token => {
|
||||||
const { core, weight, wrappers } = parseDetailedToken(token);
|
const { core, weight, wrappers, prefix, suffix } = parseDetailedToken(token);
|
||||||
|
return constructToken(core, weight, wrappers, prefix, suffix);
|
||||||
// 重构 Token
|
|
||||||
let result = core;
|
|
||||||
|
|
||||||
// 如果有权重,应用标准权重格式 (core:weight)
|
|
||||||
// 注意:如果原来的 wrappers 里包含用于权重的括号,需要处理
|
|
||||||
let currentWrappers = [...wrappers];
|
|
||||||
|
|
||||||
if (weight !== undefined && weight !== 1) {
|
|
||||||
// 检查是否需要消耗一个外层括号 (如果是 () 类型)
|
|
||||||
// 通常 (aaa:1.2) 解析出 wrappers=['()'], core='aaa:1.2' (旧逻辑)
|
|
||||||
// 新逻辑 parseDetailedToken 会解析出 wrappers=['()'], core='aaa', weight=1.2
|
|
||||||
// 我们需要保留非权重定义的 wrapper。
|
|
||||||
// 假设最内层的 () 是权重定义的一部分,我们将其消耗掉,用 (core:weight) 替代
|
|
||||||
|
|
||||||
// 简单的策略:先生成 (core:weight),然后应用剩余的 wrappers
|
|
||||||
// 但我们需要知道原来的 wrappers 是否真的包含权重的括号。
|
|
||||||
// parseDetailedToken 逻辑:如果解析出了 weight,且 strippedWrappers 包含 (),则认为消耗了一个。
|
|
||||||
|
|
||||||
// 这里简单化:如果 weight 存在,我们生成 (core:weight)。
|
|
||||||
// 如果 original wrappers 包含 (),我们认为其中一个就是这个权重括号。
|
|
||||||
// 除非是 ((aaa:1.2)) -> wrappers=['()', '()'].
|
|
||||||
|
|
||||||
const lastWrapper = currentWrappers[currentWrappers.length - 1];
|
|
||||||
if (lastWrapper === '()') {
|
|
||||||
currentWrappers.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化权重,最多保留2位小数
|
|
||||||
const wStr = Number.isInteger(weight) ? weight.toString() : weight.toFixed(2).replace(/\.?0+$/, '');
|
|
||||||
result = `(${result}:${wStr})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 应用剩余的 wrappers
|
|
||||||
return this.wrapToken(result, currentWrappers);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.promptText = processed.join(', ');
|
this.promptText = processed.join(', ');
|
||||||
},
|
},
|
||||||
// 切换下划线和空格
|
// 切换下划线和空格
|
||||||
@@ -518,25 +483,12 @@ export const usePromptStore = defineStore('promptStore', {
|
|||||||
},
|
},
|
||||||
getTranslation(key: string, lang: LangCode): string | null {
|
getTranslation(key: string, lang: LangCode): string | null {
|
||||||
// 兼容包裹层:如 {aaa}、(aaa) 等,以及复杂权重 (aaa:1.2)
|
// 兼容包裹层:如 {aaa}、(aaa) 等,以及复杂权重 (aaa:1.2)
|
||||||
const { core, wrappers, weight } = parseDetailedToken(key);
|
const { core, wrappers, weight, prefix, suffix } = parseDetailedToken(key);
|
||||||
const tag = this.getTagByKey(core);
|
const tag = this.getTagByKey(core);
|
||||||
if (!tag) return null;
|
if (!tag) return null;
|
||||||
const translatedCore = tag.translation?.[lang] ?? tag.key;
|
const translatedCore = tag.translation?.[lang] ?? tag.key;
|
||||||
|
|
||||||
// 保持原有包裹层结构,返回被翻译后的核心
|
return constructToken(translatedCore, weight, wrappers, prefix, suffix);
|
||||||
let result = translatedCore;
|
|
||||||
let currentWrappers = [...wrappers];
|
|
||||||
|
|
||||||
if (weight !== undefined && weight !== 1) {
|
|
||||||
const lastWrapper = currentWrappers[currentWrappers.length - 1];
|
|
||||||
if (lastWrapper === '()') {
|
|
||||||
currentWrappers.pop();
|
|
||||||
}
|
|
||||||
const wStr = Number.isInteger(weight) ? weight.toString() : weight.toFixed(2).replace(/\.?0+$/, '');
|
|
||||||
result = `(${result}:${wStr})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.wrapToken(result, currentWrappers);
|
|
||||||
},
|
},
|
||||||
getSuggestions(prefix: string, limit = 8): string[] {
|
getSuggestions(prefix: string, limit = 8): string[] {
|
||||||
const list: string[] = [];
|
const list: string[] = [];
|
||||||
@@ -1286,7 +1238,7 @@ function normalizeKeyForMatch(s: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 解析详细 Token 信息(核心、权重、包裹层)
|
// 解析详细 Token 信息(核心、权重、包裹层)
|
||||||
export function parseDetailedToken(token: string): { core: string; weight?: number; wrappers: string[] } {
|
export function parseDetailedToken(token: string): { core: string; weight?: number; wrappers: string[]; prefix?: string; suffix?: string } {
|
||||||
// 1. 归一化符号
|
// 1. 归一化符号
|
||||||
let current = normalizeSymbols(token).trim();
|
let current = normalizeSymbols(token).trim();
|
||||||
const wrappers: string[] = [];
|
const wrappers: string[] = [];
|
||||||
@@ -1317,10 +1269,24 @@ export function parseDetailedToken(token: string): { core: string; weight?: numb
|
|||||||
}
|
}
|
||||||
|
|
||||||
let weight: number | undefined;
|
let weight: number | undefined;
|
||||||
|
let prefix = '';
|
||||||
|
let suffix = '';
|
||||||
|
|
||||||
|
// 特殊格式处理:Prefix:Core:Suffix (例如 11:1girl:123)
|
||||||
|
// 简单启发式:如果符合 数字:内容:数字 格式,保留前后缀
|
||||||
|
const complexMatch = /^(\d+):(.+):(\d+)$/.exec(current);
|
||||||
|
if (complexMatch) {
|
||||||
|
prefix = complexMatch[1] + ':';
|
||||||
|
current = complexMatch[2]!.trim();
|
||||||
|
suffix = ':' + complexMatch[3];
|
||||||
|
// 此模式下暂不提取权重,而是作为固定前后缀处理
|
||||||
|
return { core: current, weight: undefined, wrappers, prefix, suffix };
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 解析核心内容里的权重
|
// 3. 解析核心内容里的权重
|
||||||
// 标准格式 (core:weight) -> 剥离后变成 core:weight
|
// 标准格式 (core:weight) -> 剥离后变成 core:weight
|
||||||
const colonMatch = /^(.+):([0-9.]+)$/.exec(current);
|
// 增加 \s* 允许冒号后有空格 (core: 1.2)
|
||||||
|
const colonMatch = /^(.+):\s*([0-9.]+)$/.exec(current);
|
||||||
if(colonMatch) {
|
if(colonMatch) {
|
||||||
current = colonMatch[1]?.trim() ?? '';
|
current = colonMatch[1]?.trim() ?? '';
|
||||||
const w = parseFloat(colonMatch[2]!);
|
const w = parseFloat(colonMatch[2]!);
|
||||||
@@ -1335,25 +1301,33 @@ export function parseDetailedToken(token: string): { core: string; weight?: numb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { core: current, weight, wrappers };
|
return { core: current, weight, wrappers, prefix, suffix };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重构 Token(将核心内容与权重、包裹层组合)
|
// 重构 Token(将核心内容与权重、包裹层组合)
|
||||||
export function constructToken(core: string, weight: number | undefined, wrappers: string[]): string {
|
export function constructToken(core: string, weight: number | undefined, wrappers: string[], prefix = '', suffix = ''): string {
|
||||||
let result = core;
|
let result = core;
|
||||||
let currentWrappers = [...wrappers];
|
let currentWrappers = [...wrappers];
|
||||||
|
|
||||||
|
// 应用前后缀 (如果存在)
|
||||||
|
if (prefix) result = prefix + result;
|
||||||
|
if (suffix) result = result + suffix;
|
||||||
|
|
||||||
if (weight !== undefined && weight !== 1) {
|
if (weight !== undefined && weight !== 1) {
|
||||||
// 检查并消耗一个外层 () 作为权重包裹(如果存在)
|
// 检查并消耗一个外层 () 作为权重包裹(如果存在)
|
||||||
// 注意:这里采用简化策略,假设最内层的 () 是为了包裹权重而存在的
|
// 注意:这里采用简化策略,假设最内层的 () 是为了包裹权重而存在的
|
||||||
// 实际上如果原 Token 是 (aaa:1.2),解析得到 wrappers=['()']
|
// 实际上如果原 Token 是 (aaa:1.2),解析得到 wrappers=['()']
|
||||||
// 重构时如果不消耗,会变成 ((aaa:1.2))
|
// 重构时如果不消耗,会变成 ((aaa:1.2))
|
||||||
const lastWrapper = currentWrappers[currentWrappers.length - 1];
|
const lastWrapper = currentWrappers[currentWrappers.length - 1];
|
||||||
|
const wStr = Number.isInteger(weight) ? weight.toString() : weight.toFixed(2).replace(/\.?0+$/, '');
|
||||||
|
|
||||||
if (lastWrapper === '()') {
|
if (lastWrapper === '()') {
|
||||||
currentWrappers.pop();
|
currentWrappers.pop();
|
||||||
}
|
|
||||||
const wStr = Number.isInteger(weight) ? weight.toString() : weight.toFixed(2).replace(/\.?0+$/, '');
|
|
||||||
result = `(${result}:${wStr})`;
|
result = `(${result}:${wStr})`;
|
||||||
|
} else {
|
||||||
|
// 如果没有括号包裹,保持原样 (aaa:1.1)
|
||||||
|
result = `${result}:${wStr}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用包裹层
|
// 应用包裹层
|
||||||
|
|||||||
Reference in New Issue
Block a user