i18n: add Korean/English localization via strings.xml
Replace hardcoded Korean UI strings in Piezo, CatheterizeSheet, VoidingDiary, and UrineCameraScreen with stringResource() calls. Add values/strings.xml (English) and values-ko/strings.xml (Korean). System language setting now controls app language. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+18
-16
@@ -14,12 +14,14 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.measure.VoidingRecord
|
import com.example.medilightv2android.measure.VoidingRecord
|
||||||
import com.example.medilightv2android.measure.VoidingRecordStore
|
import com.example.medilightv2android.measure.VoidingRecordStore
|
||||||
import com.example.medilightv2android.speech.SpeechRecognizerManager
|
import com.example.medilightv2android.speech.SpeechRecognizerManager
|
||||||
@@ -56,13 +58,13 @@ fun CatheterizeSheet(
|
|||||||
VoidingRecordStore.add(context, VoidingRecord(volume = volume, method = method))
|
VoidingRecordStore.add(context, VoidingRecord(volume = volume, method = method))
|
||||||
onCatheterized()
|
onCatheterized()
|
||||||
isSaved = true
|
isSaved = true
|
||||||
Toast.makeText(context, "배뇨일지에 등록되었습니다", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, context.getString(R.string.registered_diary), Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startVoice() {
|
fun startVoice() {
|
||||||
if (!hasAudioPerm) { audioPermLauncher.launch(android.Manifest.permission.RECORD_AUDIO); return }
|
if (!hasAudioPerm) { audioPermLauncher.launch(android.Manifest.permission.RECORD_AUDIO); return }
|
||||||
speechManager?.destroy()
|
speechManager?.destroy()
|
||||||
isListening = true; voiceStatus = "듣는 중..."
|
isListening = true; voiceStatus = context.getString(R.string.listening)
|
||||||
speechManager = SpeechRecognizerManager(
|
speechManager = SpeechRecognizerManager(
|
||||||
context = context,
|
context = context,
|
||||||
onResult = { candidates ->
|
onResult = { candidates ->
|
||||||
@@ -85,14 +87,14 @@ fun CatheterizeSheet(
|
|||||||
volumeText = it.volumeMl.toString()
|
volumeText = it.volumeMl.toString()
|
||||||
voiceStatus = "\"${it.rawText}\" → ${it.volumeMl} ml"
|
voiceStatus = "\"${it.rawText}\" → ${it.volumeMl} ml"
|
||||||
saveRecord(it.volumeMl, "voice")
|
saveRecord(it.volumeMl, "voice")
|
||||||
} ?: run { voiceStatus = "인식 실패: ${candidates.firstOrNull() ?: ""}" }
|
} ?: run { voiceStatus = context.getString(R.string.recognition_fail, candidates.firstOrNull() ?: "") }
|
||||||
},
|
},
|
||||||
onError = { code -> isListening = false; voiceStatus = when(code) {
|
onError = { code -> isListening = false; voiceStatus = when(code) {
|
||||||
android.speech.SpeechRecognizer.ERROR_NO_MATCH -> "인식된 음성 없음"
|
android.speech.SpeechRecognizer.ERROR_NO_MATCH -> context.getString(R.string.recognition_fail, "")
|
||||||
android.speech.SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> "시간 초과"
|
android.speech.SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> context.getString(R.string.error_code, code)
|
||||||
else -> "오류 ($code)"
|
else -> context.getString(R.string.error_code, code)
|
||||||
}},
|
}},
|
||||||
onReady = { voiceStatus = "말씀하세요..." }
|
onReady = { voiceStatus = context.getString(R.string.speak_now) }
|
||||||
)
|
)
|
||||||
speechManager?.startListening()
|
speechManager?.startListening()
|
||||||
}
|
}
|
||||||
@@ -106,7 +108,7 @@ fun CatheterizeSheet(
|
|||||||
// 저장 완료 화면
|
// 저장 완료 화면
|
||||||
Icon(Icons.Default.CheckCircle, null, Modifier.size(64.dp), tint = Color(0xFF4CAF50))
|
Icon(Icons.Default.CheckCircle, null, Modifier.size(64.dp), tint = Color(0xFF4CAF50))
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
Text("배뇨일지에 등록되었습니다", fontSize = 18.sp, fontWeight = FontWeight.SemiBold)
|
Text(stringResource(R.string.registered_diary), fontSize = 18.sp, fontWeight = FontWeight.SemiBold)
|
||||||
if (volumeText.isNotEmpty()) {
|
if (volumeText.isNotEmpty()) {
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
Text("$volumeText ml", fontSize = 32.sp, fontWeight = FontWeight.Bold, color = Color(0xFF007AFF))
|
Text("$volumeText ml", fontSize = 32.sp, fontWeight = FontWeight.Bold, color = Color(0xFF007AFF))
|
||||||
@@ -116,10 +118,10 @@ fun CatheterizeSheet(
|
|||||||
onClick = onDismiss,
|
onClick = onDismiss,
|
||||||
Modifier.fillMaxWidth().height(48.dp),
|
Modifier.fillMaxWidth().height(48.dp),
|
||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
) { Text("확인") }
|
) { Text(stringResource(R.string.confirm)) }
|
||||||
} else {
|
} else {
|
||||||
// 입력 모드 선택
|
// 입력 모드 선택
|
||||||
Text("배뇨 기록", fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.voiding_record), fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
// 3 탭 버튼
|
// 3 탭 버튼
|
||||||
@@ -135,7 +137,7 @@ fun CatheterizeSheet(
|
|||||||
) {
|
) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Icon(Icons.Default.Edit, null, Modifier.size(20.dp), tint = if (selectedTab == 0) Color.White else Color(0xFFFF9800))
|
Icon(Icons.Default.Edit, null, Modifier.size(20.dp), tint = if (selectedTab == 0) Color.White else Color(0xFFFF9800))
|
||||||
Text("수기", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = if (selectedTab == 0) Color.White else Color(0xFFFF9800))
|
Text(stringResource(R.string.manual_input), fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = if (selectedTab == 0) Color.White else Color(0xFFFF9800))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 음성
|
// 음성
|
||||||
@@ -149,7 +151,7 @@ fun CatheterizeSheet(
|
|||||||
) {
|
) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Icon(Icons.Default.SettingsVoice, null, Modifier.size(20.dp), tint = if (selectedTab == 1) Color.White else Color(0xFF007AFF))
|
Icon(Icons.Default.SettingsVoice, null, Modifier.size(20.dp), tint = if (selectedTab == 1) Color.White else Color(0xFF007AFF))
|
||||||
Text("음성", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = if (selectedTab == 1) Color.White else Color(0xFF007AFF))
|
Text(stringResource(R.string.voice_input), fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = if (selectedTab == 1) Color.White else Color(0xFF007AFF))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 카메라
|
// 카메라
|
||||||
@@ -161,7 +163,7 @@ fun CatheterizeSheet(
|
|||||||
) {
|
) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Icon(Icons.Default.CameraAlt, null, Modifier.size(20.dp), tint = Color(0xFF4CAF50))
|
Icon(Icons.Default.CameraAlt, null, Modifier.size(20.dp), tint = Color(0xFF4CAF50))
|
||||||
Text("카메라", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = Color(0xFF4CAF50))
|
Text(stringResource(R.string.camera_input), fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = Color(0xFF4CAF50))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,7 +177,7 @@ fun CatheterizeSheet(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = volumeText,
|
value = volumeText,
|
||||||
onValueChange = { volumeText = it.filter { c -> c.isDigit() }.take(4) },
|
onValueChange = { volumeText = it.filter { c -> c.isDigit() }.take(4) },
|
||||||
placeholder = { Text("예: 200", color = Color.Gray) },
|
placeholder = { Text(stringResource(R.string.volume_placeholder), color = Color.Gray) },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 32.sp, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center),
|
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 32.sp, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center),
|
||||||
@@ -193,7 +195,7 @@ fun CatheterizeSheet(
|
|||||||
modifier = Modifier.fillMaxWidth().height(48.dp),
|
modifier = Modifier.fillMaxWidth().height(48.dp),
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = RoundedCornerShape(12.dp),
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFF9800))
|
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFF9800))
|
||||||
) { Text("저장하기", fontWeight = FontWeight.SemiBold) }
|
) { Text(stringResource(R.string.save_to_diary), fontWeight = FontWeight.SemiBold) }
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
// 음성 입력
|
// 음성 입력
|
||||||
@@ -209,7 +211,7 @@ fun CatheterizeSheet(
|
|||||||
}
|
}
|
||||||
Spacer(Modifier.height(12.dp))
|
Spacer(Modifier.height(12.dp))
|
||||||
Text(
|
Text(
|
||||||
if (isListening) "듣는 중..." else "탭하여 음성 입력",
|
if (isListening) stringResource(R.string.listening) else stringResource(R.string.tap_to_speak),
|
||||||
color = Color.Gray, fontSize = 14.sp
|
color = Color.Gray, fontSize = 14.sp
|
||||||
)
|
)
|
||||||
if (voiceStatus.isNotEmpty()) {
|
if (voiceStatus.isNotEmpty()) {
|
||||||
|
|||||||
+22
-20
@@ -35,7 +35,9 @@ import com.example.medilightv2android.ui.components.BladdyRiveView
|
|||||||
import com.example.medilightv2android.ui.components.BleDisconnectionBanner
|
import com.example.medilightv2android.ui.components.BleDisconnectionBanner
|
||||||
import com.example.medilightv2android.ui.components.BleReconnectionState
|
import com.example.medilightv2android.ui.components.BleReconnectionState
|
||||||
import com.example.medilightv2android.ui.components.DonutChartView
|
import com.example.medilightv2android.ui.components.DonutChartView
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.ui.theme.*
|
import com.example.medilightv2android.ui.theme.*
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PiezoMonitoringView(appState: AppState) {
|
fun PiezoMonitoringView(appState: AppState) {
|
||||||
@@ -139,23 +141,23 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
if (showCatheterizeSheet) {
|
if (showCatheterizeSheet) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showCatheterizeSheet = false },
|
onDismissRequest = { showCatheterizeSheet = false },
|
||||||
title = { Text("배뇨 기록", fontWeight = FontWeight.Bold) },
|
title = { Text(stringResource(R.string.voiding_record), fontWeight = FontWeight.Bold) },
|
||||||
text = { Text("입력 방식을 선택하세요") },
|
text = { Text(stringResource(R.string.select_input_method)) },
|
||||||
confirmButton = {},
|
confirmButton = {},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
showCatheterizeSheet = false
|
showCatheterizeSheet = false
|
||||||
showManualInput = true
|
showManualInput = true
|
||||||
}) { Text("수기") }
|
}) { Text(stringResource(R.string.manual_input)) }
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
showCatheterizeSheet = false
|
showCatheterizeSheet = false
|
||||||
showVoiceInput = true
|
showVoiceInput = true
|
||||||
}) { Text("음성") }
|
}) { Text(stringResource(R.string.voice_input)) }
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
showCatheterizeSheet = false
|
showCatheterizeSheet = false
|
||||||
showUrineCamera = true
|
showUrineCamera = true
|
||||||
}) { Text("카메라") }
|
}) { Text(stringResource(R.string.camera_input)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -166,13 +168,13 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
var volumeText by remember { mutableStateOf(appState.estimatedVolume.toString()) }
|
var volumeText by remember { mutableStateOf(appState.estimatedVolume.toString()) }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showManualInput = false },
|
onDismissRequest = { showManualInput = false },
|
||||||
title = { Text("소변량 입력") },
|
title = { Text(stringResource(R.string.volume_input_title)) },
|
||||||
text = {
|
text = {
|
||||||
androidx.compose.foundation.layout.Row(verticalAlignment = Alignment.CenterVertically) {
|
androidx.compose.foundation.layout.Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = volumeText,
|
value = volumeText,
|
||||||
onValueChange = { volumeText = it.filter { c -> c.isDigit() }.take(4) },
|
onValueChange = { volumeText = it.filter { c -> c.isDigit() }.take(4) },
|
||||||
placeholder = { Text("예: ${appState.estimatedVolume}") },
|
placeholder = { Text(stringResource(R.string.volume_placeholder)) },
|
||||||
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(keyboardType = androidx.compose.ui.text.input.KeyboardType.Number),
|
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(keyboardType = androidx.compose.ui.text.input.KeyboardType.Number),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
@@ -189,12 +191,12 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
context, com.example.medilightv2android.measure.VoidingRecord(volume = vol, method = "manual")
|
context, com.example.medilightv2android.measure.VoidingRecord(volume = vol, method = "manual")
|
||||||
)
|
)
|
||||||
appState.resetLevel()
|
appState.resetLevel()
|
||||||
android.widget.Toast.makeText(context, "배뇨일지에 등록되었습니다", android.widget.Toast.LENGTH_SHORT).show()
|
android.widget.Toast.makeText(context, context.getString(R.string.registered_diary), android.widget.Toast.LENGTH_SHORT).show()
|
||||||
showManualInput = false
|
showManualInput = false
|
||||||
}
|
}
|
||||||
}) { Text("저장") }
|
}) { Text(stringResource(R.string.save)) }
|
||||||
},
|
},
|
||||||
dismissButton = { TextButton(onClick = { showManualInput = false }) { Text("취소") } }
|
dismissButton = { TextButton(onClick = { showManualInput = false }) { Text(stringResource(R.string.cancel)) } }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,14 +223,14 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
context, com.example.medilightv2android.measure.VoidingRecord(volume = parsed.volumeMl, method = "voice")
|
context, com.example.medilightv2android.measure.VoidingRecord(volume = parsed.volumeMl, method = "voice")
|
||||||
)
|
)
|
||||||
appState.resetLevel()
|
appState.resetLevel()
|
||||||
android.widget.Toast.makeText(context, "${parsed.volumeMl} ml 등록되었습니다", android.widget.Toast.LENGTH_SHORT).show()
|
android.widget.Toast.makeText(context, context.getString(R.string.registered_ml, parsed.volumeMl), android.widget.Toast.LENGTH_SHORT).show()
|
||||||
showVoiceInput = false
|
showVoiceInput = false
|
||||||
} else {
|
} else {
|
||||||
voiceResult = "인식 실패: ${candidates.firstOrNull() ?: ""}"
|
voiceResult = context.getString(R.string.recognition_fail, candidates.firstOrNull() ?: "")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = { code -> isListening = false; voiceResult = "오류 ($code)" },
|
onError = { code -> isListening = false; voiceResult = context.getString(R.string.error_code, code) },
|
||||||
onReady = { voiceResult = "말씀하세요..." }
|
onReady = { voiceResult = context.getString(R.string.speak_now) }
|
||||||
)
|
)
|
||||||
speechMgr?.startListening()
|
speechMgr?.startListening()
|
||||||
}
|
}
|
||||||
@@ -236,17 +238,17 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { speechMgr?.destroy(); showVoiceInput = false },
|
onDismissRequest = { speechMgr?.destroy(); showVoiceInput = false },
|
||||||
title = { Text(if (isListening) "듣는 중..." else "음성 입력") },
|
title = { Text(stringResource(if (isListening) R.string.listening else R.string.voice_input)) },
|
||||||
text = { Text(voiceResult ?: "마이크 권한을 확인하세요") },
|
text = { Text(voiceResult ?: stringResource(R.string.check_mic_permission)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
if (!isListening && voiceResult != null) {
|
if (!isListening && voiceResult != null) {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
isListening = true; voiceResult = null
|
isListening = true; voiceResult = null
|
||||||
speechMgr?.startListening()
|
speechMgr?.startListening()
|
||||||
}) { Text("다시 시도") }
|
}) { Text(stringResource(R.string.retry)) }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = { TextButton(onClick = { speechMgr?.destroy(); showVoiceInput = false }) { Text("취소") } }
|
dismissButton = { TextButton(onClick = { speechMgr?.destroy(); showVoiceInput = false }) { Text(stringResource(R.string.cancel)) } }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,10 +277,10 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Icon(Icons.Default.Home, "Home", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
Icon(Icons.Default.Home, "Home", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||||
}
|
}
|
||||||
IconButton(onClick = { appState.currentScreen = AppScreen.VOIDING_DIARY }, modifier = Modifier.size(48.dp)) {
|
IconButton(onClick = { appState.currentScreen = AppScreen.VOIDING_DIARY }, modifier = Modifier.size(48.dp)) {
|
||||||
Icon(Icons.Default.Description, contentDescription = "배뇨일지", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
Icon(Icons.Default.Description, stringResource(R.string.voiding_diary), tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||||
}
|
}
|
||||||
IconButton(onClick = { appState.currentScreen = AppScreen.REMINDER_SETTINGS }, modifier = Modifier.size(48.dp)) {
|
IconButton(onClick = { appState.currentScreen = AppScreen.REMINDER_SETTINGS }, modifier = Modifier.size(48.dp)) {
|
||||||
Icon(Icons.Default.Notifications, contentDescription = "배뇨 알리미", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
Icon(Icons.Default.Notifications, contentDescription = "Reminder", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||||
}
|
}
|
||||||
if (!isCompact) {
|
if (!isCompact) {
|
||||||
Text("Bladder Monitor", fontSize = 20.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 8.dp))
|
Text("Bladder Monitor", fontSize = 20.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 8.dp))
|
||||||
|
|||||||
+21
-19
@@ -32,8 +32,10 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.measure.SimpleMeasureService
|
import com.example.medilightv2android.measure.SimpleMeasureService
|
||||||
import com.example.medilightv2android.measure.VoidingRecord
|
import com.example.medilightv2android.measure.VoidingRecord
|
||||||
import com.example.medilightv2android.measure.VoidingRecordStore
|
import com.example.medilightv2android.measure.VoidingRecordStore
|
||||||
@@ -149,9 +151,9 @@ fun UrineCameraScreen(
|
|||||||
if (!hasCameraPerm) {
|
if (!hasCameraPerm) {
|
||||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Text("카메라 권한이 필요합니다")
|
Text(stringResource(R.string.camera_permission_needed))
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
Button(onClick = { permLauncher.launch(Manifest.permission.CAMERA) }) { Text("권한 허용") }
|
Button(onClick = { permLauncher.launch(Manifest.permission.CAMERA) }) { Text(stringResource(R.string.grant_permission)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (capturedBitmap != null) {
|
} else if (capturedBitmap != null) {
|
||||||
@@ -161,8 +163,8 @@ fun UrineCameraScreen(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
|
Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
|
||||||
IconButton(onClick = onCancel) { Icon(Icons.Default.ArrowBack, "뒤로") }
|
IconButton(onClick = onCancel) { Icon(Icons.Default.ArrowBack, stringResource(R.string.back)) }
|
||||||
Text("소변량 측정", fontWeight = FontWeight.SemiBold)
|
Text(stringResource(R.string.urine_measurement), fontWeight = FontWeight.SemiBold)
|
||||||
Spacer(Modifier.size(48.dp))
|
Spacer(Modifier.size(48.dp))
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
@@ -173,37 +175,37 @@ fun UrineCameraScreen(
|
|||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
CircularProgressIndicator(Modifier.size(24.dp))
|
CircularProgressIndicator(Modifier.size(24.dp))
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Text("분석 중...", color = Color.Gray)
|
Text(stringResource(R.string.analyzing), color = Color.Gray)
|
||||||
}
|
}
|
||||||
} else if (analysisComplete) {
|
} else if (analysisComplete) {
|
||||||
if (resultVolume != null && resultVolume!! > 0) {
|
if (resultVolume != null && resultVolume!! > 0) {
|
||||||
Text("측정 결과", color = Color.Gray, fontSize = 14.sp)
|
Text(stringResource(R.string.measurement_result), color = Color.Gray, fontSize = 14.sp)
|
||||||
Row(verticalAlignment = Alignment.Bottom) {
|
Row(verticalAlignment = Alignment.Bottom) {
|
||||||
Text("$resultVolume", fontSize = 48.sp, fontWeight = FontWeight.Bold, color = Color(0xFF007AFF))
|
Text("$resultVolume", fontSize = 48.sp, fontWeight = FontWeight.Bold, color = Color(0xFF007AFF))
|
||||||
Text(" ml", fontSize = 20.sp, color = Color.Gray, modifier = Modifier.padding(bottom = 8.dp))
|
Text(" ml", fontSize = 20.sp, color = Color.Gray, modifier = Modifier.padding(bottom = 8.dp))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text("측정 실패", fontSize = 20.sp, color = Color.Red)
|
Text(stringResource(R.string.measurement_fail), fontSize = 20.sp, color = Color.Red)
|
||||||
Text("밝은 곳에서 다시 촬영해주세요", color = Color.Gray, fontSize = 14.sp)
|
Text(stringResource(R.string.retry_in_bright), color = Color.Gray, fontSize = 14.sp)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = { capturedBitmap = null; originalBitmap = null; analysisComplete = false; resultVolume = null; isAutoCapturing = false; detectionStartTime = null; autoCaptureProgress = 0f },
|
onClick = { capturedBitmap = null; originalBitmap = null; analysisComplete = false; resultVolume = null; isAutoCapturing = false; detectionStartTime = null; autoCaptureProgress = 0f },
|
||||||
Modifier.weight(1f).height(48.dp), shape = RoundedCornerShape(12.dp)
|
Modifier.weight(1f).height(48.dp), shape = RoundedCornerShape(12.dp)
|
||||||
) { Text("다시 촬영") }
|
) { Text(stringResource(R.string.retake_photo)) }
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
val vol = resultVolume
|
val vol = resultVolume
|
||||||
if (vol != null && vol > 0) {
|
if (vol != null && vol > 0) {
|
||||||
VoidingRecordStore.add(context, VoidingRecord(volume = vol, method = "camera"))
|
VoidingRecordStore.add(context, VoidingRecord(volume = vol, method = "camera"))
|
||||||
Toast.makeText(context, "배뇨일지에 등록되었습니다", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, context.getString(R.string.registered_diary), Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
onDone(vol)
|
onDone(vol)
|
||||||
},
|
},
|
||||||
Modifier.weight(1f).height(48.dp), shape = RoundedCornerShape(12.dp),
|
Modifier.weight(1f).height(48.dp), shape = RoundedCornerShape(12.dp),
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF4CAF50))
|
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF4CAF50))
|
||||||
) { Text("저장하기", fontWeight = FontWeight.SemiBold) }
|
) { Text(stringResource(R.string.save_to_diary), fontWeight = FontWeight.SemiBold) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
@@ -242,14 +244,14 @@ fun UrineCameraScreen(
|
|||||||
|
|
||||||
// 상단
|
// 상단
|
||||||
Row(Modifier.fillMaxWidth().statusBarsPadding().padding(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.fillMaxWidth().statusBarsPadding().padding(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
IconButton(onClick = onCancel) { Icon(Icons.Default.Close, "닫기", tint = Color.White) }
|
IconButton(onClick = onCancel) { Icon(Icons.Default.Close, stringResource(R.string.close), tint = Color.White) }
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
val statusText = when {
|
val statusText = when {
|
||||||
!isModelLoaded -> "모델 로딩 중..."
|
!isModelLoaded -> stringResource(R.string.model_loading)
|
||||||
isAutoCapturing -> "자동 촬영 중..."
|
isAutoCapturing -> stringResource(R.string.auto_capturing)
|
||||||
autoCaptureProgress > 0 -> "유지하세요... ${(autoCaptureProgress * 100).toInt()}%"
|
autoCaptureProgress > 0 -> stringResource(R.string.hold_still, (autoCaptureProgress * 100).toInt())
|
||||||
currentDetection != null -> "소변컵 감지됨"
|
currentDetection != null -> stringResource(R.string.cup_detected)
|
||||||
else -> "소변컵을 찾는 중..."
|
else -> stringResource(R.string.searching_cup)
|
||||||
}
|
}
|
||||||
Text(statusText, color = Color.White, fontSize = 12.sp, fontWeight = FontWeight.SemiBold,
|
Text(statusText, color = Color.White, fontSize = 12.sp, fontWeight = FontWeight.SemiBold,
|
||||||
modifier = Modifier.background(Color.Black.copy(alpha = 0.6f), RoundedCornerShape(20.dp)).padding(horizontal = 12.dp, vertical = 6.dp))
|
modifier = Modifier.background(Color.Black.copy(alpha = 0.6f), RoundedCornerShape(20.dp)).padding(horizontal = 12.dp, vertical = 6.dp))
|
||||||
@@ -258,8 +260,8 @@ fun UrineCameraScreen(
|
|||||||
|
|
||||||
// 가이드
|
// 가이드
|
||||||
Column(Modifier.align(Alignment.Center).offset(y = 120.dp).background(Color.Black.copy(alpha = 0.5f), RoundedCornerShape(10.dp)).padding(horizontal = 16.dp, vertical = 8.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(Modifier.align(Alignment.Center).offset(y = 120.dp).background(Color.Black.copy(alpha = 0.5f), RoundedCornerShape(10.dp)).padding(horizontal = 16.dp, vertical = 8.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Text("눈금이 보이는 면을 정면으로", color = Color.White, fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
Text(stringResource(R.string.camera_guide_1), color = Color.White, fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||||
Text("밝은 곳에서 촬영해주세요", color = Color(0xFFFFD54F), fontSize = 12.sp)
|
Text(stringResource(R.string.camera_guide_2), color = Color(0xFFFFD54F), fontSize = 12.sp)
|
||||||
if (autoCaptureProgress > 0) {
|
if (autoCaptureProgress > 0) {
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
LinearProgressIndicator(progress = { autoCaptureProgress }, Modifier.fillMaxWidth(0.6f).height(4.dp).clip(RoundedCornerShape(2.dp)), color = Color.Green, trackColor = Color.White.copy(alpha = 0.3f))
|
LinearProgressIndicator(progress = { autoCaptureProgress }, Modifier.fillMaxWidth(0.6f).height(4.dp).clip(RoundedCornerShape(2.dp)), color = Color.Green, trackColor = Color.White.copy(alpha = 0.3f))
|
||||||
|
|||||||
+17
-17
@@ -15,10 +15,12 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.measure.VoidingRecord
|
import com.example.medilightv2android.measure.VoidingRecord
|
||||||
import com.example.medilightv2android.measure.VoidingRecordStore
|
import com.example.medilightv2android.measure.VoidingRecordStore
|
||||||
import com.example.medilightv2android.ui.theme.MlCardBackground
|
import com.example.medilightv2android.ui.theme.MlCardBackground
|
||||||
@@ -38,8 +40,6 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
|||||||
var showDeleteConfirm by remember { mutableStateOf<String?>(null) }
|
var showDeleteConfirm by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
val dateFormat = remember { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) }
|
val dateFormat = remember { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) }
|
||||||
val displayDateFormat = remember { SimpleDateFormat("M월 d일 (E)", Locale.KOREA) }
|
|
||||||
|
|
||||||
val grouped = remember(records) {
|
val grouped = remember(records) {
|
||||||
records.groupBy { dateFormat.format(Date(it.timestamp)) }
|
records.groupBy { dateFormat.format(Date(it.timestamp)) }
|
||||||
.toSortedMap(compareByDescending { it })
|
.toSortedMap(compareByDescending { it })
|
||||||
@@ -51,9 +51,9 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("배뇨일지", fontWeight = FontWeight.SemiBold) },
|
title = { Text(stringResource(R.string.voiding_diary), fontWeight = FontWeight.SemiBold) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "뒤로") }
|
IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back)) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
|||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Icon(Icons.Default.Description, contentDescription = null, Modifier.size(48.dp), Color.Gray.copy(alpha = 0.5f))
|
Icon(Icons.Default.Description, contentDescription = null, Modifier.size(48.dp), Color.Gray.copy(alpha = 0.5f))
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
Text("기록이 없습니다", color = Color.Gray)
|
Text(stringResource(R.string.no_records), color = Color.Gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -79,7 +79,7 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
|||||||
item {
|
item {
|
||||||
val displayDate = try {
|
val displayDate = try {
|
||||||
val d = dateFormat.parse(date)
|
val d = dateFormat.parse(date)
|
||||||
displayDateFormat.format(d!!)
|
SimpleDateFormat("M/d (E)", Locale.getDefault()).format(d!!)
|
||||||
} catch (_: Exception) { date }
|
} catch (_: Exception) { date }
|
||||||
|
|
||||||
Text(displayDate, fontWeight = FontWeight.Bold, fontSize = 15.sp,
|
Text(displayDate, fontWeight = FontWeight.Bold, fontSize = 15.sp,
|
||||||
@@ -106,21 +106,21 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
|||||||
record.activity == "awake" -> {
|
record.activity == "awake" -> {
|
||||||
Icon(Icons.Default.WbSunny, contentDescription = null, Modifier.size(16.dp), Color(0xFFFF9800))
|
Icon(Icons.Default.WbSunny, contentDescription = null, Modifier.size(16.dp), Color(0xFFFF9800))
|
||||||
Spacer(Modifier.width(4.dp))
|
Spacer(Modifier.width(4.dp))
|
||||||
Text("기상")
|
Text(stringResource(R.string.awake))
|
||||||
}
|
}
|
||||||
record.activity == "sleep" -> {
|
record.activity == "sleep" -> {
|
||||||
Icon(Icons.Default.DarkMode, contentDescription = null, Modifier.size(16.dp), Color(0xFF9C27B0))
|
Icon(Icons.Default.DarkMode, contentDescription = null, Modifier.size(16.dp), Color(0xFF9C27B0))
|
||||||
Spacer(Modifier.width(4.dp))
|
Spacer(Modifier.width(4.dp))
|
||||||
Text("취침")
|
Text(stringResource(R.string.sleep))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
val methodLabel = when (record.method) {
|
val methodLabel = when (record.method) {
|
||||||
"voice" -> "음성"
|
"voice" -> stringResource(R.string.method_voice)
|
||||||
"camera" -> "카메라"
|
"camera" -> stringResource(R.string.method_camera)
|
||||||
else -> "수기"
|
else -> stringResource(R.string.method_manual)
|
||||||
}
|
}
|
||||||
Text(methodLabel, fontSize = 10.sp, color = Color.Gray,
|
Text(methodLabel, fontSize = 10.sp, color = Color.Gray,
|
||||||
modifier = Modifier.background(Color.Gray.copy(alpha = 0.15f), RoundedCornerShape(4.dp))
|
modifier = Modifier.background(Color.Gray.copy(alpha = 0.15f), RoundedCornerShape(4.dp))
|
||||||
@@ -132,7 +132,7 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
|||||||
onClick = { showDeleteConfirm = record.id },
|
onClick = { showDeleteConfirm = record.id },
|
||||||
modifier = Modifier.size(48.dp)
|
modifier = Modifier.size(48.dp)
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Delete, contentDescription = "삭제", Modifier.size(20.dp), Color.Gray)
|
Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.delete), Modifier.size(20.dp), Color.Gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
|||||||
|
|
||||||
item {
|
item {
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
Text("총 ${records.size}건", color = Color.Gray, fontSize = 12.sp,
|
Text(stringResource(R.string.total_records, records.size), color = Color.Gray, fontSize = 12.sp,
|
||||||
modifier = Modifier.fillMaxWidth(), textAlign = androidx.compose.ui.text.style.TextAlign.Center)
|
modifier = Modifier.fillMaxWidth(), textAlign = androidx.compose.ui.text.style.TextAlign.Center)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,16 +151,16 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
|||||||
showDeleteConfirm?.let { id ->
|
showDeleteConfirm?.let { id ->
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showDeleteConfirm = null },
|
onDismissRequest = { showDeleteConfirm = null },
|
||||||
title = { Text("기록 삭제") },
|
title = { Text(stringResource(R.string.delete_record)) },
|
||||||
text = { Text("이 기록을 삭제하시겠습니까?") },
|
text = { Text(stringResource(R.string.delete_confirm_msg)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
VoidingRecordStore.delete(context, id)
|
VoidingRecordStore.delete(context, id)
|
||||||
records = VoidingRecordStore.getAll(context).sortedByDescending { it.timestamp }
|
records = VoidingRecordStore.getAll(context).sortedByDescending { it.timestamp }
|
||||||
showDeleteConfirm = null
|
showDeleteConfirm = null
|
||||||
}) { Text("삭제", color = Color.Red) }
|
}) { Text(stringResource(R.string.delete), color = Color.Red) }
|
||||||
},
|
},
|
||||||
dismissButton = { TextButton(onClick = { showDeleteConfirm = null }) { Text("취소") } }
|
dismissButton = { TextButton(onClick = { showDeleteConfirm = null }) { Text(stringResource(R.string.cancel)) } }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">MedilightV2</string>
|
||||||
|
|
||||||
|
<!-- Common -->
|
||||||
|
<string name="confirm">확인</string>
|
||||||
|
<string name="cancel">취소</string>
|
||||||
|
<string name="close">닫기</string>
|
||||||
|
<string name="save">저장</string>
|
||||||
|
<string name="delete">삭제</string>
|
||||||
|
<string name="retry">다시 시도</string>
|
||||||
|
<string name="back">뒤로</string>
|
||||||
|
|
||||||
|
<!-- Voiding Record -->
|
||||||
|
<string name="voiding_record">배뇨 기록</string>
|
||||||
|
<string name="select_input_method">입력 방식을 선택하세요</string>
|
||||||
|
<string name="manual_input">수기</string>
|
||||||
|
<string name="voice_input">음성</string>
|
||||||
|
<string name="camera_input">카메라</string>
|
||||||
|
<string name="volume_input_title">소변량 입력</string>
|
||||||
|
<string name="volume_placeholder">예: 200</string>
|
||||||
|
<string name="registered_diary">배뇨일지에 등록되었습니다</string>
|
||||||
|
<string name="registered_ml">%d ml 등록되었습니다</string>
|
||||||
|
<string name="recognition_fail">인식 실패: %s</string>
|
||||||
|
<string name="error_code">오류 (%d)</string>
|
||||||
|
<string name="speak_now">말씀하세요…</string>
|
||||||
|
<string name="listening">듣는 중…</string>
|
||||||
|
<string name="check_mic_permission">마이크 권한을 확인하세요</string>
|
||||||
|
<string name="voiding_diary">배뇨일지</string>
|
||||||
|
|
||||||
|
<!-- Voiding Diary List -->
|
||||||
|
<string name="no_records">기록이 없습니다</string>
|
||||||
|
<string name="delete_record">기록 삭제</string>
|
||||||
|
<string name="delete_confirm_msg">이 기록을 삭제하시겠습니까?</string>
|
||||||
|
<string name="awake">기상</string>
|
||||||
|
<string name="sleep">취침</string>
|
||||||
|
<string name="method_voice">음성</string>
|
||||||
|
<string name="method_camera">카메라</string>
|
||||||
|
<string name="method_manual">수기</string>
|
||||||
|
<string name="record">기록</string>
|
||||||
|
<string name="total_records">총 %d건</string>
|
||||||
|
<string name="urine_volume_ml">소변량 %d ml</string>
|
||||||
|
|
||||||
|
<!-- Urine Camera -->
|
||||||
|
<string name="camera_permission_needed">카메라 권한이 필요합니다</string>
|
||||||
|
<string name="grant_permission">권한 허용</string>
|
||||||
|
<string name="urine_measurement">소변량 측정</string>
|
||||||
|
<string name="analyzing">분석 중…</string>
|
||||||
|
<string name="measurement_result">측정 결과</string>
|
||||||
|
<string name="measurement_fail">측정 실패</string>
|
||||||
|
<string name="retry_in_bright">밝은 곳에서 다시 촬영해주세요</string>
|
||||||
|
<string name="retake_photo">다시 촬영</string>
|
||||||
|
<string name="save_to_diary">저장하기</string>
|
||||||
|
<string name="camera_guide_1">눈금이 보이는 면을 정면으로</string>
|
||||||
|
<string name="camera_guide_2">밝은 곳에서 촬영해주세요</string>
|
||||||
|
|
||||||
|
<!-- Catheterize Sheet -->
|
||||||
|
<string name="save_complete">저장 완료</string>
|
||||||
|
<string name="tap_to_speak">탭하여 음성 입력</string>
|
||||||
|
|
||||||
|
<!-- Camera Status -->
|
||||||
|
<string name="model_loading">모델 로딩 중…</string>
|
||||||
|
<string name="auto_capturing">자동 촬영 중…</string>
|
||||||
|
<string name="hold_still">유지하세요… %d%%</string>
|
||||||
|
<string name="cup_detected">소변컵 감지됨</string>
|
||||||
|
<string name="searching_cup">소변컵을 찾는 중…</string>
|
||||||
|
|
||||||
|
<!-- Measurement -->
|
||||||
|
<string name="too_dark">이미지가 너무 어둡습니다</string>
|
||||||
|
<string name="not_enough_marks">눈금 부족</string>
|
||||||
|
<string name="detection_fail">감지 실패</string>
|
||||||
|
</resources>
|
||||||
@@ -1,3 +1,71 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">medilightv2android</string>
|
<string name="app_name">MedilightV2</string>
|
||||||
|
|
||||||
|
<!-- Common -->
|
||||||
|
<string name="confirm">OK</string>
|
||||||
|
<string name="cancel">Cancel</string>
|
||||||
|
<string name="close">Close</string>
|
||||||
|
<string name="save">Save</string>
|
||||||
|
<string name="delete">Delete</string>
|
||||||
|
<string name="retry">Retry</string>
|
||||||
|
<string name="back">Back</string>
|
||||||
|
|
||||||
|
<!-- Voiding Record -->
|
||||||
|
<string name="voiding_record">Voiding Record</string>
|
||||||
|
<string name="select_input_method">Select input method</string>
|
||||||
|
<string name="manual_input">Manual</string>
|
||||||
|
<string name="voice_input">Voice</string>
|
||||||
|
<string name="camera_input">Camera</string>
|
||||||
|
<string name="volume_input_title">Enter Volume</string>
|
||||||
|
<string name="volume_placeholder">e.g. 200</string>
|
||||||
|
<string name="registered_diary">Registered to voiding diary</string>
|
||||||
|
<string name="registered_ml">%d ml registered</string>
|
||||||
|
<string name="recognition_fail">Recognition failed: %s</string>
|
||||||
|
<string name="error_code">Error (%d)</string>
|
||||||
|
<string name="speak_now">Speak now…</string>
|
||||||
|
<string name="listening">Listening…</string>
|
||||||
|
<string name="check_mic_permission">Check microphone permission</string>
|
||||||
|
<string name="voiding_diary">Voiding Diary</string>
|
||||||
|
|
||||||
|
<!-- Voiding Diary List -->
|
||||||
|
<string name="no_records">No records</string>
|
||||||
|
<string name="delete_record">Delete Record</string>
|
||||||
|
<string name="delete_confirm_msg">Delete this record?</string>
|
||||||
|
<string name="awake">Wake up</string>
|
||||||
|
<string name="sleep">Bedtime</string>
|
||||||
|
<string name="method_voice">Voice</string>
|
||||||
|
<string name="method_camera">Camera</string>
|
||||||
|
<string name="method_manual">Manual</string>
|
||||||
|
<string name="record">Record</string>
|
||||||
|
<string name="total_records">Total %d</string>
|
||||||
|
<string name="urine_volume_ml">Volume %d ml</string>
|
||||||
|
|
||||||
|
<!-- Urine Camera -->
|
||||||
|
<string name="camera_permission_needed">Camera permission required</string>
|
||||||
|
<string name="grant_permission">Grant Permission</string>
|
||||||
|
<string name="urine_measurement">Urine Measurement</string>
|
||||||
|
<string name="analyzing">Analyzing…</string>
|
||||||
|
<string name="measurement_result">Measurement Result</string>
|
||||||
|
<string name="measurement_fail">Measurement Failed</string>
|
||||||
|
<string name="retry_in_bright">Try again in a brighter area</string>
|
||||||
|
<string name="retake_photo">Retake Photo</string>
|
||||||
|
<string name="save_to_diary">Save</string>
|
||||||
|
<string name="camera_guide_1">Face the graduated side forward</string>
|
||||||
|
<string name="camera_guide_2">Take photo in a bright area</string>
|
||||||
|
|
||||||
|
<!-- Catheterize Sheet -->
|
||||||
|
<string name="save_complete">Saved</string>
|
||||||
|
<string name="tap_to_speak">Tap to speak</string>
|
||||||
|
|
||||||
|
<!-- Camera Status -->
|
||||||
|
<string name="model_loading">Loading model…</string>
|
||||||
|
<string name="auto_capturing">Auto capturing…</string>
|
||||||
|
<string name="hold_still">Hold still… %d%%</string>
|
||||||
|
<string name="cup_detected">Cup detected</string>
|
||||||
|
<string name="searching_cup">Searching for cup…</string>
|
||||||
|
|
||||||
|
<!-- Measurement -->
|
||||||
|
<string name="too_dark">Image is too dark</string>
|
||||||
|
<string name="not_enough_marks">Not enough marks</string>
|
||||||
|
<string name="detection_fail">Detection failed</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user