클라이언트 메시지 다국어 처리

공개 API 변경 없음.
This commit is contained in:
2025-12-22 23:12:29 +09:00
parent 93e0411337
commit 4dcf9f6ed1
11 changed files with 165 additions and 54 deletions

View File

@@ -389,6 +389,88 @@ class SodaMessageSource {
)
)
private val messageMessages = mapOf(
"message.error.recipient_not_found" to mapOf(
Lang.KO to "받는 사람이 없습니다.",
Lang.EN to "Recipient not found.",
Lang.JA to "受信者が見つかりません。"
),
"message.error.recipient_inactive" to mapOf(
Lang.KO to "탈퇴한 유저에게는 메시지를 보내실 수 없습니다.",
Lang.EN to "You cannot send messages to a deactivated user.",
Lang.JA to "退会したユーザーにはメッセージを送れません。"
),
"message.error.blocked_by_recipient" to mapOf(
Lang.KO to "%s님의 요청으로 메시지를 보낼 수 없습니다.",
Lang.EN to "You cannot send messages at %s's request.",
Lang.JA to "%sの要請によりメッセージを送信できません。"
),
"message.fcm.title" to mapOf(
Lang.KO to "메시지",
Lang.EN to "Message",
Lang.JA to "メッセージ"
),
"message.fcm.text_received" to mapOf(
Lang.KO to "%s님으로 부터 문자메시지가 도착했습니다.",
Lang.EN to "You have received a text message from %s.",
Lang.JA to "%sからテキストメッセージが届きました。"
),
"message.fcm.voice_received" to mapOf(
Lang.KO to "%s님으로 부터 음성메시지가 도착했습니다.",
Lang.EN to "You have received a voice message from %s.",
Lang.JA to "%sからボイスメッセージが届きました。"
),
"message.error.not_found_retry" to mapOf(
Lang.KO to "해당하는 메시지가 없습니다.\n다시 확인해 주시기 바랍니다.",
Lang.EN to "Message not found. Please check again.",
Lang.JA to "該当するメッセージがありません。\nもう一度ご確認ください。"
),
"message.error.already_kept" to mapOf(
Lang.KO to "이미 보관된 메시지 입니다.",
Lang.EN to "Message is already kept.",
Lang.JA to "すでに保管されたメッセージです。"
)
)
private val noticeMessages = mapOf(
"notice.error.title_required" to mapOf(
Lang.KO to "제목을 입력하세요.",
Lang.EN to "Please enter a title.",
Lang.JA to "タイトルを入力してください。"
),
"notice.error.content_required" to mapOf(
Lang.KO to "내용을 입력하세요.",
Lang.EN to "Please enter content.",
Lang.JA to "内容を入力してください。"
),
"notice.error.update_required" to mapOf(
Lang.KO to "수정할 내용을 입력하세요.",
Lang.EN to "Please enter content to update.",
Lang.JA to "修正する内容を入力してください。"
)
)
private val reportMessages = mapOf(
"report.received" to mapOf(
Lang.KO to "신고가 접수되었습니다.",
Lang.EN to "Your report has been received.",
Lang.JA to "通報が受け付けられました。"
)
)
private val imageValidationMessages = mapOf(
"image.error.only_image_allowed" to mapOf(
Lang.KO to "이미지 파일만 업로드할 수 있습니다.",
Lang.EN to "Only image files can be uploaded.",
Lang.JA to "画像ファイルのみアップロードできます。"
),
"image.error.gif_paid_only" to mapOf(
Lang.KO to "GIF 파일은 유료 게시물만 업로드 할 수 있습니다.",
Lang.EN to "GIF files can be uploaded only for paid posts.",
Lang.JA to "GIFファイルは有料投稿のみアップロードできます。"
)
)
fun getMessage(key: String, lang: Lang): String? {
val messageGroups = listOf(
commonMessages,
@@ -410,7 +492,11 @@ class SodaMessageSource {
adminContentSeriesMessages,
adminContentSeriesBannerMessages,
adminContentSeriesGenreMessages,
adminContentThemeMessages
adminContentThemeMessages,
messageMessages,
noticeMessages,
reportMessages,
imageValidationMessages
)
for (messages in messageGroups) {
val translations = messages[key] ?: continue

View File

@@ -12,7 +12,7 @@ class MenuService(
) {
fun getMenus(user: User): List<GetMenuResponse> {
val member = memberRepository.findByEmail(user.username)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
return repository.getMenu(member.role)
}
}

View File

@@ -25,7 +25,7 @@ class MessageController(private val service: MessageService) {
@RequestBody request: SendTextMessageRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.sendTextMessage(request, member))
}
@@ -36,7 +36,7 @@ class MessageController(private val service: MessageService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getSentTextMessages(member, pageable, timezone))
}
@@ -47,7 +47,7 @@ class MessageController(private val service: MessageService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getReceivedTextMessages(member, pageable, timezone))
}
@@ -58,7 +58,7 @@ class MessageController(private val service: MessageService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getKeepTextMessages(member, pageable, timezone))
}
@@ -68,7 +68,7 @@ class MessageController(private val service: MessageService) {
@PathVariable id: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.keepTextMessage(id, member))
}
@@ -78,7 +78,7 @@ class MessageController(private val service: MessageService) {
@RequestPart("request") requestString: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.sendVoiceMessage(voiceMessageFile, requestString, member))
}
@@ -89,7 +89,7 @@ class MessageController(private val service: MessageService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getSentVoiceMessages(member, pageable, timezone))
}
@@ -99,7 +99,7 @@ class MessageController(private val service: MessageService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getReceivedVoiceMessages(member, pageable, timezone))
}
@@ -110,7 +110,7 @@ class MessageController(private val service: MessageService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getKeepVoiceMessages(member, pageable, timezone))
}
@@ -120,7 +120,7 @@ class MessageController(private val service: MessageService) {
@PathVariable id: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.keepVoiceMessage(id, member))
}
@@ -129,7 +129,7 @@ class MessageController(private val service: MessageService) {
@PathVariable messageId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.deleteMessage(messageId, member))
}

