캐릭터 상세 - 폰 언어 설정에 따라 번역 데이터를 조회하도록 수정

This commit is contained in:
2025-12-11 20:09:58 +09:00
parent 6f67c4e8e1
commit e2c7134f61
6 changed files with 73 additions and 17 deletions

View File

@@ -6,12 +6,12 @@ import kr.co.vividnext.sodalive.chat.character.detail.gallery.CharacterImageList
import kr.co.vividnext.sodalive.chat.character.detail.gallery.CharacterImagePurchaseRequest
import kr.co.vividnext.sodalive.chat.character.detail.gallery.CharacterImagePurchaseResponse
import kr.co.vividnext.sodalive.common.ApiResponse
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.Body
import retrofit2.http.POST
interface CharacterApi {
@GET("/api/chat/character/main")
@@ -22,7 +22,8 @@ interface CharacterApi {
@GET("/api/chat/character/{characterId}")
fun getCharacterDetail(
@Header("Authorization") authHeader: String,
@Path("characterId") characterId: Long
@Path("characterId") characterId: Long,
@Query("languageCode") languageCode: String
): Single<ApiResponse<CharacterDetailResponse>>
@GET("/api/chat/character/image/list")

View File

@@ -23,6 +23,7 @@ import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity.Co
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.common.Utils.getCurrentLanguageCode
import kr.co.vividnext.sodalive.databinding.FragmentCharacterDetailBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import org.koin.android.ext.android.inject
@@ -78,7 +79,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
setupView()
bindObservers()
viewModel.load(characterId)
viewModel.load(characterId, languageCode = getCurrentLanguageCode(requireContext()))
}
@SuppressLint("SetTextI18n")
@@ -117,7 +118,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
// 기본 정보
if (detail.gender != null) {
binding.tvGender.visibility = View.VISIBLE
binding.tvGender.text = detail.gender
binding.tvGender.text = detail.translated?.gender ?: detail.gender
if (detail.gender == "남성") {
binding.tvGender.setTextColor(
@@ -164,7 +165,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
View.VISIBLE
}
binding.tvCharacterName.text = detail.name
binding.tvCharacterName.text = detail.translated?.name ?: detail.name
binding.tvCharacterStatus.text = when (detail.characterType) {
CharacterType.CLONE -> getString(R.string.chat_character_type_clone)
CharacterType.CHARACTER -> getString(R.string.chat_character_type_character)
@@ -176,11 +177,13 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
CharacterType.CHARACTER -> R.drawable.bg_character_status_character
}
)
binding.tvCharacterDescription.text = detail.description
binding.tvCharacterTags.text = detail.tags
binding.tvCharacterDescription.text = detail.translated?.description
?: detail.description
binding.tvCharacterTags.text = detail.translated?.tags ?: detail.tags
// 세계관 내용과 버튼 가시성 초기화
val worldviewText = detail.backgrounds?.description.orEmpty()
val worldviewText = detail.translated?.background?.description
?: detail.backgrounds?.description.orEmpty()
binding.tvWorldviewContent.text = worldviewText
// 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용
binding.tvWorldviewContent.post {
@@ -196,7 +199,8 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
}
// 성격 내용과 버튼 가시성 초기화
val personalityText = detail.personalities?.description.orEmpty()
val personalityText = detail.translated?.personality?.description
?: detail.personalities?.description.orEmpty()
binding.tvPersonalityContent.text = personalityText
// 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용
binding.tvPersonalityContent.post {
@@ -313,7 +317,10 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
if (resp.success) {
binding.etCommentInput.setText("")
showToast(getString(R.string.character_detail_comment_register_success))
viewModel.load(targetCharacterId)
viewModel.load(
targetCharacterId,
languageCode = getCurrentLanguageCode(requireContext())
)
} else {
showToast(
resp.message ?: getString(R.string.common_error_request)

View File

@@ -8,8 +8,15 @@ class CharacterDetailRepository(
private val characterApi: CharacterApi,
private val talkApi: TalkApi
) {
fun getCharacterDetail(token: String, characterId: Long) =
characterApi.getCharacterDetail(authHeader = token, characterId = characterId)
fun getCharacterDetail(
token: String,
characterId: Long,
languageCode: String
) = characterApi.getCharacterDetail(
authHeader = token,
characterId = characterId,
languageCode = languageCode
)
fun createChatRoom(token: String, request: CreateChatRoomRequest) =
talkApi.createChatRoom(authHeader = token, request = request)

View File

@@ -21,7 +21,8 @@ data class CharacterDetailResponse(
@SerializedName("characterType") val characterType: CharacterType,
@SerializedName("others") val others: List<OtherCharacter>,
@SerializedName("latestComment") val latestComment: CharacterCommentResponse?,
@SerializedName("totalComments") val totalComments: Int
@SerializedName("totalComments") val totalComments: Int,
@SerializedName("translated") val translated: TranslatedAiCharacterDetail?
)
@Keep
@@ -51,3 +52,25 @@ data class CharacterBackgroundResponse(
@SerializedName("topic") val topic: String,
@SerializedName("description") val description: String
)
@Keep
data class TranslatedAiCharacterDetail(
@SerializedName("name") val name: String?,
@SerializedName("description") val description: String?,
@SerializedName("gender") val gender: String?,
@SerializedName("personality") val personality: TranslatedAiCharacterPersonality?,
@SerializedName("background") val background: TranslatedAiCharacterBackground?,
@SerializedName("tags") val tags: String?
)
@Keep
data class TranslatedAiCharacterPersonality(
@SerializedName("trait") val trait: String?,
@SerializedName("description") val description: String?
)
@Keep
data class TranslatedAiCharacterBackground(
@SerializedName("topic") val topic: String?,
@SerializedName("description") val description: String?
)

View File

@@ -8,8 +8,8 @@ import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseViewModel
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.UiText
/**
* 캐릭터 상세 화면에서 사용하는 ViewModel.
@@ -33,12 +33,16 @@ class CharacterDetailViewModel(
private val _uiState = MutableLiveData(UiState())
val uiState: LiveData<UiState> get() = _uiState
fun load(characterId: Long) {
fun load(characterId: Long, languageCode: String) {
_uiState.value = _uiState.value?.copy(isLoading = true, error = null)
val token = "Bearer ${SharedPreferenceManager.token}"
compositeDisposable.add(
repository.getCharacterDetail(token = token, characterId = characterId)
repository.getCharacterDetail(
token = token,
characterId = characterId,
languageCode = languageCode
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
@@ -46,6 +50,7 @@ class CharacterDetailViewModel(
val success = response.success
val data = response.data
if (success && data != null) {
Logger.d("character detail: $data")
_uiState.value = UiState(detail = data, isLoading = false, error = null)
} else {
_uiState.value = UiState(

View File

@@ -1,5 +1,7 @@
package kr.co.vividnext.sodalive.common
import android.content.Context
import android.os.Build
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
@@ -38,4 +40,15 @@ object Utils {
return "https://voiceon.onelink.me/RkTm?$encodedParams"
}
fun getCurrentLanguageCode(context: Context): String {
val config = context.resources.configuration
val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.locales.get(0)
} else {
@Suppress("DEPRECATION")
config.locale
}
return locale.language // "ko", "en" 등
}
}