Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion mreader/Base.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@
"ocr.appleTranslation" = "Apple 原生翻译";
"error.libraryLoad" = "Library Data Issue";
"ocr.appleTranslationFooter" = "Use Apple on-device low-latency translation for ordinary dialogue. Falls back to cloud AI when unavailable or incomplete.";
"ocr.borderProgress" = "AI Translation Border Progress";
"ocr.borderProgress" = "Show Translation Progress Effect";
"ocr.translationProgressEffect" = "Progress Effect";
"ocr.translationProgressEffect.bubblePulse" = "Breathing Bubble Outline";
"ocr.translationProgressEffect.marquee" = "Classic Marquee Border";
"ocr.colorStyle" = "Translation Text Color";
"ocr.targetLanguage" = "Translate To";
"translation.language.zh-Hans" = "Simplified Chinese";
Expand Down
911 changes: 632 additions & 279 deletions mreader/ReaderView.swift

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion mreader/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@
"ocr.appleTranslation" = "Apple 原生翻译";
"error.libraryLoad" = "Library Data Issue";
"ocr.appleTranslationFooter" = "Use Apple on-device low-latency translation for ordinary dialogue. Falls back to cloud AI when unavailable or incomplete.";
"ocr.borderProgress" = "AI Translation Border Progress";
"ocr.borderProgress" = "Show Translation Progress Effect";
"ocr.translationProgressEffect" = "Progress Effect";
"ocr.translationProgressEffect.bubblePulse" = "Breathing Bubble Outline";
"ocr.translationProgressEffect.marquee" = "Classic Marquee Border";
"ocr.colorStyle" = "Translation Text Color";
"ocr.colorStyle.contrast" = "High Contrast";
"ocr.colorStyle.coolWarm" = "Cool & Warm";
Expand Down
5 changes: 4 additions & 1 deletion mreader/ja.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@
"ocr.appleTranslation" = "Apple 原生翻译";
"error.libraryLoad" = "ライブラリデータの不具合";
"ocr.appleTranslationFooter" = "普通の会話は Apple の端末内・低遅延翻訳を使います。利用できない場合や未翻訳のまま残る場合はクラウド AI に自動フォールバックします。";
"ocr.borderProgress" = "AI 翻訳ボーダー進捗";
"ocr.borderProgress" = "翻訳中エフェクトを表示";
"ocr.translationProgressEffect" = "進行エフェクト";
"ocr.translationProgressEffect.bubblePulse" = "吹き出し呼吸アウトライン";
"ocr.translationProgressEffect.marquee" = "クラシック走査ボーダー";
"ocr.colorStyle" = "翻訳テキスト配色";
"ocr.colorStyle.contrast" = "ハイコントラスト";
"ocr.colorStyle.coolWarm" = "クール・ウォーム";
Expand Down
5 changes: 4 additions & 1 deletion mreader/ko.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@
"ocr.appleTranslation" = "Apple 原生翻译";
"error.libraryLoad" = "라이브러리 데이터 문제";
"ocr.appleTranslationFooter" = "일반 대사는 Apple 기기 내 저지연 번역을 사용합니다. 사용할 수 없거나 미번역 항목이 남으면 클라우드 AI로 자동 폴백합니다.";
"ocr.borderProgress" = "AI 번역 테두리 진행률";
"ocr.borderProgress" = "번역 진행 효과 표시";
"ocr.translationProgressEffect" = "진행 효과";
"ocr.translationProgressEffect.bubblePulse" = "말풍선 호흡 윤곽선";
"ocr.translationProgressEffect.marquee" = "클래식 마키 테두리";
"ocr.colorStyle" = "번역 텍스트 색상";
"ocr.colorStyle.contrast" = "고대비";
"ocr.colorStyle.coolWarm" = "쿨·웜";
Expand Down
5 changes: 4 additions & 1 deletion mreader/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@
"ocr.appleTranslation" = "Apple 原生翻译";
"error.libraryLoad" = "书架数据异常";
"ocr.appleTranslationFooter" = "普通对白使用 Apple 端侧低延迟翻译;不可用或未覆盖时自动回退云端 AI。";
"ocr.borderProgress" = "AI 翻译边框进度";
"ocr.borderProgress" = "显示翻译进度特效";
"ocr.translationProgressEffect" = "翻译中特效";
"ocr.translationProgressEffect.bubblePulse" = "气泡呼吸描边";
"ocr.translationProgressEffect.marquee" = "经典跑马灯";
"ocr.colorStyle" = "翻译文字配色";
"ocr.colorStyle.contrast" = "高对比";
"ocr.colorStyle.coolWarm" = "冷暖分明";
Expand Down
85 changes: 85 additions & 0 deletions mreaderTests/MangaVisionLayerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,91 @@ struct MangaVisionLayerTests {
#expect(segmentation.bubbles[0].bubbleBox == balloon.normalizedRect)
}

@Test func translationProgressDefaultsToSubtleBubblePulse() {
#expect(TranslationProgressEffect.defaultEffect == .bubblePulse)
#expect(TranslationProgressEffect.allCases.map(\.rawValue) == ["bubblePulse", "marquee"])
}

@Test func translationProgressPrefersPhysicalBalloonsOverTextFallback() {
let balloon = region(
.balloon,
x: 0.20,
y: 0.20,
width: 0.34,
height: 0.18,
confidence: 0.92
)
let text = region(
.text,
x: 0.26,
y: 0.25,
width: 0.16,
height: 0.06,
confidence: 0.94
)
let analysis = MangaPageAnalysis(
pageIdentifier: MangaPageIdentifier(
scope: "translation-progress-balloon",
pageIndex: 0,
sourceFingerprint: "fixture"
),
imageSize: CGSize(width: 1200, height: 1800),
panels: [],
texts: [text],
balloons: [balloon],
faces: [],
bodies: [],
modelIdentifier: "fixture",
modelVersion: 4
)

let regions = TranslationProgressRegionPolicy.regions(from: analysis)

#expect(regions.count == 1)
#expect(regions[0].id == balloon.id)
#expect(regions[0].type == .balloon)
}

@Test func translationProgressRejectsPanelSizedBalloonAndFallsBackToText() {
let oversizedBalloon = region(
.balloon,
x: 0.08,
y: 0.10,
width: 0.76,
height: 0.42,
confidence: 0.98
)
let text = region(
.text,
x: 0.30,
y: 0.24,
width: 0.20,
height: 0.07,
confidence: 0.90
)
let analysis = MangaPageAnalysis(
pageIdentifier: MangaPageIdentifier(
scope: "translation-progress-text-fallback",
pageIndex: 0,
sourceFingerprint: "fixture"
),
imageSize: CGSize(width: 1200, height: 1800),
panels: [],
texts: [text],
balloons: [oversizedBalloon],
faces: [],
bodies: [],
modelIdentifier: "fixture",
modelVersion: 4
)

let regions = TranslationProgressRegionPolicy.regions(from: analysis)

#expect(regions.count == 1)
#expect(regions[0].id == text.id)
#expect(regions[0].type == .text)
}

@Test func mangaBalloonContourFlowsIntoTranslationUnitAndSafeRegion() {
let contourPoints = [
CGPoint(x: 0.50, y: 0.18),
Expand Down
Loading