Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f78738b
docs(pr10): 新增 FTB Quest 抽取驗證報告
jlin53882 Mar 21, 2026
e6d80ec
feat(ftbquest): add step6 progress bar validation report #PR10
jlin53882 Mar 21, 2026
27b663f
fix(translation_view): 移除 ListView 不支援的 bgcolor 參數
jlin53882 Mar 22, 2026
7bf6355
fix(translation_view): 以深色 Container 取代 styled_card 作為日誌區
jlin53882 Mar 22, 2026
9bbaf93
fix(ui): 日誌區初始提示 + FTB 進度以檔案數均勻推進
jlin53882 Mar 22, 2026
bf290d3
fix(log_presenter): 移除 _entry_color 錯誤的 hex 前綴拼接,Colors enum 直接回傳即可
jlin53882 Mar 22, 2026
fab546e
fix(log_presenter): _entry_color correctly handles Colors enum, use .…
jlin53882 Mar 22, 2026
cdf34a6
fix: log_view scroll_to(end=True) after presenter.sync() to auto-scro…
jlin53882 Mar 22, 2026
1b368f7
fix(kubejs): should_skip_text() add skip_chinese=False for .js files
jlin53882 Mar 22, 2026
1918f42
fix(kubejs): add missing import json
jlin53882 Mar 22, 2026
15f4ee1
fix(kubejs): skip ASCII block art (█ ▓ ▒ ░) in should_skip_text()
jlin53882 Mar 22, 2026
37632a1
fix(kubejs): apply OpenCC (safe_convert_text_fn) to client_scripts va…
jlin53882 Mar 22, 2026
7f7f2f5
fix(ui): scroll_to offset=1.0 instead of invalid end=True
jlin53882 Mar 22, 2026
baa4673
fix(kubejs): skip items already in Traditional Chinese in step2 LM
jlin53882 Mar 22, 2026
a41d71e
fix(translation_view): remove unused threading import
jlin53882 Mar 23, 2026
ae61960
fix(CI): restore import threading + apply ruff format
jlin53882 Mar 23, 2026
f824580
fix(CI): use noqa instead of comment for threading F401
jlin53882 Mar 23, 2026
7c6a011
docs(CHANGELOG): fill v0.7.0 with PR10~PR39 changes
jlin53882 Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,27 @@

---

## [0.7.0] - TBD
## [0.7.0] - 2026-03-23

> 規劃範圍:PR40 ~ PR58(依 roadmap)。
> 範圍:PR10 ~ PR39

### Refactoring
- non-UI 核心 pipeline 拆分(lm_translator/lang_merge_content/FTB/KubeJS/MD/jar_processor)。
- `plugins/shared` 進一步收斂。
- `services_impl` lifecycle 抽共用 task runner。
- UI:先補 view characterization tests,再拆大型 view(cache_view/extractor_view/translation_view/config/rules)。
### Features
- FTB Quest 翻譯進度條驗證報告(`step6 progress bar validation report`),新增 FTB Quest 抽取格式完整性驗證。

### Improvements
- **翻譯視圖日誌區**:深色 Container 背景替代 styled_card;ListView 移除無效 bgcolor 參數;初始化提示文字;日誌自動滾到底。
- **KubeJS 翻譯 Pipeline**:OpenCC 簡→繁轉換延伸至 `client_scripts` 來源值;跳過 ASCII 藝術字(█▓▒░);`skip_chinese=False` 邏輯修正;新增 `item.kubejs.*` 翻譯記憶匹配。
- **翻譯進度**:FTB 進度改以檔案數均勻推進(非 chunk 數)。

### Bug Fixes
- `scroll_to(end=True)` → `scroll_to(offset=1.0)`(Flet 0.28.3 API 相容)。
- `log_presenter._entry_color` 移除錯誤 hex 前綴拼接,正確使用 `Colors` enum 的 `.value`。
- `kubejs_translator_clean.py` 補回 `import json`(`jq` 依賴)。
- CI:修復 lint F401(`threading` import 移除又恢復);Ruff format 6 個檔案。

### Tests
- 新增多顆 focused tests + characterization tests,讓後續 UI/core 重構不再盲飛。
- 3 個 `test_translation_view_characterization.py` characterization tests 新增(翻譯視圖行為迴歸保護)。

---

