diff --git a/adapter/firebase/build.gradle.kts b/adapter/firebase/build.gradle.kts index e80b41d5..566adef9 100644 --- a/adapter/firebase/build.gradle.kts +++ b/adapter/firebase/build.gradle.kts @@ -6,6 +6,7 @@ plugins { dependencies { implementation(project(":domain")) + implementation(project(":app:support:i18n")) implementation(project(":support:metric")) implementation("org.springframework.boot:spring-boot-starter:${Versions.SPRING_BOOT}") diff --git a/adapter/firebase/src/main/kotlin/com/xorker/draw/firebase/FcmService.kt b/adapter/firebase/src/main/kotlin/com/xorker/draw/firebase/FcmService.kt index cf4f2700..efbf8341 100644 --- a/adapter/firebase/src/main/kotlin/com/xorker/draw/firebase/FcmService.kt +++ b/adapter/firebase/src/main/kotlin/com/xorker/draw/firebase/FcmService.kt @@ -3,15 +3,15 @@ package com.xorker.draw.firebase import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.Message import com.google.firebase.messaging.Notification +import com.xorker.draw.i18n.MessageService import com.xorker.draw.notification.PushMessageUseCase import com.xorker.draw.websocket.WaitingQueueSession import java.util.Locale -import org.springframework.context.MessageSource import org.springframework.stereotype.Service @Service class FcmService( - private val messageSource: MessageSource, + private val messageService: MessageService, ) : PushMessageUseCase { override fun quickStart(session: WaitingQueueSession) { @@ -22,8 +22,8 @@ class FcmService( val locale = Locale(localeStr) val notification = Notification.builder() - .setTitle(messageSource.getMessage("fcm.quickstart.title", null, locale)) - .setBody(String.format(messageSource.getMessage("fcm.quickstart.body", null, locale), nickname)) + .setTitle(messageService.getMessage("fcm.quickstart.title", locale)) + .setBody(String.format(messageService.getMessage("fcm.quickstart.body", locale), nickname)) .build() val message = Message.builder() diff --git a/adapter/firebase/src/main/resources/i18n/messages_en.properties b/adapter/firebase/src/main/resources/i18n/firebase_en.properties similarity index 100% rename from adapter/firebase/src/main/resources/i18n/messages_en.properties rename to adapter/firebase/src/main/resources/i18n/firebase_en.properties diff --git a/adapter/firebase/src/main/resources/i18n/messages_ko.properties b/adapter/firebase/src/main/resources/i18n/firebase_ko.properties similarity index 100% rename from adapter/firebase/src/main/resources/i18n/messages_ko.properties rename to adapter/firebase/src/main/resources/i18n/firebase_ko.properties diff --git a/app/support/exception/build.gradle.kts b/app/support/exception/build.gradle.kts index e455aa4d..4d4bbdbe 100644 --- a/app/support/exception/build.gradle.kts +++ b/app/support/exception/build.gradle.kts @@ -7,6 +7,7 @@ plugins { dependencies { implementation(project(":domain")) + implementation(project(":app:support:i18n")) implementation("org.springframework.boot:spring-boot-starter-web:${Versions.SPRING_BOOT}") } diff --git a/app/support/exception/src/main/kotlin/com/xorker/draw/exception/ExceptionButtonType.kt b/app/support/exception/src/main/kotlin/com/xorker/draw/exception/ExceptionButtonType.kt index 022f9fcf..360d9b2d 100644 --- a/app/support/exception/src/main/kotlin/com/xorker/draw/exception/ExceptionButtonType.kt +++ b/app/support/exception/src/main/kotlin/com/xorker/draw/exception/ExceptionButtonType.kt @@ -2,8 +2,8 @@ package com.xorker.draw.exception import com.xorker.draw.exception.ExceptionButtonAction.CloseDialog import com.xorker.draw.exception.ExceptionButtonAction.ForceUpdate +import com.xorker.draw.i18n.MessageService import java.util.* -import org.springframework.context.MessageSource enum class ExceptionButtonType( private val i18nCode: String, @@ -17,10 +17,10 @@ enum class ExceptionButtonType( private val responseMap = HashMap() - fun toResponse(source: MessageSource, locale: Locale): ExceptionButtonResponse { + fun toResponse(source: MessageService, locale: Locale): ExceptionButtonResponse { if (responseMap.containsKey(locale).not()) { val response = ExceptionButtonResponse( - text = source.getMessage(this.i18nCode, null, locale), + text = source.getMessage(this.i18nCode, locale), action = this.action, ) @@ -62,6 +62,7 @@ fun XorkerException.getButtons(): List { InvalidRequestOtherPlayingException, AlreadyPlayingRoomException, InvalidWebSocketStatusException, + is NotDefinedMessageCodeException, -> buttonOk } } diff --git a/app/support/exception/src/main/kotlin/com/xorker/draw/exception/ExceptionDesignFactory.kt b/app/support/exception/src/main/kotlin/com/xorker/draw/exception/ExceptionDesignFactory.kt index 42cc1803..f392fa9a 100644 --- a/app/support/exception/src/main/kotlin/com/xorker/draw/exception/ExceptionDesignFactory.kt +++ b/app/support/exception/src/main/kotlin/com/xorker/draw/exception/ExceptionDesignFactory.kt @@ -1,12 +1,12 @@ package com.xorker.draw.exception -import org.springframework.context.MessageSource +import com.xorker.draw.i18n.MessageService import org.springframework.context.i18n.LocaleContextHolder import org.springframework.stereotype.Component @Component class ExceptionDesignFactory( - private val messageSource: MessageSource, + private val messageService: MessageService, ) { fun generate(ex: XorkerException): ExceptionDesign { return when (ex) { @@ -20,8 +20,8 @@ class ExceptionDesignFactory( private fun generateToast(ex: XorkerException): ToastResponse { val locale = LocaleContextHolder.getLocale() - val text = messageSource.getMessage("exception.${ex.code}.text", null, "ERROR", locale) - ?: messageSource.getMessage("exception.default.description", null, locale) + val text = messageService.getMessageOrNull("exception.${ex.code}.description", locale) + ?: messageService.getMessage("exception.default.description", locale) return ToastResponse(text) } @@ -29,10 +29,11 @@ class ExceptionDesignFactory( private fun generateDialog(ex: XorkerException): DialogResponse { val locale = LocaleContextHolder.getLocale() - val title = messageSource.getMessage("exception.${ex.code}.title", null, "ERROR", locale) // TODO 기본 에러 처리 하기 - val description = messageSource.getMessage("exception.${ex.code}.description", null, null, locale) - ?: messageSource.getMessage("exception.default.description", null, locale) - val buttons = ex.getButtons().map { it.toResponse(messageSource, locale) } + val title = messageService.getMessageOrNull("exception.${ex.code}.title", locale) ?: "ERROR" // TODO 기본 에러 처리 하기 + val description = + messageService.getMessageOrNull("exception.${ex.code}.description", locale) + ?: messageService.getMessage("exception.default.description", locale) + val buttons = ex.getButtons().map { it.toResponse(messageService, locale) } return DialogResponse(title, description, buttons) } diff --git a/app/support/exception/src/main/resources/application-exception.yml b/app/support/exception/src/main/resources/application-exception.yml deleted file mode 100644 index 83c5e46a..00000000 --- a/app/support/exception/src/main/resources/application-exception.yml +++ /dev/null @@ -1,4 +0,0 @@ -spring: - messages: - basename: i18n/messages - encoding: UTF-8 diff --git a/app/support/exception/src/main/resources/i18n/messages_en.properties b/app/support/exception/src/main/resources/i18n/exception_en.properties similarity index 100% rename from app/support/exception/src/main/resources/i18n/messages_en.properties rename to app/support/exception/src/main/resources/i18n/exception_en.properties diff --git a/app/support/exception/src/main/resources/i18n/messages_ko.properties b/app/support/exception/src/main/resources/i18n/exception_ko.properties similarity index 100% rename from app/support/exception/src/main/resources/i18n/messages_ko.properties rename to app/support/exception/src/main/resources/i18n/exception_ko.properties diff --git a/app/support/i18n/build.gradle.kts b/app/support/i18n/build.gradle.kts new file mode 100644 index 00000000..267e97e9 --- /dev/null +++ b/app/support/i18n/build.gradle.kts @@ -0,0 +1,19 @@ +import org.springframework.boot.gradle.tasks.bundling.BootJar + +plugins { + id("org.springframework.boot") + kotlin("plugin.spring") +} + +dependencies { + implementation(project(":domain")) + implementation(project(":support:logging")) + implementation(project(":support:yaml")) + + implementation("org.springframework.boot:spring-boot-starter:${Versions.SPRING_BOOT}") +} + +tasks { + withType { enabled = true } + withType { enabled = false } +} diff --git a/app/support/i18n/src/main/kotlin/com/xorker/draw/i18n/MessageConfig.kt b/app/support/i18n/src/main/kotlin/com/xorker/draw/i18n/MessageConfig.kt new file mode 100644 index 00000000..25c97de1 --- /dev/null +++ b/app/support/i18n/src/main/kotlin/com/xorker/draw/i18n/MessageConfig.kt @@ -0,0 +1,30 @@ +package com.xorker.draw.i18n + +import org.springframework.context.MessageSource +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.ResourceBundleMessageSource +import org.springframework.core.io.support.PathMatchingResourcePatternResolver +import org.springframework.core.io.support.ResourcePatternResolver + +@Configuration +class MessageConfig { + + @Bean + fun messageSource(): MessageSource { + val resolver: ResourcePatternResolver = PathMatchingResourcePatternResolver() + val resources = resolver.getResources("classpath*:i18n/*") + + val basename = resources + .mapNotNull { it.filename } + .map { "i18n/${it.substring(0, it.lastIndexOf('_'))}" } + .distinct() + .toTypedArray() + + val messageSource = ResourceBundleMessageSource() + messageSource.setDefaultEncoding("UTF-8") + messageSource.setBasenames(*basename) + + return messageSource + } +} diff --git a/app/support/i18n/src/main/kotlin/com/xorker/draw/i18n/MessageService.kt b/app/support/i18n/src/main/kotlin/com/xorker/draw/i18n/MessageService.kt new file mode 100644 index 00000000..41f482d8 --- /dev/null +++ b/app/support/i18n/src/main/kotlin/com/xorker/draw/i18n/MessageService.kt @@ -0,0 +1,33 @@ +package com.xorker.draw.i18n + +import com.xorker.draw.exception.NotDefinedMessageCodeException +import com.xorker.draw.support.logging.logger +import java.util.Locale +import org.springframework.context.MessageSource +import org.springframework.context.NoSuchMessageException +import org.springframework.stereotype.Component + +@Component +class MessageService( + private val messageSource: MessageSource, +) { + private val logger = logger() + + fun getMessage(code: String, locale: Locale): String { + try { + return messageSource.getMessage(code, null, locale) + } catch (e: NoSuchMessageException) { + logger.error("i18n Error - $code", e) + throw NotDefinedMessageCodeException(code, locale, e) + } + } + + fun getMessageOrNull(code: String, locale: Locale): String? { + try { + return messageSource.getMessage(code, null, locale) + } catch (e: NoSuchMessageException) { + logger.warn("i18n Error - $code", e) + return null + } + } +} diff --git a/domain/src/main/kotlin/com/xorker/draw/exception/XorkerException.kt b/domain/src/main/kotlin/com/xorker/draw/exception/XorkerException.kt index 8ed48b08..32be3692 100644 --- a/domain/src/main/kotlin/com/xorker/draw/exception/XorkerException.kt +++ b/domain/src/main/kotlin/com/xorker/draw/exception/XorkerException.kt @@ -1,5 +1,8 @@ package com.xorker.draw.exception +import java.util.Locale +import org.springframework.context.NoSuchMessageException + sealed class XorkerException(val code: String, message: String, cause: Throwable? = null) : RuntimeException(message, cause) //region Client @@ -36,4 +39,5 @@ data object UnSupportedException : CriticalException("crt003", "정의하지 않 data object InvalidBroadcastException : CriticalException("crt004", "유효하지 않은 브로드캐스트 상태") { private fun readResolve(): Any = InvalidBroadcastException } class InvalidMafiaPhaseException(message: String) : CriticalException("crt005", message) data object InvalidWebSocketStatusException : CriticalException("crt006", "웹 소켓 세션 상태가 유효하지 않음") { private fun readResolve(): Any = InvalidWebSocketStatusException } +class NotDefinedMessageCodeException(messageCode: String, locale: Locale, cause: NoSuchMessageException) : CriticalException("crt007", "정의되지 않은 메시지 코드 $locale $messageCode", cause) //endregion diff --git a/settings.gradle.kts b/settings.gradle.kts index 788ce429..8e3d1ddd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,6 +6,7 @@ include( "app:api", "app:support:auth", "app:support:exception", + "app:support:i18n", "app:websocket", "domain", "core",