feat(locale): Phase 2 — 모든 사용자 노출 Korean 문자열 → strings.xml 추출 + 영문 번역

Sub-agent 작업 결과:
- values/strings.xml + values-ko/strings.xml 각각 71 → 194 줄 (약 85 신규 string)
- 17 파일 수정 (UI views 9개, components 3개, managers 1개, services 2개)

상세:
- ui/views/{home, consent, registration, reminder, monitoring (3), personalization, connection}
- components/{BatteryWarningBanner, FirmwareWarningBanner, LockableSlider}
- managers/AlignmentAdvisorV2 — Out/AdvisorState 에 msgKey + msgArgs 추가 (UI 가 context.getString 으로 localize)
- services/{ReminderService, BleForegroundService}

의도적으로 skip:
- ClinicalHomeView / ClinicalLiveView (R&D-only dev mode)
- PiezoMonitoring dev panel labels (dev 도구)
- AlignmentAdvice / 2-phase AlignGuide (UI 미사용)
- 이미 영어인 파일들 (Onboarding, Pin, SensorSelect, Calibration 등)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 17:27:05 +09:00
parent bc7843809e
commit 2cf4f4db2f
17 changed files with 409 additions and 110 deletions
@@ -18,6 +18,7 @@
*/
package com.example.medilightv2android.managers
import com.example.medilightv2android.R
import com.example.medilightv2android.walldetect.MethodDResult
import com.example.medilightv2android.walldetect.MethodDRunner
import com.example.medilightv2android.walldetect.algo.methodd.MethodDParams
@@ -32,6 +33,10 @@ data class AdvisorState(
val state: String, // 'accum' 또는 'commit'
val action: AlignAction?,
val msg: String,
/** Localized message string resource id (null if no localized message). */
val msgKey: Int? = null,
/** Args for msgKey (formatted into string). */
val msgArgs: List<Any> = emptyList(),
val dets: List<MethodDResult?>?,
val summary: AlignSummary?,
val accumProgress: Int = 0, // 'accum' 일 때 (현재/총)
@@ -161,7 +166,16 @@ class AlignGuide4Stage(
) {
enum class Phase4 { INITIAL_ACCUM, VERTICAL_CLIMB, CH3_STABILIZE, LR_BALANCE }
data class Out(val action: AlignAction?, val msg: String, val phase: Phase4, val progress: Int = 0)
data class Out(
val action: AlignAction?,
val msg: String,
val phase: Phase4,
val progress: Int = 0,
/** Localized string resource id for user-facing UI (msg is dev/debug). */
val msgKey: Int? = null,
/** Args for msgKey formatting. */
val msgArgs: List<Any> = emptyList(),
)
var phase: Phase4 = Phase4.INITIAL_ACCUM
private set
@@ -191,7 +205,9 @@ class AlignGuide4Stage(
initialCount++
if (initialCount < initialTarget) {
return Out(null, "잠시 그대로 두세요 (${initialCount}/${initialTarget})",
phase, initialCount)
phase, initialCount,
msgKey = R.string.align_hold_still,
msgArgs = listOf(initialCount, initialTarget))
}
phase = Phase4.VERTICAL_CLIMB
}
@@ -200,7 +216,8 @@ class AlignGuide4Stage(
if (phase == Phase4.VERTICAL_CLIMB) {
if (!s.ch3) {
return Out(AlignAction.MOVE_UP, "↑ 위로 조금씩 올리세요",
phase, 0)
phase, 0,
msgKey = R.string.align_move_up)
}
phase = Phase4.CH3_STABILIZE
stabilizeCount = 0
@@ -216,12 +233,15 @@ class AlignGuide4Stage(
phase = Phase4.VERTICAL_CLIMB
stabilizeCount = 0
return Out(AlignAction.MOVE_UP, "↑ 살짝 놓침 — 다시 위로",
phase, 0)
phase, 0,
msgKey = R.string.align_recover_up)
}
if (stabilizeCount < stabilizeTarget) {
return Out(AlignAction.STOP,
"✓ 좋아요! 그 위치 유지 (${stabilizeCount}/${stabilizeTarget})",
phase, stabilizeCount)
phase, stabilizeCount,
msgKey = R.string.align_hold_position,
msgArgs = listOf(stabilizeCount, stabilizeTarget))
}
phase = Phase4.LR_BALANCE
}
@@ -235,7 +255,10 @@ class AlignGuide4Stage(
val recoveryDir = if (3 in lost) AlignAction.MOVE_UP else AlignAction.MOVE_DOWN
val arrow = if (recoveryDir == AlignAction.MOVE_UP) "↑" else "↓"
val dirWord = if (recoveryDir == AlignAction.MOVE_UP) "위" else "아래"
return Out(recoveryDir, "$arrow 위치 살짝 벗어남 — ${dirWord}로 약간", phase, 0)
val recoveryKey = if (recoveryDir == AlignAction.MOVE_UP)
R.string.align_recover_up_short else R.string.align_recover_down_short
return Out(recoveryDir, "$arrow 위치 살짝 벗어남 — ${dirWord}로 약간", phase, 0,
msgKey = recoveryKey)
}
// full set 유지 중 — 좌우 균형 (deadband + consecutive 악화 로 oscillation 차단)
@@ -245,7 +268,8 @@ class AlignGuide4Stage(
val imbal = kotlin.math.abs(u4 - u5)
if (imbal <= latTol) {
return Out(AlignAction.STOP,
"✓ 정렬 완료! 그대로 측정 가능", phase, 0)
"✓ 정렬 완료! 그대로 측정 가능", phase, 0,
msgKey = R.string.align_complete)
}
if (lateralDir == null) {
// 첫 진입 — u4/u5 비교로 방향 추정
@@ -270,10 +294,14 @@ class AlignGuide4Stage(
lateralImbal = imbal
val arrow = if (lateralDir == AlignAction.MOVE_LEFT) "←" else "→"
val sideWord = if (lateralDir == AlignAction.MOVE_LEFT) "왼쪽" else "오른쪽"
return Out(lateralDir!!, "$arrow ${sideWord}으로 살짝", phase, 0)
val sideKey = if (lateralDir == AlignAction.MOVE_LEFT)
R.string.align_left else R.string.align_right
return Out(lateralDir!!, "$arrow ${sideWord}으로 살짝", phase, 0,
msgKey = sideKey)
}
// u4/u5 미검출 → probe
return Out(AlignAction.PROBE_LR, "↔ 좌우로 천천히 움직여 보세요", phase, 0)
return Out(AlignAction.PROBE_LR, "↔ 좌우로 천천히 움직여 보세요", phase, 0,
msgKey = R.string.align_probe_lr)
}
}
@@ -329,6 +357,7 @@ class RollingAligner(
val state = if (out.phase == AlignGuide4Stage.Phase4.INITIAL_ACCUM) "accum" else "commit"
val st = AdvisorState(
state = state, action = out.action, msg = out.msg,
msgKey = out.msgKey, msgArgs = out.msgArgs,
dets = dets, summary = summary,
accumProgress = if (state == "accum") out.progress else accumK,
accumTarget = accumK,