캐릭터 상세 문자열 리소스화

CharacterDetail/갤러리 탭 다국어 리소스 추가

UiText로 오류 메시지 지역화 처리
This commit is contained in:
2025-12-01 17:00:26 +09:00
parent 4e0e6708e6
commit 3cf24c2ab6
12 changed files with 189 additions and 54 deletions

View File

@@ -17,7 +17,7 @@ class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
val characterId = intent.getLongExtra(EXTRA_CHARACTER_ID, 0) val characterId = intent.getLongExtra(EXTRA_CHARACTER_ID, 0)
if (characterId <= 0) { if (characterId <= 0) {
showToast("잘못된 접근 입니다.") showToast(getString(R.string.character_detail_error_invalid_access))
finish() finish()
return return
} }
@@ -28,11 +28,15 @@ class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
override fun setupView() { override fun setupView() {
// 뒤로 가기 // 뒤로 가기
binding.detailToolbar.tvBack.setOnClickListener { finish() } binding.detailToolbar.tvBack.setOnClickListener { finish() }
binding.detailToolbar.tvBack.text = "캐릭터 정보" binding.detailToolbar.tvBack.text = getString(R.string.screen_character_detail_title)
// 탭 구성: 상세, 갤러리 // 탭 구성: 상세, 갤러리
binding.tabLayout.addTab(binding.tabLayout.newTab().setText("상세")) binding.tabLayout.addTab(
binding.tabLayout.addTab(binding.tabLayout.newTab().setText("갤러리")) binding.tabLayout.newTab().setText(R.string.screen_character_detail_tab_info)
)
binding.tabLayout.addTab(
binding.tabLayout.newTab().setText(R.string.screen_character_detail_tab_gallery)
)
val characterId = intent.getLongExtra(EXTRA_CHARACTER_ID, 0) val characterId = intent.getLongExtra(EXTRA_CHARACTER_ID, 0)

View File

@@ -92,10 +92,9 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
} }
// 2) 에러 토스트 처리 // 2) 에러 토스트 처리
state.error?.let { errorMsg -> state.error?.let { error ->
if (errorMsg.isNotBlank()) { val message = error.asString(requireContext())
showToast(errorMsg) if (message.isNotBlank()) showToast(message)
}
} }
// 2-1) 채팅방 생성 성공 처리 (이벤트) // 2-1) 채팅방 생성 성공 처리 (이벤트)
@@ -143,7 +142,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
if (detail.age != null) { if (detail.age != null) {
binding.tvAge.visibility = View.VISIBLE binding.tvAge.visibility = View.VISIBLE
binding.tvAge.text = "${detail.age}" binding.tvAge.text = getString(R.string.character_detail_age, detail.age)
} else { } else {
binding.tvAge.visibility = View.GONE binding.tvAge.visibility = View.GONE
} }
@@ -167,8 +166,8 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
binding.tvCharacterName.text = detail.name binding.tvCharacterName.text = detail.name
binding.tvCharacterStatus.text = when (detail.characterType) { binding.tvCharacterStatus.text = when (detail.characterType) {
CharacterType.CLONE -> "Clone" CharacterType.CLONE -> getString(R.string.chat_character_type_clone)
CharacterType.CHARACTER -> "Character" CharacterType.CHARACTER -> getString(R.string.chat_character_type_character)
} }
// 캐릭터 타입에 따른 배경 설정 // 캐릭터 타입에 따른 배경 설정
binding.tvCharacterStatus.setBackgroundResource( binding.tvCharacterStatus.setBackgroundResource(
@@ -192,7 +191,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
// 표시 상태는 항상 접힘 상태로 시작 // 표시 상태는 항상 접힘 상태로 시작
applyWorldviewCollapsedLayout() applyWorldviewCollapsedLayout()
isWorldviewExpanded = false isWorldviewExpanded = false
binding.tvWorldviewExpand.text = "더보기" binding.tvWorldviewExpand.text = getString(R.string.read_more)
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_down) binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_down)
} }
@@ -207,7 +206,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
binding.llPersonalityExpand.visibility = if (needExpand) View.VISIBLE else View.GONE binding.llPersonalityExpand.visibility = if (needExpand) View.VISIBLE else View.GONE
applyPersonalityCollapsedLayout() applyPersonalityCollapsedLayout()
isPersonalityExpanded = false isPersonalityExpanded = false
binding.tvPersonalityExpand.text = "더보기" binding.tvPersonalityExpand.text = getString(R.string.read_more)
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_down) binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_down)
} }
@@ -300,7 +299,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
val idFromState = viewModel.uiState.value?.detail?.characterId ?: 0L val idFromState = viewModel.uiState.value?.detail?.characterId ?: 0L
val targetCharacterId = if (idFromState > 0) idFromState else characterId val targetCharacterId = if (idFromState > 0) idFromState else characterId
if (targetCharacterId <= 0) { if (targetCharacterId <= 0) {
showToast("잘못된 접근 입니다.") showToast(getString(R.string.character_detail_error_invalid_access))
return@setOnClickListener return@setOnClickListener
} }
@@ -313,13 +312,15 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
.subscribe({ resp -> .subscribe({ resp ->
if (resp.success) { if (resp.success) {
binding.etCommentInput.setText("") binding.etCommentInput.setText("")
showToast("등록되었습니다.") showToast(getString(R.string.character_detail_comment_register_success))
viewModel.load(targetCharacterId) viewModel.load(targetCharacterId)
} else { } else {
showToast(resp.message ?: "요청 중 오류가 발생했습니다") showToast(
resp.message ?: getString(R.string.common_error_request)
)
} }
}, { e -> }, { e ->
showToast(e.message ?: "요청 중 오류가 발생했습니다") showToast(e.message ?: getString(R.string.common_error_request))
}) })
compositeDisposable.add(d) compositeDisposable.add(d)
} }
@@ -384,7 +385,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
if (targetId > 0) { if (targetId > 0) {
viewModel.createChatRoom(targetId) viewModel.createChatRoom(targetId)
} else { } else {
showToast("잘못된 접근 입니다.") showToast(getString(R.string.character_detail_error_invalid_access))
} }
} }
} }
@@ -395,12 +396,12 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
// 확장 상태 // 확장 상태
binding.tvWorldviewContent.maxLines = Integer.MAX_VALUE binding.tvWorldviewContent.maxLines = Integer.MAX_VALUE
binding.tvWorldviewContent.ellipsize = null binding.tvWorldviewContent.ellipsize = null
binding.tvWorldviewExpand.text = "간략히" binding.tvWorldviewExpand.text = getString(R.string.read_less)
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_up) binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_up)
} else { } else {
// 접힘 상태 (3줄) // 접힘 상태 (3줄)
applyWorldviewCollapsedLayout() applyWorldviewCollapsedLayout()
binding.tvWorldviewExpand.text = "더보기" binding.tvWorldviewExpand.text = getString(R.string.read_more)
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_down) binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_down)
} }
} }
@@ -415,11 +416,11 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
if (isPersonalityExpanded) { if (isPersonalityExpanded) {
binding.tvPersonalityContent.maxLines = Integer.MAX_VALUE binding.tvPersonalityContent.maxLines = Integer.MAX_VALUE
binding.tvPersonalityContent.ellipsize = null binding.tvPersonalityContent.ellipsize = null
binding.tvPersonalityExpand.text = "간략히" binding.tvPersonalityExpand.text = getString(R.string.read_less)
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_up) binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_up)
} else { } else {
applyPersonalityCollapsedLayout() applyPersonalityCollapsedLayout()
binding.tvPersonalityExpand.text = "더보기" binding.tvPersonalityExpand.text = getString(R.string.read_more)
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_down) binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_down)
} }
} }