## [0.6.0] - 2026-03-12
10 changes: 5 additions & 5 deletions app/logging/log_presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def _sync_tail(
entries: Sequence[LogEntry],
) -> List[LogEntry]:
"""Tail 模式:全量替換為最後 N 筆。"""
tail = list(entries[-self.tail_lines:]) if entries else []
tail = list(entries[-self.tail_lines :]) if entries else []
list_view.controls.clear()
for entry in tail:
color = self._entry_color(entry)
Expand All @@ -137,10 +137,10 @@ def _entry_color(self, entry: LogEntry) -> str:
"""根據 entry 等級取得顏色。"""
if not self.colorize:
color = self.default_color
# 預防:若不是有效 hex 格式,主動加上 #
if not color.startswith("#"):
return f"#{color}"
return color
# Colors enum → 取 .value;hex string 直接用;其他嘗試 str()
if hasattr(color, "value"):
return color.value # Colors enum → "grey100"
return str(color)
return "#" + get_level_color(entry.level)

def _truncate(self, list_view: ft.ListView) -> None:
Expand Down
6 changes: 3 additions & 3 deletions app/views/lm_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
load_config().get("lm_translator", {}).get("lm_translate_folder_name", "LM翻譯後")
)


class LMView(ft.Column):
"""LM 翻譯頁(風格對齊 Translation/Extractor)。"""

Expand Down Expand Up @@ -74,9 +75,7 @@ def __init__(self, page: ft.Page, file_picker: ft.FilePicker):
)

# 狀態與日誌
self.status_chip = ft.Chip(
label=ft.Text("尚未開始"), bgcolor=theme.GREY_200
)
self.status_chip = ft.Chip(label=ft.Text("尚未開始"), bgcolor=theme.GREY_200)
self.progress_bar = ft.ProgressBar(
value=0, height=8, bgcolor=theme.GREY_200, color=theme.BLUE
)
Expand Down Expand Up @@ -274,6 +273,7 @@ def loop():
logs = snap.get("logs", []) or []
try:
self.log_presenter.sync(self.log_view, logs)
self.log_view.scroll_to(offset=1.0)
except Exception as e:
log_debug(f"LM log presenter sync failed: {e}")

Expand Down
4 changes: 3 additions & 1 deletion app/views/translation/translation_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def start_ui_timer(view):
mode="tail",
tail_lines=ui_cfg.get("tail_lines", 250),
colorize=False, # Translation 目前只有灰白色,保持現有外觀
default_color=str(ft.Colors.GREY_100),
default_color=ft.Colors.GREY_100,
)

