feat(locale): DeviceScanView 한글화 — 기기 연결 페이지

이전 sweep 에서 누락됐던 두번째 '기기 연결' 화면 풀 한글화.

추가 strings: 25 신규 (각 언어)
- '기기 연결' 제목, 검색/탭 안내
- 페어링 필요 알림 (다이얼로그 + 에러 메시지)
- Bluetooth off / 권한 필요 경고
- 검색 중 / 기기 없음 / 트러블슈팅 4단계
- 저장된 기기 / 새 기기 섹션
- 권한 허용 / 검색 시작·중지 버튼
- 최근, 페어링 삭제, 신호 좋음/보통/약함

설계:
- signalLabel(rssi) → 영어 — dev/log 용 유지
- signalLabelRes(rssi) 신규 — UI 용 Locale-aware 라벨

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 11:31:50 +09:00
parent 3b37c816fc
commit 6cff741370
3 changed files with 97 additions and 28 deletions
@@ -133,7 +133,7 @@ fun DeviceScanView(appState: AppState) {
appState.deviceConnected() appState.deviceConnected()
} else { } else {
bleManager.disconnect() bleManager.disconnect()
bleManager.connectionError.value = "Pairing required. Please pair the device." bleManager.connectionError.value = context.getString(R.string.device_scan_pairing_required_err)
} }
} }
} }
@@ -149,8 +149,8 @@ fun DeviceScanView(appState: AppState) {
if (showPairingHint) { if (showPairingHint) {
AlertDialog( AlertDialog(
onDismissRequest = {}, onDismissRequest = {},
title = { Text("Pairing Required", fontWeight = FontWeight.Bold) }, title = { Text(stringResource(R.string.device_scan_pairing_required_title), fontWeight = FontWeight.Bold) },
text = { Text("A Bluetooth pairing request will appear.\nTap 'Pair', then enter PIN if prompted.") }, text = { Text(stringResource(R.string.device_scan_pairing_required_msg)) },
confirmButton = {} confirmButton = {}
) )
} }
@@ -166,7 +166,7 @@ fun DeviceScanView(appState: AppState) {
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
IconButton(onClick = { appState.backToSensorSelect() }) { IconButton(onClick = { appState.backToSensorSelect() }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = MlPrimary) Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.device_scan_back_desc), tint = MlPrimary)
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
} }
@@ -175,11 +175,11 @@ fun DeviceScanView(appState: AppState) {
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text("Connect Device", fontSize = 24.sp, fontWeight = FontWeight.Bold) Text(stringResource(R.string.device_scan_title), fontSize = 24.sp, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
if (knownDevices.isEmpty()) "Searching for nearby devices..." if (knownDevices.isEmpty()) stringResource(R.string.device_scan_searching)
else "Tap a saved device or scan for new", else stringResource(R.string.device_scan_tap_or_scan),
fontSize = 14.sp, color = MlSecondaryText fontSize = 14.sp, color = MlSecondaryText
) )
} }
@@ -197,10 +197,10 @@ fun DeviceScanView(appState: AppState) {
.padding(16.dp), .padding(16.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(Icons.Default.Warning, "Warning", tint = Color.White) Icon(Icons.Default.Warning, stringResource(R.string.device_scan_warning_desc), tint = Color.White)
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Column { Column {
Text("Bluetooth is off", fontWeight = FontWeight.Bold, color = Color.White, fontSize = 14.sp) Text(stringResource(R.string.device_scan_bluetooth_off), fontWeight = FontWeight.Bold, color = Color.White, fontSize = 14.sp)
Text("Enable Bluetooth in Settings", color = Color.White.copy(alpha = 0.8f), fontSize = 12.sp) Text("Enable Bluetooth in Settings", color = Color.White.copy(alpha = 0.8f), fontSize = 12.sp)
} }
} }
@@ -218,10 +218,10 @@ fun DeviceScanView(appState: AppState) {
.padding(16.dp), .padding(16.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(Icons.Default.Warning, "Warning", tint = Color.White) Icon(Icons.Default.Warning, stringResource(R.string.device_scan_warning_desc), tint = Color.White)
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Column { Column {
Text("Permissions required", fontWeight = FontWeight.Bold, color = Color.White, fontSize = 14.sp) Text(stringResource(R.string.device_scan_permissions_required), fontWeight = FontWeight.Bold, color = Color.White, fontSize = 14.sp)
Text("Bluetooth and Location permissions are needed to scan for devices", color = Color.White.copy(alpha = 0.8f), fontSize = 12.sp) Text("Bluetooth and Location permissions are needed to scan for devices", color = Color.White.copy(alpha = 0.8f), fontSize = 12.sp)
} }
} }
@@ -239,11 +239,11 @@ fun DeviceScanView(appState: AppState) {
.padding(12.dp), .padding(12.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(Icons.Default.Close, "Error", tint = Color.White) Icon(Icons.Default.Close, stringResource(R.string.device_scan_error_desc), tint = Color.White)
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Text(error, color = Color.White, fontSize = 13.sp, modifier = Modifier.weight(1f)) Text(error, color = Color.White, fontSize = 13.sp, modifier = Modifier.weight(1f))
IconButton(onClick = { bleManager.connectionError.value = null }) { IconButton(onClick = { bleManager.connectionError.value = null }) {
Icon(Icons.Default.Close, "Dismiss", tint = Color.White.copy(alpha = 0.8f)) Icon(Icons.Default.Close, stringResource(R.string.device_scan_dismiss_desc), tint = Color.White.copy(alpha = 0.8f))
} }
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -259,7 +259,7 @@ fun DeviceScanView(appState: AppState) {
) { ) {
BladdyIdleRiveView(size = 180.dp) BladdyIdleRiveView(size = 180.dp)
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text("Scanning...", color = MlSecondaryText) Text(stringResource(R.string.device_scan_scanning), color = MlSecondaryText)
} }
} else if (emptyAll) { } else if (emptyAll) {
Column( Column(
@@ -269,17 +269,17 @@ fun DeviceScanView(appState: AppState) {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Text("No devices found", fontSize = 16.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText) Text(stringResource(R.string.device_scan_no_devices), fontSize = 16.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText)
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Card( Card(
shape = RoundedCornerShape(14.dp), shape = RoundedCornerShape(14.dp),
colors = CardDefaults.cardColors(containerColor = MlCardBackground) colors = CardDefaults.cardColors(containerColor = MlCardBackground)
) { ) {
Column(modifier = Modifier.padding(16.dp)) { Column(modifier = Modifier.padding(16.dp)) {
TroubleshootItem("1.", "Make sure the device is powered on") TroubleshootItem("1.", stringResource(R.string.device_scan_troubleshoot_1))
TroubleshootItem("2.", "Keep the device within 1 meter") TroubleshootItem("2.", stringResource(R.string.device_scan_troubleshoot_2))
TroubleshootItem("3.", "Check Bluetooth is enabled in Settings") TroubleshootItem("3.", stringResource(R.string.device_scan_troubleshoot_3))
TroubleshootItem("4.", "Try restarting the device") TroubleshootItem("4.", stringResource(R.string.device_scan_troubleshoot_4))
} }
} }
if (appState.isDevMode) { if (appState.isDevMode) {
@@ -293,7 +293,7 @@ fun DeviceScanView(appState: AppState) {
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(containerColor = MlPrimary.copy(alpha = 0.15f)) colors = ButtonDefaults.buttonColors(containerColor = MlPrimary.copy(alpha = 0.15f))
) { ) {
Text("Enter Measurement Mode (Dev)", color = MlPrimary, fontWeight = FontWeight.SemiBold) Text(stringResource(R.string.device_scan_enter_measurement_dev), color = MlPrimary, fontWeight = FontWeight.SemiBold)
} }
} }
} }
@@ -305,7 +305,7 @@ fun DeviceScanView(appState: AppState) {
) { ) {
if (knownDevices.isNotEmpty()) { if (knownDevices.isNotEmpty()) {
item("known_header") { item("known_header") {
SectionHeader("Saved Devices", knownDevices.size) SectionHeader(stringResource(R.string.device_scan_saved_devices), knownDevices.size)
} }
items(knownDevices, key = { "k_${it.address}" }) { known -> items(knownDevices, key = { "k_${it.address}" }) { known ->
KnownDeviceRow( KnownDeviceRow(
@@ -323,7 +323,7 @@ fun DeviceScanView(appState: AppState) {
if (newDevices.isNotEmpty()) { if (newDevices.isNotEmpty()) {
item("new_header") { item("new_header") {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
SectionHeader("New Devices", newDevices.size) SectionHeader(stringResource(R.string.device_scan_new_devices), newDevices.size)
} }
items(newDevices, key = { "n_${it.address}" }) { device -> items(newDevices, key = { "n_${it.address}" }) { device ->
DeviceRow( DeviceRow(
@@ -390,9 +390,9 @@ fun DeviceScanView(appState: AppState) {
) { ) {
Text( Text(
when { when {
!permissionsGranted -> "Grant Permissions" !permissionsGranted -> stringResource(R.string.device_scan_grant_permissions)
isScanning -> "Stop Scan" isScanning -> stringResource(R.string.device_scan_stop_scan)
else -> "Start Scan" else -> stringResource(R.string.device_scan_start_scan)
}, },
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
@@ -451,7 +451,7 @@ private fun KnownDeviceRow(
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Text(device.name, fontSize = 15.sp, fontWeight = FontWeight.SemiBold) Text(device.name, fontSize = 15.sp, fontWeight = FontWeight.SemiBold)
Text("Last: $lastConnectedLabel", fontSize = 11.sp, color = MlSecondaryText) Text(stringResource(R.string.device_scan_last_label, lastConnectedLabel), fontSize = 11.sp, color = MlSecondaryText)
} }
if (isConnecting) { if (isConnecting) {
CircularProgressIndicator(modifier = Modifier.size(22.dp), strokeWidth = 2.dp) CircularProgressIndicator(modifier = Modifier.size(22.dp), strokeWidth = 2.dp)
@@ -461,7 +461,7 @@ private fun KnownDeviceRow(
enabled = enabled, enabled = enabled,
modifier = Modifier.size(36.dp) modifier = Modifier.size(36.dp)
) { ) {
Icon(Icons.Default.Delete, "Delete pairing", Icon(Icons.Default.Delete, stringResource(R.string.device_scan_delete_pairing_desc),
tint = Color.Red.copy(alpha = 0.7f), tint = Color.Red.copy(alpha = 0.7f),
modifier = Modifier.size(18.dp)) modifier = Modifier.size(18.dp))
} }
@@ -501,7 +501,7 @@ private fun DeviceRow(device: BleDevice, isConnecting: Boolean, enabled: Boolean
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
SignalBars(rssi = device.rssi) SignalBars(rssi = device.rssi)
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
Text(signalLabel(device.rssi), fontSize = 12.sp, color = MlSecondaryText) Text(stringResource(signalLabelRes(device.rssi)), fontSize = 12.sp, color = MlSecondaryText)
} }
} }
@@ -545,6 +545,13 @@ private fun signalLabel(rssi: Int): String = when {
else -> "Weak" else -> "Weak"
} }
/** UI 용 — Locale 적용된 라벨. signalLabel(rssi) 는 dev/log 용으로 유지. */
private fun signalLabelRes(rssi: Int): Int = when {
rssi >= -80 -> R.string.device_scan_signal_good
rssi >= -90 -> R.string.device_scan_signal_fair
else -> R.string.device_scan_signal_weak
}
@Composable @Composable
private fun TroubleshootItem(number: String, text: String) { private fun TroubleshootItem(number: String, text: String) {
Row(modifier = Modifier.padding(vertical = 4.dp)) { Row(modifier = Modifier.padding(vertical = 4.dp)) {
+31
View File
@@ -367,4 +367,35 @@
<string name="personalization_back_desc">뒤로</string> <string name="personalization_back_desc">뒤로</string>
<string name="personalization_max_capacity">최대 방광 용량</string> <string name="personalization_max_capacity">최대 방광 용량</string>
<string name="personalization_voiding_reminder_title">배뇨 알림 기준</string> <string name="personalization_voiding_reminder_title">배뇨 알림 기준</string>
<!-- Device Scan -->
<string name="device_scan_title">기기 연결</string>
<string name="device_scan_searching">주변 기기를 찾는 중…</string>
<string name="device_scan_tap_or_scan">저장된 기기를 탭하거나 새 기기를 검색하세요</string>
<string name="device_scan_pairing_required_title">페어링 필요</string>
<string name="device_scan_pairing_required_msg">블루투스 페어링 요청이 표시됩니다.\n\'페어링\' 을 누르고, PIN 이 나타나면 입력하세요.</string>
<string name="device_scan_pairing_required_err">페어링이 필요합니다. 기기를 페어링해주세요.</string>
<string name="device_scan_back_desc">뒤로</string>
<string name="device_scan_warning_desc">경고</string>
<string name="device_scan_error_desc">오류</string>
<string name="device_scan_dismiss_desc">닫기</string>
<string name="device_scan_bluetooth_off">블루투스가 꺼져 있습니다</string>
<string name="device_scan_permissions_required">권한이 필요합니다</string>
<string name="device_scan_scanning">검색 중…</string>
<string name="device_scan_no_devices">기기를 찾을 수 없습니다</string>
<string name="device_scan_troubleshoot_1">기기 전원이 켜져 있는지 확인하세요</string>
<string name="device_scan_troubleshoot_2">기기를 1 미터 이내에 두세요</string>
<string name="device_scan_troubleshoot_3">설정에서 블루투스를 켜주세요</string>
<string name="device_scan_troubleshoot_4">기기를 다시 켜보세요</string>
<string name="device_scan_enter_measurement_dev">측정 모드 진입 (Dev)</string>
<string name="device_scan_saved_devices">저장된 기기</string>
<string name="device_scan_new_devices">새 기기</string>
<string name="device_scan_grant_permissions">권한 허용</string>
<string name="device_scan_stop_scan">검색 중지</string>
<string name="device_scan_start_scan">검색 시작</string>
<string name="device_scan_last_label">최근: %1$s</string>
<string name="device_scan_delete_pairing_desc">페어링 삭제</string>
<string name="device_scan_signal_good">좋음</string>
<string name="device_scan_signal_fair">보통</string>
<string name="device_scan_signal_weak">약함</string>
</resources> </resources>
+31
View File
@@ -367,4 +367,35 @@
<string name="personalization_back_desc">Back</string> <string name="personalization_back_desc">Back</string>
<string name="personalization_max_capacity">Maximum Bladder Capacity</string> <string name="personalization_max_capacity">Maximum Bladder Capacity</string>
<string name="personalization_voiding_reminder_title">Voiding Reminder</string> <string name="personalization_voiding_reminder_title">Voiding Reminder</string>
<!-- Device Scan -->
<string name="device_scan_title">Connect Device</string>
<string name="device_scan_searching">Searching for nearby devices...</string>
<string name="device_scan_tap_or_scan">Tap a saved device or scan for new</string>
<string name="device_scan_pairing_required_title">Pairing Required</string>
<string name="device_scan_pairing_required_msg">A Bluetooth pairing request will appear.\nTap \'Pair\', then enter PIN if prompted.</string>
<string name="device_scan_pairing_required_err">Pairing required. Please pair the device.</string>
<string name="device_scan_back_desc">Back</string>
<string name="device_scan_warning_desc">Warning</string>
<string name="device_scan_error_desc">Error</string>
<string name="device_scan_dismiss_desc">Dismiss</string>
<string name="device_scan_bluetooth_off">Bluetooth is off</string>
<string name="device_scan_permissions_required">Permissions required</string>
<string name="device_scan_scanning">Scanning...</string>
<string name="device_scan_no_devices">No devices found</string>
<string name="device_scan_troubleshoot_1">Make sure the device is powered on</string>
<string name="device_scan_troubleshoot_2">Keep the device within 1 meter</string>
<string name="device_scan_troubleshoot_3">Check Bluetooth is enabled in Settings</string>
<string name="device_scan_troubleshoot_4">Try restarting the device</string>
<string name="device_scan_enter_measurement_dev">Enter Measurement Mode (Dev)</string>
<string name="device_scan_saved_devices">Saved Devices</string>
<string name="device_scan_new_devices">New Devices</string>
<string name="device_scan_grant_permissions">Grant Permissions</string>
<string name="device_scan_stop_scan">Stop Scan</string>
<string name="device_scan_start_scan">Start Scan</string>
<string name="device_scan_last_label">Last: %1$s</string>
<string name="device_scan_delete_pairing_desc">Delete pairing</string>
<string name="device_scan_signal_good">Good</string>
<string name="device_scan_signal_fair">Fair</string>
<string name="device_scan_signal_weak">Weak</string>
</resources> </resources>