View File

@@ -5,8 +5,10 @@ import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomRequest import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomRequest
import kr.co.vividnext.sodalive.common.UiText
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
/** /**
@@ -24,7 +26,7 @@ class CharacterDetailViewModel(
data class UiState( data class UiState(
val detail: CharacterDetailResponse? = null, val detail: CharacterDetailResponse? = null,
val isLoading: Boolean = false, val isLoading: Boolean = false,
val error: String? = null, val error: UiText? = null,
val chatRoomId: Long? = null val chatRoomId: Long? = null
) )
@@ -49,7 +51,9 @@ class CharacterDetailViewModel(
_uiState.value = UiState( _uiState.value = UiState(
detail = null, detail = null,
isLoading = false, isLoading = false,
error = response.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." error = response.message?.takeIf { it.isNotBlank() }
?.let { UiText.DynamicString(it) }
?: UiText.StringResource(R.string.common_error_unknown)
) )
} }
}, },
@@ -58,7 +62,7 @@ class CharacterDetailViewModel(
_uiState.value = UiState( _uiState.value = UiState(
detail = null, detail = null,
isLoading = false, isLoading = false,
error = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." error = UiText.StringResource(R.string.common_error_unknown)
) )
} }
) )
@@ -89,7 +93,9 @@ class CharacterDetailViewModel(
} else { } else {
_uiState.value = _uiState.value?.copy( _uiState.value = _uiState.value?.copy(
isLoading = false, isLoading = false,
error = response.message ?: "채팅방 생성에 실패했습니다. 다시 시도해 주세요." error = response.message?.takeIf { it.isNotBlank() }
?.let { UiText.DynamicString(it) }
?: UiText.StringResource(R.string.character_detail_chat_room_create_failed)
) )
} }
}, },
@@ -97,7 +103,7 @@ class CharacterDetailViewModel(
Logger.e(throwable, throwable.message ?: "") Logger.e(throwable, throwable.message ?: "")
_uiState.value = _uiState.value?.copy( _uiState.value = _uiState.value?.copy(
isLoading = false, isLoading = false,
error = "채팅방 생성 중 오류가 발생했습니다. 다시 시도해 주세요." error = UiText.StringResource(R.string.character_detail_chat_room_create_failed)
) )
} }
) )

View File

@@ -7,6 +7,7 @@ import android.view.View
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.base.SodaDialog import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity.Companion.EXTRA_CHARACTER_ID import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity.Companion.EXTRA_CHARACTER_ID
@@ -106,11 +107,16 @@ class CharacterGalleryFragment : BaseFragment<FragmentCharacterGalleryBinding>(
binding.clRatio.visibility = View.VISIBLE binding.clRatio.visibility = View.VISIBLE
val percent = (state.ratio * 100).toInt() val percent = (state.ratio * 100).toInt()
binding.tvRatioLeft.text = "$percent% 보유중" binding.tvRatioLeft.text =
getString(R.string.character_gallery_ratio_owned, percent)
val ownedStr = state.ownedCount.toString() val ownedStr = state.ownedCount.toString()
val totalStr = state.totalCount.toString() val totalStr = state.totalCount.toString()
val fullText = "$ownedStr / ${totalStr}" val fullText = getString(
R.string.character_gallery_ratio_count,
state.ownedCount,
state.totalCount
)
val spannable = android.text.SpannableString(fullText) val spannable = android.text.SpannableString(fullText)
val ownedColor = "#FDD453".toColorInt() val ownedColor = "#FDD453".toColorInt()
spannable.setSpan( spannable.setSpan(
@@ -132,7 +138,10 @@ class CharacterGalleryFragment : BaseFragment<FragmentCharacterGalleryBinding>(
binding.clRatio.visibility = View.GONE binding.clRatio.visibility = View.GONE
} }
state.error?.let { showToast(it) } state.error?.let { error ->
val message = error.asString(requireContext())
if (message.isNotBlank()) showToast(message)
}
} }
} }
@@ -140,13 +149,16 @@ class CharacterGalleryFragment : BaseFragment<FragmentCharacterGalleryBinding>(
SodaDialog( SodaDialog(
activity = requireActivity(), activity = requireActivity(),
layoutInflater = this.layoutInflater, layoutInflater = this.layoutInflater,
title = "구매 확인", title = getString(R.string.character_gallery_purchase_title),
desc = "선택한 이미지를 구매하시겠습니까?", desc = getString(R.string.character_gallery_purchase_desc),
confirmButtonTitle = "${item.imagePriceCan}캔으로 구매", confirmButtonTitle = getString(
R.string.character_gallery_purchase_confirm,
item.imagePriceCan
),
confirmButtonClick = { confirmButtonClick = {
viewModel.purchaseImage(item.id, position) viewModel.purchaseImage(item.id, position)
}, },
cancelButtonTitle = "취소" cancelButtonTitle = getString(R.string.cancel)
).show(screenWidth) ).show(screenWidth)
} }

View File

@@ -5,8 +5,10 @@ import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.common.UiText
class CharacterGalleryViewModel( class CharacterGalleryViewModel(
private val repository: CharacterGalleryRepository private val repository: CharacterGalleryRepository
@@ -18,7 +20,7 @@ class CharacterGalleryViewModel(
val ratio: Float = 0f, // 0.0 ~ 1.0 val ratio: Float = 0f, // 0.0 ~ 1.0
val items: List<CharacterImageListItemResponse> = emptyList(), val items: List<CharacterImageListItemResponse> = emptyList(),
val isLoading: Boolean = false, val isLoading: Boolean = false,
val error: String? = null val error: UiText? = null
) )
private val _uiState = MutableLiveData(UiState()) private val _uiState = MutableLiveData(UiState())
@@ -92,7 +94,9 @@ class CharacterGalleryViewModel(
} else { } else {
_uiState.value = _uiState.value?.copy( _uiState.value = _uiState.value?.copy(
isLoading = isRequesting || isPurchasing, isLoading = isRequesting || isPurchasing,
error = response.message ?: "갤러리 정보를 불러오지 못했습니다." error = response.message?.takeIf { it.isNotBlank() }
?.let { UiText.DynamicString(it) }
?: UiText.StringResource(R.string.character_gallery_load_error)
) )
} }
}, { throwable -> }, { throwable ->
@@ -100,7 +104,7 @@ class CharacterGalleryViewModel(
Logger.e(throwable, throwable.message ?: "") Logger.e(throwable, throwable.message ?: "")
_uiState.value = _uiState.value?.copy( _uiState.value = _uiState.value?.copy(
isLoading = isRequesting || isPurchasing, isLoading = isRequesting || isPurchasing,
error = "네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요." error = UiText.StringResource(R.string.common_error_network_retry)
) )
}) })
) )
@@ -153,7 +157,9 @@ class CharacterGalleryViewModel(
) )
} else { } else {
_uiState.value = _uiState.value?.copy( _uiState.value = _uiState.value?.copy(
error = response.message ?: "구매에 실패했습니다." error = response.message?.takeIf { it.isNotBlank() }
?.let { UiText.DynamicString(it) }
?: UiText.StringResource(R.string.character_gallery_purchase_failed)
) )
} }
}, },
@@ -162,7 +168,7 @@ class CharacterGalleryViewModel(
Logger.e(throwable, throwable.message ?: "") Logger.e(throwable, throwable.message ?: "")
_uiState.value = _uiState.value?.copy( _uiState.value = _uiState.value?.copy(
isLoading = isRequesting || isPurchasing, isLoading = isRequesting || isPurchasing,
error = "네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요." error = UiText.StringResource(R.string.common_error_network_retry)
) )
} }
) )

View File

@@ -0,0 +1,16 @@
package kr.co.vividnext.sodalive.common
import android.content.Context
import androidx.annotation.StringRes
sealed class UiText {
data class DynamicString(val value: String) : UiText()
class StringResource(@StringRes val resId: Int, vararg val args: Any) : UiText() {
val formatArgs: Array<out Any> = args
}
fun asString(context: Context): String = when (this) {
is DynamicString -> value
is StringResource -> context.getString(resId, *formatArgs)
}
}

View File

@@ -170,7 +170,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_bold" android:fontFamily="@font/pretendard_bold"
android:text="[세계관 및 작품 소개]" android:text="@string/character_detail_worldview_title"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" /> android:textSize="16sp" />
@@ -209,7 +209,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_regular" android:fontFamily="@font/pretendard_regular"
android:text="더보기" android:text="@string/read_more"
android:textColor="@color/color_607d8b" android:textColor="@color/color_607d8b"
android:textSize="14sp" /> android:textSize="14sp" />
</LinearLayout> </LinearLayout>
@@ -230,7 +230,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_bold" android:fontFamily="@font/pretendard_bold"
android:text="[원작]" android:text="@string/character_detail_original_title"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" /> android:textSize="16sp" />
@@ -256,7 +256,7 @@
android:background="@drawable/bg_round_corner_16_stroke_3bb9f1" android:background="@drawable/bg_round_corner_16_stroke_3bb9f1"
android:fontFamily="@font/pretendard_bold" android:fontFamily="@font/pretendard_bold"
android:gravity="center" android:gravity="center"
android:text="원작 보러가기" android:text="@string/character_detail_original_link"
android:textColor="@color/color_3bb9f1" android:textColor="@color/color_3bb9f1"
android:textSize="16sp" /> android:textSize="16sp" />
</LinearLayout> </LinearLayout>
@@ -276,7 +276,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_bold" android:fontFamily="@font/pretendard_bold"
android:text="[성격 및 특징]" android:text="@string/character_detail_personality_title"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" /> android:textSize="16sp" />
@@ -315,7 +315,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_regular" android:fontFamily="@font/pretendard_regular"
android:text="더보기" android:text="@string/read_more"
android:textColor="@color/color_607d8b" android:textColor="@color/color_607d8b"
android:textSize="14sp" /> android:textSize="14sp" />
</LinearLayout> </LinearLayout>
@@ -339,7 +339,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_bold" android:fontFamily="@font/pretendard_bold"
android:text="⚠️ 캐릭터톡 대화 가이드" android:text="@string/character_detail_chat_guide_title"
android:textColor="@color/color_b0bec5" android:textColor="@color/color_b0bec5"
android:textSize="16sp" /> android:textSize="16sp" />
@@ -351,7 +351,7 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:fontFamily="@font/pretendard_regular" android:fontFamily="@font/pretendard_regular"
android:lineSpacingExtra="4dp" android:lineSpacingExtra="4dp"
android:text="보이스온 AI캐릭터톡은 대화의 자유도가 높아 대화에 참여하는 당신은 누구든 될 수 있습니다.\n세계관 속 캐릭터로 대화를 하거나 새로운 인물로 캐릭터와 당신만의 스토리를 만들어보세요." android:text="@string/character_detail_chat_guide_desc"
android:textColor="@color/color_7c7c80" android:textColor="@color/color_7c7c80"
android:textSize="16sp" /> android:textSize="16sp" />
@@ -363,7 +363,7 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:fontFamily="@font/pretendard_regular" android:fontFamily="@font/pretendard_regular"
android:lineSpacingExtra="4dp" android:lineSpacingExtra="4dp"
android:text="※ AI캐릭터톡은 오픈베타 서비스 중이며, 캐릭터의 대화가 어색하거나 불완전할 수 있습니다. 대화 초반에 캐릭터 붕괴가 느껴진다면 대화를 리셋하고 다시 시도해보세요." android:text="@string/character_detail_chat_guide_notice"
android:textColor="@color/color_7c7c80" android:textColor="@color/color_7c7c80"
android:textSize="14sp" /> android:textSize="14sp" />
</LinearLayout> </LinearLayout>
@@ -393,7 +393,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_regular" android:fontFamily="@font/pretendard_regular"
android:text="댓글" android:text="@string/character_detail_comments_label"
android:textColor="@color/color_b0bec5" android:textColor="@color/color_b0bec5"
android:textSize="16sp" /> android:textSize="16sp" />
@@ -479,7 +479,7 @@
android:layout_weight="1" android:layout_weight="1"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:fontFamily="@font/pretendard_regular" android:fontFamily="@font/pretendard_regular"
android:hint="댓글을 입력해보세요" android:hint="@string/character_detail_comment_input_hint"
android:imeOptions="actionSend" android:imeOptions="actionSend"
android:importantForAutofill="no" android:importantForAutofill="no"
android:inputType="textCapSentences|textMultiLine" android:inputType="textCapSentences|textMultiLine"
@@ -523,7 +523,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:fontFamily="@font/pretendard_bold" android:fontFamily="@font/pretendard_bold"
android:text="장르의 다른 캐릭터" android:text="@string/character_detail_other_characters_title"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="26sp" /> android:textSize="26sp" />
</LinearLayout> </LinearLayout>
@@ -550,7 +550,7 @@
android:background="@drawable/bg_round_corner_16_solid_3bb9f1" android:background="@drawable/bg_round_corner_16_solid_3bb9f1"
android:fontFamily="@font/pretendard_bold" android:fontFamily="@font/pretendard_bold"
android:gravity="center" android:gravity="center"
android:text="대화하기" android:text="@string/character_detail_chat_button"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@@ -79,7 +79,7 @@
android:layout_weight="1" android:layout_weight="1"
android:fontFamily="@font/pretendard_bold" android:fontFamily="@font/pretendard_bold"
android:gravity="center" android:gravity="center"
android:text="갤러리가 비어있습니다" android:text="@string/character_gallery_empty"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="20sp" android:textSize="20sp"
android:visibility="gone" /> android:visibility="gone" />

View File

@@ -20,7 +20,7 @@
android:layout_height="40dp" android:layout_height="40dp"
android:layout_margin="16dp" android:layout_margin="16dp"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:contentDescription="close" android:contentDescription="@string/a11y_close"
android:scaleType="center" android:scaleType="center"
android:src="@android:drawable/ic_menu_close_clear_cancel" android:src="@android:drawable/ic_menu_close_clear_cancel"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"

View File

@@ -164,6 +164,36 @@
<string name="screen_home_series_complete">Complete</string> <string name="screen_home_series_complete">Complete</string>
<string name="screen_home_recommend_channel_content_label">Contents</string> <string name="screen_home_recommend_channel_content_label">Contents</string>
<string name="common_error_unknown">An unknown error occurred. Please try again.</string> <string name="common_error_unknown">An unknown error occurred. Please try again.</string>
<string name="common_error_request">An error occurred while processing the request.</string>
<string name="common_error_network_retry">A network error occurred. Please try again later.</string>
<!-- Character detail -->
<string name="screen_character_detail_title">Character info</string>
<string name="screen_character_detail_tab_info">Details</string>
<string name="screen_character_detail_tab_gallery">Gallery</string>
<string name="character_detail_error_invalid_access">Invalid access.</string>
<string name="character_detail_age">%1$d years old</string>
<string name="character_detail_worldview_title">[World &amp; Story Intro]</string>
<string name="character_detail_original_title">[Original work]</string>
<string name="character_detail_original_link">View original work</string>
<string name="character_detail_personality_title">[Personality &amp; Traits]</string>
<string name="character_detail_chat_guide_title">⚠️ Character Talk Guide</string>
<string name="character_detail_chat_guide_desc">VoiceOn AI Character Talk lets you become anyone in the conversation.\nChat as a story character or create your own role and build a story with the character.</string>
<string name="character_detail_chat_guide_notice">※ AI Character Talk is in open beta, so replies may be awkward or incomplete. If the character breaks early in the conversation, reset and try again.</string>
<string name="character_detail_comments_label">Comments</string>
<string name="character_detail_comment_input_hint">Write a comment</string>
<string name="character_detail_other_characters_title">Other characters in this genre</string>
<string name="character_detail_chat_button">Start chat</string>
<string name="character_detail_comment_register_success">Saved.</string>
<string name="character_detail_chat_room_create_failed">Failed to create a chat room. Please try again.</string>
<!-- Character gallery -->
<string name="character_gallery_empty">No images in the gallery.</string>
<string name="character_gallery_ratio_owned">%1$d%% owned</string>
<string name="character_gallery_ratio_count">%1$d / %2$d items</string>
<string name="character_gallery_purchase_title">Purchase confirmation</string>
<string name="character_gallery_purchase_desc">Do you want to buy the selected image?</string>
<string name="character_gallery_purchase_confirm">Buy with %1$d cans</string>
<string name="character_gallery_load_error">Failed to load gallery info.</string>
<string name="character_gallery_purchase_failed">Failed to complete the purchase.</string>
<string name="screen_character_tab_recent_title">Recent characters</string> <string name="screen_character_tab_recent_title">Recent characters</string>
<string name="screen_character_tab_popular_title">Popular characters</string> <string name="screen_character_tab_popular_title">Popular characters</string>
<string name="screen_character_tab_new_title">New characters</string> <string name="screen_character_tab_new_title">New characters</string>

View File

@@ -164,6 +164,36 @@
<string name="screen_home_series_complete">完結</string> <string name="screen_home_series_complete">完結</string>
<string name="screen_home_recommend_channel_content_label">コンテンツ</string> <string name="screen_home_recommend_channel_content_label">コンテンツ</string>
<string name="common_error_unknown">不明なエラーが発生しました。もう一度お試しください。</string> <string name="common_error_unknown">不明なエラーが発生しました。もう一度お試しください。</string>
<string name="common_error_request">リクエスト処理中にエラーが発生しました。</string>
<string name="common_error_network_retry">ネットワークエラーが発生しました。しばらくしてから再試行してください。</string>
<!-- Character detail -->
<string name="screen_character_detail_title">キャラクター情報</string>
<string name="screen_character_detail_tab_info">詳細</string>
<string name="screen_character_detail_tab_gallery">ギャラリー</string>
<string name="character_detail_error_invalid_access">不正なアクセスです。</string>
<string name="character_detail_age">%1$d歳</string>
<string name="character_detail_worldview_title">[世界観・作品紹介]</string>
<string name="character_detail_original_title">[原作]</string>
<string name="character_detail_original_link">原作を見に行く</string>
<string name="character_detail_personality_title">[性格・特徴]</string>
<string name="character_detail_chat_guide_title">⚠️ キャラクタートークガイド</string>
<string name="character_detail_chat_guide_desc">VoiceOn AIキャラクタートークは会話の自由度が高く、あなたは誰にでもなれます。\n世界観のキャラクターとして話すことも、新しい人物になってキャラクターと物語を作ることもできます。</string>
<string name="character_detail_chat_guide_notice">※ AIキャラクタートークはオープンベータ中のため、会話が不自然または不完全な場合があります。会話の序盤でキャラクター崩壊を感じたら、リセットして再試行してください。</string>
<string name="character_detail_comments_label">コメント</string>
<string name="character_detail_comment_input_hint">コメントを入力してください</string>
<string name="character_detail_other_characters_title">同ジャンルの他のキャラクター</string>
<string name="character_detail_chat_button">チャットする</string>
<string name="character_detail_comment_register_success">登録しました。</string>
<string name="character_detail_chat_room_create_failed">チャットルームの作成に失敗しました。もう一度お試しください。</string>
<!-- Character gallery -->
<string name="character_gallery_empty">ギャラリーが空です</string>
<string name="character_gallery_ratio_owned">%1$d%% 保有中</string>
<string name="character_gallery_ratio_count">%1$d / %2$d件</string>
<string name="character_gallery_purchase_title">購入確認</string>
<string name="character_gallery_purchase_desc">選択した画像を購入しますか?</string>
<string name="character_gallery_purchase_confirm">%1$dキャンで購入</string>
<string name="character_gallery_load_error">ギャラリー情報を読み込めませんでした。</string>
<string name="character_gallery_purchase_failed">購入に失敗しました。</string>
<string name="screen_character_tab_recent_title">最近話したキャラクター</string> <string name="screen_character_tab_recent_title">最近話したキャラクター</string>
<string name="screen_character_tab_popular_title">人気キャラクター</string> <string name="screen_character_tab_popular_title">人気キャラクター</string>
<string name="screen_character_tab_new_title">新着キャラクター</string> <string name="screen_character_tab_new_title">新着キャラクター</string>

View File

@@ -163,6 +163,36 @@
<string name="screen_home_series_complete">완결</string> <string name="screen_home_series_complete">완결</string>
<string name="screen_home_recommend_channel_content_label">콘텐츠</string> <string name="screen_home_recommend_channel_content_label">콘텐츠</string>
<string name="common_error_unknown">알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.</string> <string name="common_error_unknown">알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.</string>
<string name="common_error_request">요청 중 오류가 발생했습니다.</string>
<string name="common_error_network_retry">네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.</string>
<!-- Character detail -->
<string name="screen_character_detail_title">캐릭터 정보</string>
<string name="screen_character_detail_tab_info">상세</string>
<string name="screen_character_detail_tab_gallery">갤러리</string>
<string name="character_detail_error_invalid_access">잘못된 접근입니다.</string>
<string name="character_detail_age">%1$d세</string>
<string name="character_detail_worldview_title">[세계관 및 작품 소개]</string>
<string name="character_detail_original_title">[원작]</string>
<string name="character_detail_original_link">원작 보러가기</string>
<string name="character_detail_personality_title">[성격 및 특징]</string>
<string name="character_detail_chat_guide_title">⚠️ 캐릭터톡 대화 가이드</string>
<string name="character_detail_chat_guide_desc">보이스온 AI캐릭터톡은 대화의 자유도가 높아 대화에 참여하는 당신은 누구든 될 수 있습니다.\n세계관 속 캐릭터로 대화를 하거나 새로운 인물로 캐릭터와 당신만의 스토리를 만들어보세요.</string>
<string name="character_detail_chat_guide_notice">※ AI캐릭터톡은 오픈베타 서비스 중이며, 캐릭터의 대화가 어색하거나 불완전할 수 있습니다. 대화 초반에 캐릭터 붕괴가 느껴진다면 대화를 리셋하고 다시 시도해보세요.</string>
<string name="character_detail_comments_label">댓글</string>
<string name="character_detail_comment_input_hint">댓글을 입력해보세요</string>
<string name="character_detail_other_characters_title">장르의 다른 캐릭터</string>
<string name="character_detail_chat_button">대화하기</string>
<string name="character_detail_comment_register_success">등록되었습니다.</string>
<string name="character_detail_chat_room_create_failed">채팅방 생성에 실패했습니다. 다시 시도해 주세요.</string>
<!-- Character gallery -->
<string name="character_gallery_empty">갤러리가 비어있습니다</string>
<string name="character_gallery_ratio_owned">%1$d%% 보유중</string>
<string name="character_gallery_ratio_count">%1$d / %2$d개</string>
<string name="character_gallery_purchase_title">구매 확인</string>
<string name="character_gallery_purchase_desc">선택한 이미지를 구매하시겠습니까?</string>
<string name="character_gallery_purchase_confirm">%1$d캔으로 구매</string>
<string name="character_gallery_load_error">갤러리 정보를 불러오지 못했습니다.</string>
<string name="character_gallery_purchase_failed">구매에 실패했습니다.</string>
<string name="screen_character_tab_recent_title">최근 대화한 캐릭터</string> <string name="screen_character_tab_recent_title">최근 대화한 캐릭터</string>
<string name="screen_character_tab_popular_title">인기 캐릭터</string> <string name="screen_character_tab_popular_title">인기 캐릭터</string>
<string name="screen_character_tab_new_title">신규 캐릭터</string> <string name="screen_character_tab_new_title">신규 캐릭터</string>