def loop():
Expand All @@ -225,6 +225,8 @@ def loop():
try:
# PR3:presenter.sync() 內部處理 tail rebuild + 顏色
presenter.sync(view.log_view, logs)
# sync() 會 clear() + 重新加入所有 item,手動滾到最底部
view.log_view.scroll_to(offset=1.0)
except Exception as e:
log_warning(f"更新日誌視圖失敗: {e}")
status = (snap.get("status") or "").upper()
Expand Down
37 changes: 19 additions & 18 deletions app/views/translation_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
維護注意:本檔案的函式 docstring 用於維護說明,不代表行為變更。
"""


import threading

import flet as ft
import threading # noqa: F401

from app.ui import theme
from translation_tool.utils.log_unit import log_info

Expand Down Expand Up @@ -49,6 +48,7 @@
except Exception:
TaskSession = None


class TranslationView(ft.Column):
"""翻譯工作台:FTB / KubeJS / Markdown 三流程統一入口。"""

Expand All @@ -69,17 +69,16 @@ def __init__(self, page: ft.Page, file_picker: ft.FilePicker):
self._ui_timer_running = False

# 右側共用狀態與日誌
self.status_chip = ft.Chip(
label=ft.Text("尚未開始"), bgcolor=theme.GREY_200
)
self.status_chip = ft.Chip(label=ft.Text("尚未開始"), bgcolor=theme.GREY_200)
self.progress = ft.ProgressBar(
value=0, height=8, bgcolor=theme.GREY_200, color=theme.BLUE
)
# 初始提示文字,避免 ListView 空白時透明顯現深灰背景
self.log_view = ft.ListView(
expand=True,
spacing=4,
auto_scroll=True,
bgcolor="#1e1e1e", # 與 Container 背景一致,解決透明導致灰色圖塊問題
controls=[ft.Text("等待翻譯開始...", size=13, color=theme.GREY_100)],
)

header = ft.Row(
Expand Down Expand Up @@ -125,17 +124,13 @@ def __init__(self, page: ft.Page, file_picker: ft.FilePicker):
spacing=10,
),
),
styled_card(
title="執行日誌",
icon=ft.Icons.RECEIPT_LONG,
ft.Container(
expand=True,
content=ft.Container(
expand=True,
bgcolor="#1e1e1e",
border_radius=8,
padding=10,
content=self.log_view,
),
bgcolor="#1e1e1e",
border_radius=8,
border=ft.border.all(1, theme.GREY_800),
padding=10,
content=self.log_view,
),
],
expand=True,
Expand Down Expand Up @@ -199,7 +194,13 @@ def _action_row(
trailing: list[ft.Control] | None = None,
) -> ft.Control:
"""建立操作按鈕列 UI"""
return build_action_row(view=self, on_start=on_start, on_dry_run=on_dry_run, on_reset=on_reset, trailing=trailing)
return build_action_row(
view=self,
on_start=on_start,
on_dry_run=on_dry_run,
on_reset=on_reset,
trailing=trailing,
)

# ------------------------------------------------------------------
# Tab builders
Expand Down
90 changes: 80 additions & 10 deletions translation_tool/core/kubejs_translator_clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@

from pathlib import Path
from typing import Any, Callable
import json
import re

_LANG_REF_RE = re.compile(r"^\{.+\}$")


def is_filled_text_impl(v: Any) -> bool:
"""判斷是否為有實質內容的文字。"""
if not isinstance(v, str):
Expand All @@ -23,7 +25,10 @@ def is_filled_text_impl(v: Any) -> bool:
return False
return True

def deep_merge_3way_flat_impl(tw: dict, cn: dict, en: dict, *, safe_convert_text_fn: Callable[[str], str]) -> dict:

def deep_merge_3way_flat_impl(
tw: dict, cn: dict, en: dict, *, safe_convert_text_fn: Callable[[str], str]
) -> dict:
"""扁平 KubeJS 三語 merge:tw > cn->tw > en。"""
out = {}
keys = set(tw.keys()) | set(cn.keys()) | set(en.keys())
Expand All @@ -45,6 +50,7 @@ def deep_merge_3way_flat_impl(tw: dict, cn: dict, en: dict, *, safe_convert_text

return out


def prune_en_by_tw_flat_impl(en_map: dict, tw_available: dict) -> dict:
"""剪掉 tw 已有內容的 en key。"""
out = {}
Expand All @@ -54,6 +60,7 @@ def prune_en_by_tw_flat_impl(en_map: dict, tw_available: dict) -> dict:
out[k] = v
return out


def clean_kubejs_from_raw_impl(
base_dir: str,
*,
Expand All @@ -68,7 +75,7 @@ def clean_kubejs_from_raw_impl(
log_info_fn: Callable[..., None],
) -> dict:
"""實作:將 KubeJS 原始 lang 檔(en_us/zh_cn/zh_tw)做三方合併,產出待翻譯 en_us 與完成品 zh_tw。

