diff --git a/core/ui/src/main/kotlin/com/afternote/core/ui/calendar/BottomSheetCalendar.kt b/core/ui/src/main/kotlin/com/afternote/core/ui/calendar/BottomSheetCalendar.kt index 088bfa63c1..7e8a35a25a 100644 --- a/core/ui/src/main/kotlin/com/afternote/core/ui/calendar/BottomSheetCalendar.kt +++ b/core/ui/src/main/kotlin/com/afternote/core/ui/calendar/BottomSheetCalendar.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.selected @@ -177,7 +178,7 @@ fun DatePickerContent( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = "${currentYear}년 ${currentMonth}월", + text = stringResource(R.string.core_ui_calendar_year_month, currentYear, currentMonth), style = AfternoteDesign.typography.bodySmallR, color = AfternoteDesign.colors.gray9, modifier = Modifier.weight(1f), @@ -240,7 +241,7 @@ fun CalendarGridContent( modifier: Modifier = Modifier, ) { Column(modifier = modifier) { - val dayLabels = listOf("일", "월", "화", "수", "목", "금", "토") + val dayLabels = stringArrayResource(R.array.core_ui_calendar_day_labels) Row(modifier = Modifier.fillMaxWidth()) { dayLabels.forEach { label -> Text( diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index c7c6650282..980b7b4307 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -35,6 +35,18 @@ 다시 시도하기 설정 + + %1$d년 %2$d월 + + 일 + 월 + 화 + 수 + 목 + 금 + 토 + 수신자 선택 이름으로 검색하기 diff --git a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/detail/MemorialDetailScreen.kt b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/detail/MemorialDetailScreen.kt index d8b2fbf258..cc9ff5b7a9 100644 --- a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/detail/MemorialDetailScreen.kt +++ b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/detail/MemorialDetailScreen.kt @@ -297,7 +297,7 @@ private fun VideoCard( content = { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( - text = "장례식에 남길 영상", + text = stringResource(R.string.afternote_editor_funeral_video_label), style = AfternoteDesign.typography.textField.copy( fontWeight = FontWeight.Medium, @@ -328,7 +328,8 @@ private fun VideoThumbnail(thumbnailUrl: String?) { if (!thumbnailUrl.isNullOrBlank()) { AsyncImage( model = thumbnailUrl, - contentDescription = "장례식에 남길 영상 썸네일", + contentDescription = + stringResource(R.string.afternote_content_description_memorial_video_thumbnail), modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop, ) @@ -354,7 +355,7 @@ private fun VideoThumbnail(thumbnailUrl: String?) { // 재생 아이콘 Image( painter = painterResource(R.drawable.feature_afternote_ic_playback), - contentDescription = "영상 재생", + contentDescription = stringResource(R.string.content_description_video_play), modifier = Modifier .align(Alignment.Center) @@ -380,7 +381,7 @@ private fun PlaylistCard( content = { Column { Text( - text = "추억 플레이리스트", + text = stringResource(R.string.afternote_editor_playlist_screen_title), style = AfternoteDesign.typography.textField.copy( fontWeight = FontWeight.Medium, @@ -393,7 +394,7 @@ private fun PlaylistCard( } Spacer(Modifier.height(8.dp)) Text( - text = "현재 ${songCount}개의 노래가 담겨 있습니다.", + text = stringResource(R.string.afternote_detail_playlist_song_count, songCount), style = AfternoteDesign.typography.bodySmallR.copy( color = AfternoteDesign.colors.black, diff --git a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/EditorSectionLabel.kt b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/EditorSectionLabel.kt index ebe6c87acf..81a4999f06 100644 --- a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/EditorSectionLabel.kt +++ b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/EditorSectionLabel.kt @@ -43,14 +43,14 @@ fun EditorSectionLabel( fontWeight = FontWeight.Medium, ), ) { - val requiredMarkerDescription = - stringResource(R.string.afternote_editor_semantics_required_field) + val requiredFieldDescription = + stringResource(R.string.afternote_editor_semantics_required_field_description, text) Row( modifier = modifier.semantics(mergeDescendants = true) { if (isRequired) { - contentDescription = "$text, $requiredMarkerDescription" + contentDescription = requiredFieldDescription } }, verticalAlignment = Alignment.CenterVertically, diff --git a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/home/HomeHeaderSection.kt b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/home/HomeHeaderSection.kt index 70e0f27c2a..d21f9acc51 100644 --- a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/home/HomeHeaderSection.kt +++ b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/home/HomeHeaderSection.kt @@ -14,11 +14,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.afternote.core.ui.AfternoteOutlinedCard import com.afternote.core.ui.AfternoteSectionHeader import com.afternote.core.ui.icon.RightArrowIcon import com.afternote.core.ui.theme.AfternoteDesign +import com.afternote.feature.afternote.presentation.R /** * NEXT STEP 카드에 필요한 값 묶음. @@ -52,7 +54,7 @@ internal fun HomeHeaderSection( .padding(horizontal = 25.dp), ) { Text( - text = "애프터노트", + text = stringResource(R.string.afternote_home_title), style = AfternoteDesign.typography.h1, color = AfternoteDesign.colors.gray9, ) @@ -82,7 +84,7 @@ private fun NextStepCard( modifier = modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(4.dp), ) { - AfternoteSectionHeader(title = "NEXT STEP") + AfternoteSectionHeader(title = stringResource(R.string.afternote_home_next_step_section_title)) AfternoteOutlinedCard( onClick = onClick, contentPadding = PaddingValues(start = 16.dp, top = 16.dp, bottom = 16.dp, end = 20.dp), diff --git a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/receiver/detail/MemorialReceivedDetailScreen.kt b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/receiver/detail/MemorialReceivedDetailScreen.kt index b977f59a16..d1d35e51b7 100644 --- a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/receiver/detail/MemorialReceivedDetailScreen.kt +++ b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/receiver/detail/MemorialReceivedDetailScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.core.net.toUri @@ -82,7 +83,7 @@ fun MemorialReceivedDetailScreen( // → 탑바가 상태바(시계·배터리) 밑에서 시작(겹침 방지). 동적 인셋이라 회전·분할화면에도 대응. Column(modifier = Modifier.statusBarsPadding()) { DetailTopBar( - title = "故 ${senderName}님의 애프터노트", + title = stringResource(R.string.afternote_receiver_detail_title, senderName), onBackClick = { onBackClick() }, ) } @@ -117,7 +118,6 @@ fun MemorialReceivedDetailScreen( }, playlistContent = { MemorialPlaylist( - label = "추억 플레이리스트", songCount = songCount, albumCovers = albumCovers, onCardClick = onNavigateToPlaylist, @@ -143,7 +143,7 @@ fun MemorialReceivedDetailScreen( Spacer(modifier = Modifier.height(70.dp)) AfternoteButton( - text = "애프터노트 확인하기", + text = stringResource(R.string.afternote_receiver_detail_confirm), onClick = onNavigateToFullList, type = AfternoteButtonType.Default, ) @@ -153,14 +153,13 @@ fun MemorialReceivedDetailScreen( } } -private const val LABEL_VIDEO_SECTION = "장례식에 남길 영상" - @Composable private fun ReceiverVideoSection( memorialVideoUrl: String? = null, memorialThumbnailUrl: String? = null, ) { val context = LocalContext.current + val noVideoAppMessage = stringResource(R.string.receiver_memorial_video_no_app) Column(modifier = Modifier.fillMaxWidth()) { ReceiverSectionHeader() Spacer(modifier = Modifier.height(12.dp)) @@ -190,7 +189,7 @@ private fun ReceiverVideoSection( Toast .makeText( context, - "영상을 재생할 수 있는 앱이 없습니다.", + noVideoAppMessage, Toast.LENGTH_SHORT, ).show() }, @@ -211,7 +210,9 @@ private fun ReceiverVideoSection( ) { Icon( painter = painterResource(R.drawable.receiver_ic_play_arrow), - contentDescription = "Play", + // 영상이 없을 때만 그리는 플레이스홀더다. clickable 이 없어 재생 액션이 없으므로 + // 라벨을 붙이면 없는 어포던스를 알린다. 맥락은 위 ReceiverSectionHeader 가 읽어 준다. + contentDescription = null, tint = AfternoteDesign.colors.white, modifier = Modifier @@ -295,7 +296,8 @@ private fun ReceiverMemorialVideoThumbnail(thumbnailUrl: String?) { if (!thumbnailUrl.isNullOrBlank()) { AsyncImage( model = thumbnailUrl, - contentDescription = "장례식에 남길 영상 썸네일", + contentDescription = + stringResource(R.string.afternote_content_description_memorial_video_thumbnail), modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop, ) @@ -317,7 +319,7 @@ private fun ReceiverMemorialVideoThumbnail(thumbnailUrl: String?) { ) Image( painter = painterResource(R.drawable.feature_afternote_ic_playback), - contentDescription = "영상 재생", + contentDescription = stringResource(R.string.content_description_video_play), modifier = Modifier .align(Alignment.Center) @@ -327,7 +329,7 @@ private fun ReceiverMemorialVideoThumbnail(thumbnailUrl: String?) { } @Composable -private fun ReceiverSectionHeader(title: String = LABEL_VIDEO_SECTION) { +private fun ReceiverSectionHeader(title: String = stringResource(R.string.afternote_editor_funeral_video_label)) { Text( text = title, style = diff --git a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/shared/detail/song/MemorialPlaylist.kt b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/shared/detail/song/MemorialPlaylist.kt index 1cfaf96627..410ec9bf4b 100644 --- a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/shared/detail/song/MemorialPlaylist.kt +++ b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/shared/detail/song/MemorialPlaylist.kt @@ -50,7 +50,7 @@ import com.afternote.feature.afternote.presentation.shared.model.AlbumCover @Composable fun MemorialPlaylist( modifier: Modifier = Modifier, - label: String = "추억 플레이리스트", + label: String = stringResource(R.string.afternote_editor_playlist_screen_title), songCount: Int = 0, albumCovers: List = emptyList(), onCardClick: (() -> Unit)? = null, @@ -133,7 +133,7 @@ private fun MemorialPlaylistCardContent( ) Spacer(modifier = Modifier.size(24.dp)) Text( - text = "현재 ${songCount}개의 노래가 담겨 있습니다.", + text = stringResource(R.string.afternote_detail_playlist_song_count, songCount), style = AfternoteDesign.typography.bodySmallR.copy( color = AfternoteDesign.colors.black, diff --git a/feature/afternote/presentation/src/main/res/values/strings.xml b/feature/afternote/presentation/src/main/res/values/strings.xml index 1419e07612..04f38832ce 100644 --- a/feature/afternote/presentation/src/main/res/values/strings.xml +++ b/feature/afternote/presentation/src/main/res/values/strings.xml @@ -4,7 +4,7 @@ 홈 기록 타임 레터 - 애프터노트 + 애프터노트 홈 (준비 중) 안녕하세요, %1$s님 @@ -168,6 +168,7 @@ %d명에게 아래로 영상 재생 + 장례식에 남길 영상 썸네일 마지막 인사말을 입력해주세요 확인 @@ -222,7 +223,7 @@ 드롭다운 추가 더보기 - 필수 항목 + %1$s, 필수 항목 장례식에 남길 영상 영상 변경 영상 추가 @@ -241,6 +242,13 @@ 사진 삭제 영상 삭제 카메라를 사용할 수 없습니다. + + 현재 %1$d개의 노래가 담겨 있습니다. + NEXT STEP + + 故 %1$s님의 애프터노트 + 애프터노트 확인하기 고인이 사후 정리하고자 하는 데이터입니다. diff --git a/feature/onboarding/presentation/src/main/java/com/afternote/feature/onboarding/presentation/terms/TermsDetailScreen.kt b/feature/onboarding/presentation/src/main/java/com/afternote/feature/onboarding/presentation/terms/TermsDetailScreen.kt index c6bf26f6ba..e77426cac1 100644 --- a/feature/onboarding/presentation/src/main/java/com/afternote/feature/onboarding/presentation/terms/TermsDetailScreen.kt +++ b/feature/onboarding/presentation/src/main/java/com/afternote/feature/onboarding/presentation/terms/TermsDetailScreen.kt @@ -50,27 +50,27 @@ fun TermsDetailScreen( // 각 약관 섹션들 (반복되는 구조는 별도 컴포저블로 분리하여 가독성 확보) TermsSectionText( - title = "1. 서비스 이용약관", + title = stringResource(R.string.onboarding_terms_detail_section_1_title), content = stringResource(R.string.onboarding_terms_detail_section_1), ) TermsSectionText( - title = "2. 개인정보 수집 및 이용 동의", + title = stringResource(R.string.onboarding_terms_detail_section_2_title), content = stringResource(R.string.onboarding_terms_detail_section_2), ) TermsSectionText( - title = "3. 기록 및 콘텐츠 저장에 대한 안내", + title = stringResource(R.string.onboarding_terms_detail_section_3_title), content = stringResource(R.string.onboarding_terms_detail_section_3), ) TermsSectionText( - title = "4. 계정 및 데이터 관리", + title = stringResource(R.string.onboarding_terms_detail_section_4_title), content = stringResource(R.string.onboarding_terms_detail_section_4), ) TermsSectionText( - title = "5. 서비스 변경 및 종료", + title = stringResource(R.string.onboarding_terms_detail_section_5_title), content = stringResource(R.string.onboarding_terms_detail_section_5), ) TermsSectionText( - title = "6. 동의 철회", + title = stringResource(R.string.onboarding_terms_detail_section_6_title), content = stringResource(R.string.onboarding_terms_detail_section_6), ) diff --git a/feature/onboarding/presentation/src/main/res/values/strings.xml b/feature/onboarding/presentation/src/main/res/values/strings.xml index 230695399a..7b7a6d656d 100644 --- a/feature/onboarding/presentation/src/main/res/values/strings.xml +++ b/feature/onboarding/presentation/src/main/res/values/strings.xml @@ -100,4 +100,11 @@ 애프터노트에서의\n프로필을 설정해주세요 이름을 지정해주세요 회원가입 완료 + + 1. 서비스 이용약관 + 2. 개인정보 수집 및 이용 동의 + 3. 기록 및 콘텐츠 저장에 대한 안내 + 4. 계정 및 데이터 관리 + 5. 서비스 변경 및 종료 + 6. 동의 철회 diff --git a/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/AfternoteSection.kt b/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/AfternoteSection.kt index 7ef83765c3..fe1d8b69de 100644 --- a/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/AfternoteSection.kt +++ b/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/AfternoteSection.kt @@ -45,8 +45,8 @@ fun AfternoteSection( description = stringResource(R.string.receiver_home_afternote_section_desc), countLine = rememberCountLine( - prefix = "${countText(totalCount)}개", - suffix = "의 애프터노트가 있습니다.", + prefix = stringResource(R.string.receiver_home_afternote_count_prefix, countText(totalCount)), + suffix = stringResource(R.string.receiver_home_afternote_count_suffix), ), buttonText = stringResource(R.string.receiver_home_afternote_section_button), onButtonClick = onGoClick, diff --git a/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/MindRecordSection.kt b/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/MindRecordSection.kt index 266d778521..bdbb94fad2 100644 --- a/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/MindRecordSection.kt +++ b/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/MindRecordSection.kt @@ -29,8 +29,9 @@ fun MindRecordSection( description = stringResource(R.string.receiver_home_mindrecord_section_desc), countLine = rememberCountLine( - prefix = "${countText(summary?.totalCount)}개 ", - suffix = "마음의 기록이 있습니다.", + prefix = + stringResource(R.string.receiver_home_mindrecord_count_prefix, countText(summary?.totalCount)), + suffix = stringResource(R.string.receiver_home_mindrecord_count_suffix), ), buttonText = stringResource(R.string.receiver_home_mindrecord_section_button), onButtonClick = onGoClick, diff --git a/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/TimeLetterSection.kt b/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/TimeLetterSection.kt index 52243a6d0a..e8e8174962 100644 --- a/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/TimeLetterSection.kt +++ b/feature/receiver/presentation/src/main/kotlin/com/afternote/feature/receiver/presentation/home/component/TimeLetterSection.kt @@ -18,8 +18,9 @@ fun TimeLetterSection( description = stringResource(R.string.receiver_home_timeletter_section_desc), countLine = rememberCountLine( - prefix = "${countText(totalCount)}개 ", - suffix = "라이프 이벤트 레터가 있습니다.", + prefix = + stringResource(R.string.receiver_home_timeletter_count_prefix, countText(totalCount)), + suffix = stringResource(R.string.receiver_home_timeletter_count_suffix), ), buttonText = stringResource(R.string.receiver_home_timeletter_section_button), onButtonClick = onGoClick, diff --git a/feature/receiver/presentation/src/main/res/values/strings.xml b/feature/receiver/presentation/src/main/res/values/strings.xml index 55eeca7a51..da532b4d6e 100644 --- a/feature/receiver/presentation/src/main/res/values/strings.xml +++ b/feature/receiver/presentation/src/main/res/values/strings.xml @@ -131,4 +131,12 @@ 제출하신 서류를 확인 중입니다.\n빠르게 검토하여 열람하실 수 있도록 안내드리겠습니다. 받은 기록함으로 돌아가기 수신자 인증 진행도 4단계 중 %1$d단계 + + "%1$s개 " + 마음의 기록이 있습니다. + "%1$s개 " + 라이프 이벤트 레터가 있습니다. + %1$s개 + 의 애프터노트가 있습니다.