View File

@@ -6,6 +6,8 @@ import kr.co.vividnext.sodalive.aws.s3.S3Uploader
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.fcm.FcmEvent
import kr.co.vividnext.sodalive.fcm.FcmEventType
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
@@ -26,6 +28,8 @@ class MessageService(
private val repository: MessageRepository,
private val memberRepository: MemberRepository,
private val blockMemberRepository: BlockMemberRepository,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
private val applicationEventPublisher: ApplicationEventPublisher,
private val objectMapper: ObjectMapper,
@@ -39,17 +43,21 @@ class MessageService(
@Transactional
fun sendTextMessage(request: SendTextMessageRequest, member: Member) {
val recipient = memberRepository.findByIdOrNull(request.recipientId)
?: throw SodaException("받는 사람이 없습니다.")
?: throw SodaException(messageKey = "message.error.recipient_not_found")
if (!recipient.isActive) {
throw SodaException("탈퇴한 유저에게는 메시지를 보내실 수 없습니다.")
throw SodaException(messageKey = "message.error.recipient_inactive")
}
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = request.recipientId)
if (isBlocked) throw SodaException("${recipient.nickname}님의 요청으로 메시지를 보낼 수 없습니다.")
if (isBlocked) {
val messageTemplate = messageSource.getMessage("message.error.blocked_by_recipient", langContext.lang).orEmpty()
val message = String.format(messageTemplate, recipient.nickname)
throw SodaException(message = message)
}
val sender = memberRepository.findByIdOrNull(member.id!!)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
val message = Message(
textMessage = request.textMessage,
@@ -64,8 +72,11 @@ class MessageService(
applicationEventPublisher.publishEvent(
FcmEvent(
type = FcmEventType.SEND_MESSAGE,
title = "메시지",
message = "${sender.nickname}님으로 부터 문자메시지가 도착했습니다.",
title = messageSource.getMessage("message.fcm.title", langContext.lang).orEmpty(),
message = run {
val messageTemplate = messageSource.getMessage("message.fcm.text_received", langContext.lang).orEmpty()
String.format(messageTemplate, sender.nickname)
},
messageId = message.id
)
)
@@ -99,17 +110,21 @@ class MessageService(
val request = objectMapper.readValue(requestString, SendVoiceMessageRequest::class.java)
val recipient = memberRepository.findByIdOrNull(request.recipientId)
?: throw SodaException("받는 사람이 없습니다.")
?: throw SodaException(messageKey = "message.error.recipient_not_found")
if (!recipient.isActive) {
throw SodaException("탈퇴한 유저에게는 메시지를 보내실 수 없습니다.")
throw SodaException(messageKey = "message.error.recipient_inactive")
}
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = request.recipientId)
if (isBlocked) throw SodaException("${recipient.nickname}님의 요청으로 메시지를 보낼 수 없습니다.")
if (isBlocked) {
val messageTemplate = messageSource.getMessage("message.error.blocked_by_recipient", langContext.lang).orEmpty()
val message = String.format(messageTemplate, recipient.nickname)
throw SodaException(message = message)
}
val sender = memberRepository.findByIdOrNull(member.id!!)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
val message = Message(messageType = MessageType.VOICE)
message.sender = sender
@@ -132,8 +147,11 @@ class MessageService(
applicationEventPublisher.publishEvent(
FcmEvent(
type = FcmEventType.SEND_MESSAGE,
title = "메시지",
message = "${sender.nickname}님으로 부터 음성메시지가 도착했습니다.",
title = messageSource.getMessage("message.fcm.title", langContext.lang).orEmpty(),
message = run {
val messageTemplate = messageSource.getMessage("message.fcm.voice_received", langContext.lang).orEmpty()
String.format(messageTemplate, sender.nickname)
},
messageId = message.id
)
)
@@ -166,7 +184,7 @@ class MessageService(
@Transactional
fun deleteMessage(messageId: Long, member: Member) {
val message = repository.findByIdOrNull(messageId)
?: throw SodaException("해당하는 메시지가 없습니다.\n다시 확인해 주시기 바랍니다.")
?: throw SodaException(messageKey = "message.error.not_found_retry")
if (message.sender!!.id!! == member.id!!) {
message.isSenderDelete = true
@@ -247,14 +265,14 @@ class MessageService(
private fun keepMessage(messageId: Long, member: Member) {
val message = repository.findByIdOrNull(messageId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (message.recipient != member) {
throw SodaException("잘못된 요청입니다.")
throw SodaException(messageKey = "common.error.invalid_request")
}
if (message.isRecipientKeep) {
throw SodaException("이미 보관된 메시지 입니다.")
throw SodaException(messageKey = "message.error.already_kept")
}
message.isRecipientKeep = true

View File

@@ -11,8 +11,8 @@ import org.springframework.transaction.annotation.Transactional
class ServiceNoticeService(private val repository: ServiceServiceNoticeRepository) {
@Transactional
fun save(request: CreateNoticeRequest): Long {
if (request.title.isBlank()) throw SodaException("제목을 입력하세요.")
if (request.content.isBlank()) throw SodaException("내용을 입력하세요.")
if (request.title.isBlank()) throw SodaException(messageKey = "notice.error.title_required")
if (request.content.isBlank()) throw SodaException(messageKey = "notice.error.content_required")
val notice = request.toEntity()
return repository.save(notice).id!!
@@ -20,13 +20,13 @@ class ServiceNoticeService(private val repository: ServiceServiceNoticeRepositor
@Transactional
fun update(request: UpdateNoticeRequest) {
if (request.id <= 0) throw SodaException("잘못된 요청입니다.")
if (request.id <= 0) throw SodaException(messageKey = "common.error.invalid_request")
if (request.title.isNullOrBlank() && request.content.isNullOrBlank()) {
throw SodaException("수정할 내용을 입력하세요.")
throw SodaException(messageKey = "notice.error.update_required")
}
val notice = repository.findByIdOrNull(request.id)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (!request.title.isNullOrBlank()) notice.title = request.title
if (!request.content.isNullOrBlank()) notice.content = request.content
@@ -34,9 +34,9 @@ class ServiceNoticeService(private val repository: ServiceServiceNoticeRepositor
@Transactional
fun delete(id: Long) {
if (id <= 0) throw SodaException("잘못된 요청입니다.")
if (id <= 0) throw SodaException(messageKey = "common.error.invalid_request")
val notice = repository.findByIdOrNull(id)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
notice.isActive = false
}

View File

@@ -17,7 +17,7 @@ class PointController(private val service: PointService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
ApiResponse.ok(service.getPointStatus(member))
@@ -29,7 +29,7 @@ class PointController(private val service: PointService) {
@RequestParam("timezone") timezone: String
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
ApiResponse.ok(service.getPointUseStatus(member, timezone))
@@ -41,7 +41,7 @@ class PointController(private val service: PointService) {
@RequestParam("timezone") timezone: String
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
ApiResponse.ok(service.getPointRewardStatus(member, timezone))

View File

@@ -2,6 +2,8 @@ package kr.co.vividnext.sodalive.report
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.PostMapping
@@ -11,13 +13,18 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/report")
class ReportController(private val service: ReportService) {
class ReportController(
private val service: ReportService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext
) {
@PostMapping
fun report(
@RequestBody request: ReportRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(service.save(member, request), "신고가 접수되었습니다.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val message = messageSource.getMessage("report.received", langContext.lang)
ApiResponse.ok(service.save(member, request), message)
}
}

View File

@@ -20,26 +20,26 @@ class ReportService(
@Transactional
fun save(member: Member, request: ReportRequest) {
if (conditionAllIsNull(request, isNull = true) || conditionAllIsNull(request, isNull = false)) {
throw SodaException("신고가 접수되었습니다.")
throw SodaException(messageKey = "report.received")
}
val reportedAccount = if (request.reportedMemberId != null) {
memberRepository.findByIdOrNull(request.reportedMemberId)
?: throw SodaException("신고가 접수되었습니다.")
?: throw SodaException(messageKey = "report.received")
} else {
null
}
val cheers = if (request.cheersId != null) {
cheersRepository.findByIdOrNull(request.cheersId)
?: throw SodaException("신고가 접수되었습니다.")
?: throw SodaException(messageKey = "report.received")
} else {
null
}
val communityPost = if (request.communityPostId != null) {
creatorCommunityRepository.findByIdOrNull(request.communityPostId)
?: throw SodaException("신고가 접수되었습니다.")
?: throw SodaException(messageKey = "report.received")
} else {
null
}

View File

@@ -21,7 +21,7 @@ class SearchController(private val service: SearchService) {
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.searchUnified(
keyword,
@@ -40,7 +40,7 @@ class SearchController(private val service: SearchService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.searchCreatorList(
keyword,
@@ -61,7 +61,7 @@ class SearchController(private val service: SearchService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.searchContentList(
keyword,
@@ -82,7 +82,7 @@ class SearchController(private val service: SearchService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.searchSeriesList(
keyword,

View File

@@ -17,7 +17,7 @@ class UserActionController(private val service: UserActionService) {
@RequestBody request: UserActionRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
service.recordAction(
memberId = member.id!!,
@@ -25,6 +25,6 @@ class UserActionController(private val service: UserActionService) {
actionType = request.actionType
)
ApiResponse.ok(Unit, "")
ApiResponse.ok(Unit)
}
}

View File

@@ -12,10 +12,10 @@ fun validateImage(file: MultipartFile, gifAllowed: Boolean) {
val mimeType = Tika().detect(file.bytes)
if (!mimeType.startsWith("image/")) {
throw SodaException("이미지 파일만 업로드할 수 있습니다.")
throw SodaException(messageKey = "image.error.only_image_allowed")
}
if (mimeType == "image/gif" && !gifAllowed) {
throw SodaException("GIF 파일은 유료 게시물만 업로드 할 수 있습니다.")
throw SodaException(messageKey = "image.error.gif_paid_only")
}
}