Args:
base_dir: Modpack 根目錄。
output_dir: 輸出根目錄(預設 base_dir/Output)。
Expand All @@ -85,9 +92,19 @@ def clean_kubejs_from_raw_impl(
"""
base = Path(base_dir).resolve()
out_root = Path(output_dir).resolve() if output_dir else (base / "Output")
raw_root = Path(raw_dir).resolve() if raw_dir else (out_root / "kubejs" / "raw" / "kubejs")
pending_root_p = Path(pending_root).resolve() if pending_root else (out_root / "kubejs" / "待翻譯" / "kubejs")
final_root_p = Path(final_root).resolve() if final_root else (out_root / "kubejs" / "完成" / "kubejs")
raw_root = (
Path(raw_dir).resolve() if raw_dir else (out_root / "kubejs" / "raw" / "kubejs")
)
pending_root_p = (
Path(pending_root).resolve()
if pending_root
else (out_root / "kubejs" / "待翻譯" / "kubejs")
)
final_root_p = (
Path(final_root).resolve()
if final_root
else (out_root / "kubejs" / "完成" / "kubejs")
)

pending_root_p.mkdir(parents=True, exist_ok=True)
final_root_p.mkdir(parents=True, exist_ok=True)
Expand All @@ -101,13 +118,60 @@ def clean_kubejs_from_raw_impl(
else:
other_jsons.append(p)

# 建立 zh_tw lookup table:用於過濾 client_scripts/*.json
# 已翻譯的 key(有 zh_tw 對應)→ skip;未翻譯 → 保留到 pending
tw_lookup: dict[str, str] = {}
if final_root_p.exists():
for tw_file in final_root_p.rglob("zh_tw.json"):
tw_data = read_json_dict_fn(tw_file)
if tw_data:
tw_lookup.update(tw_data)
# 同時從 raw_root 的 lang/zh_tw.json 讀取(確保新翻譯也被納入)
for tw_file in raw_root.rglob("zh_tw.json"):
tw_data = read_json_dict_fn(tw_file)
if tw_data:
tw_lookup.update(
deep_merge_3way_flat_impl(
tw_data, {}, {}, safe_convert_text_fn=safe_convert_text_fn
)
)

copied_other = 0
for p in other_jsons:
rel = p.relative_to(raw_root)
dst = pending_root_p / rel
dst.parent.mkdir(parents=True, exist_ok=True)
dst.write_bytes(p.read_bytes())
copied_other += 1

if "client_scripts" in str(p):
# 對 client_scripts/*.json 做三語合併比對過濾
# client_scripts JSON key 格式:tooltips.js|modid:item.tooltip.0
# zh_tw.json key 格式:modid:item(無 .tooltip.N 後綴)
# → 需剝除前綴與 .tooltip.N 後綴才能正確比對
data = read_json_dict_fn(p)
if data:
filtered = {}
for k, v in data.items():
# 解析 key:去掉前綴 tooltips.js| 和 .tooltip.N 後綴
lookup_key = k.split("|", 1)[-1] if "|" in k else k
lookup_key = re.sub(r"\.tooltip\.\d+$", "", lookup_key)
lookup_key = re.sub(r"\[.*?\]", "", lookup_key).strip()
# 有 zh_tw 翻譯 → skip(視為 cache hit);無 → 保留
if lookup_key and lookup_key not in tw_lookup:
# ✅ 對簡體中文值做 OpenCC 轉換(s2tw),轉為繁體中文
v_converted = safe_convert_text_fn(v)
filtered[k] = v_converted
if filtered:
dst.write_text(
json.dumps(filtered, indent=2, ensure_ascii=False), "utf-8"
)
copied_other += 1
# else: 全部被過濾,不寫入也不計入 copied_other
else:
dst.write_bytes(p.read_bytes())
copied_other += 1
else:
dst.write_bytes(p.read_bytes())
copied_other += 1

groups: dict[Path, dict[str, Path]] = {}
for p in lang_files:
Expand All @@ -123,14 +187,18 @@ def clean_kubejs_from_raw_impl(
cn = read_json_dict_fn(files_map.get("zh_cn"))
tw = read_json_dict_fn(files_map.get("zh_tw"))

log_debug_fn(f"[KubeJS-CLEAN-DBG] group={group_dir} | en={len(en or {})} cn={len(cn or {})} tw={len(tw or {})}")
log_debug_fn(
f"[KubeJS-CLEAN-DBG] group={group_dir} | en={len(en or {})} cn={len(cn or {})} tw={len(tw or {})}"
)

has_twcn = bool(cn or tw)
rel_group = group_dir.relative_to(raw_root)

if en:
if has_twcn:
available_tw = deep_merge_3way_flat_impl(tw, cn, {}, safe_convert_text_fn=safe_convert_text_fn)
available_tw = deep_merge_3way_flat_impl(
tw, cn, {}, safe_convert_text_fn=safe_convert_text_fn
)
pending_en = prune_en_by_tw_flat_impl(en, available_tw)
else:
pending_en = en
Expand All @@ -141,7 +209,9 @@ def clean_kubejs_from_raw_impl(
pending_lang_written += 1

if has_twcn:
merged_tw = deep_merge_3way_flat_impl(tw, cn, {}, safe_convert_text_fn=safe_convert_text_fn)
merged_tw = deep_merge_3way_flat_impl(
tw, cn, {}, safe_convert_text_fn=safe_convert_text_fn
)
dst_tw = final_root_p / rel_group / "zh_tw.json"
write_json_fn(dst_tw, merged_tw)
merged_lang_written += 1
Expand Down
Loading
Loading