Better handling of SG plugins
This commit is contained in:
parent
2e0a7d4899
commit
065baae500
2 changed files with 108 additions and 5 deletions
|
|
@ -49,7 +49,7 @@ _ACTOR_MAP_CACHE_LOCK = threading.Lock()
|
|||
_VAR_ACTOR_RE = re.compile(r"\\n\[(\d+)\]", re.IGNORECASE)
|
||||
|
||||
# Regex - Need to change this if you want to translate from/to other languages. Default is Japanese Regex
|
||||
LANGREGEX = r"[\u3000\u3002-\u3009\u300C-\u303F\u3040-\u309A\u309C-\u30FF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\uFF61-\uFF9F]+"
|
||||
LANGREGEX = r"[\u3000\u3002-\u3009\u300C-\u303F\u3040-\u309A\u309C-\u30FA\u30FC-\u30FF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\uFF61-\uFF9F]+"
|
||||
|
||||
# Get pricing configuration based on the model
|
||||
PRICING_CONFIG = getPricingConfig(MODEL)
|
||||
|
|
@ -688,7 +688,37 @@ def parseMap(data, filename):
|
|||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateNote(event, regex, wordwrap=False):
|
||||
def _normalize_sg_desc(text: str) -> str:
|
||||
"""Normalize SG description text before AI translation.
|
||||
|
||||
Japanese body text is hard-wrapped at screen width using bare \\n.
|
||||
This collapses those intra-paragraph newlines into spaces so the AI
|
||||
receives clean prose paragraphs, while preserving:
|
||||
- \\n\\n paragraph / section breaks
|
||||
- ◆ / ・ / • / ● header lines (kept on their own line)
|
||||
"""
|
||||
HEADER_CHARS = ("◆", "・", "•", "●")
|
||||
blocks = text.split("\n\n")
|
||||
normalized_blocks = []
|
||||
for block in blocks:
|
||||
lines = block.split("\n")
|
||||
result_lines: list[str] = []
|
||||
body_buf: list[str] = []
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if stripped.startswith(HEADER_CHARS):
|
||||
if body_buf:
|
||||
result_lines.append(" ".join(body_buf))
|
||||
body_buf = []
|
||||
result_lines.append(stripped)
|
||||
elif stripped:
|
||||
body_buf.append(stripped)
|
||||
if body_buf:
|
||||
result_lines.append(" ".join(body_buf))
|
||||
normalized_blocks.append("\n".join(result_lines))
|
||||
return "\n\n".join(normalized_blocks)
|
||||
|
||||
|
||||
# Regex String
|
||||
jaString = event.get("note") or ""
|
||||
if not isinstance(jaString, str):
|
||||
|
|
@ -1340,7 +1370,8 @@ def searchNames(data, pbar, context, filename):
|
|||
# Skip if IGNORETLTEXT is enabled and no Japanese text
|
||||
if IGNORETLTEXT and not re.search(LANGREGEX, match_text):
|
||||
continue
|
||||
notesBatch.append(match_text)
|
||||
# Normalize for AI (collapse intra-paragraph \n, keep headers)
|
||||
notesBatch.append(_normalize_sg_desc(match_text))
|
||||
notesBatchMap.append((idx, regex, match_text, wordwrap))
|
||||
else:
|
||||
for m in matches:
|
||||
|
|
@ -1367,7 +1398,10 @@ def searchNames(data, pbar, context, filename):
|
|||
break
|
||||
translated = translatedNotesBatch[note_insert_idx]
|
||||
if wordwrap:
|
||||
translated = dazedwrap.wrapText(translated, width=NOTEWIDTH)
|
||||
if regex.startswith(r"<SG説明:"):
|
||||
translated = dazedwrap.wrapSGDesc(translated, width=NOTEWIDTH)
|
||||
else:
|
||||
translated = dazedwrap.wrapText(translated, width=NOTEWIDTH)
|
||||
translated = translated.replace('"', "")
|
||||
# Use a safe literal match for the replacement (no re.escape, just str.replace)
|
||||
data[idx]["note"] = data[idx]["note"].replace(match_text, translated, 1)
|
||||
|
|
|
|||
|
|
@ -53,4 +53,73 @@ def wrapText(text: str, width: int) -> str:
|
|||
lines.append(" ".join(current_line))
|
||||
wrapped_segments.append("\n".join(lines))
|
||||
# Rejoin with double newlines to preserve hard breaks
|
||||
return "\n\n".join(wrapped_segments)
|
||||
return "\n\n".join(wrapped_segments)
|
||||
|
||||
|
||||
def wrapSGDesc(text: str, width: int) -> str:
|
||||
"""Structured word-wrap for SG plugin description blocks.
|
||||
|
||||
Unlike wrapText, this function understands the layout of SG info-box
|
||||
content:
|
||||
◆ / ・ / • / ● lines are always kept on their own line as section
|
||||
headers; the body text immediately following is word-wrapped to
|
||||
*width*. Paragraph breaks (\\n\\n) are preserved between sections.
|
||||
"""
|
||||
HEADER_CHARS = ("◆", "・", "•", "●")
|
||||
|
||||
paragraphs = text.split("\n\n")
|
||||
wrapped_paragraphs = []
|
||||
for para in paragraphs:
|
||||
lines = para.split("\n")
|
||||
out_parts = []
|
||||
body_buf: list[str] = []
|
||||
|
||||
def flush_body():
|
||||
if not body_buf:
|
||||
return
|
||||
words = " ".join(body_buf).split()
|
||||
cur_line: list[str] = []
|
||||
cur_len = 0
|
||||
for word in words:
|
||||
wl = _get_visible_length(word)
|
||||
if cur_len + wl + len(cur_line) <= width:
|
||||
cur_line.append(word)
|
||||
cur_len += wl
|
||||
else:
|
||||
if cur_line:
|
||||
out_parts.append(" ".join(cur_line))
|
||||
cur_line = [word]
|
||||
cur_len = wl
|
||||
if cur_line:
|
||||
out_parts.append(" ".join(cur_line))
|
||||
body_buf.clear()
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
continue
|
||||
if stripped.startswith(HEADER_CHARS):
|
||||
flush_body()
|
||||
# Word-wrap the header line itself — the bullet stays on the
|
||||
# first wrapped line; continuation lines are plain body lines.
|
||||
words = stripped.split()
|
||||
cur_line: list[str] = []
|
||||
cur_len = 0
|
||||
for word in words:
|
||||
wl = _get_visible_length(word)
|
||||
if cur_len + wl + len(cur_line) <= width:
|
||||
cur_line.append(word)
|
||||
cur_len += wl
|
||||
else:
|
||||
if cur_line:
|
||||
out_parts.append(" ".join(cur_line))
|
||||
cur_line = [word]
|
||||
cur_len = wl
|
||||
if cur_line:
|
||||
out_parts.append(" ".join(cur_line))
|
||||
else:
|
||||
body_buf.append(stripped)
|
||||
flush_body()
|
||||
wrapped_paragraphs.append("\n".join(out_parts))
|
||||
|
||||
return "\n\n".join(wrapped_paragraphs)
|
||||
Loading…
Reference in a new issue