[Compose Multiplatform]moko-resources 라이브러리를 통해 국제화(i18n) 적용하기

이지훈·2024년 1월 5일
0
post-thumbnail

서두

저번 글에서 언급했던 moko-resources 를 이용한 string resource 국제화를 적용 하는 방법을 다뤄보도록 하겠다.

환경 설정 관련해서는 저번 글 에서 다뤘으니, 바로 시작해보도록 하겠다.

Android setup

공식 문서(레포 README.md) 에 워낙 설명이 잘 나와있으니, 그대로 따라하면 되는데


우선 한국어를 Default 언어로 설정할 것이기 때문에 commonMain 모듈의 build.gradle.kts 에 다음과 같이 설정을 추가해줘야 한다.

multiplatformResources {
    multiplatformResourcesPackage = "org.moneyking.imagepicker"
    iosBaseLocalizationRegion = "ko" // default "en"
}

한국어는 "kr" 이 아니라 "ko"이다 항상 헷갈림... 각 나라의 앞에서 부터 2글자 ㅇㅇ

base 패키지 내에 ko strings.xml 을 추가하고,이번엔 간단하게 영어 만을 다룰 것이기 때문에 en 패키지만을 추가하여 en strings.xml을 추가해주었다.

<!--ko strings.xml-->
<resources>
    <string name="pick_single_image">사진 한 장 선택하기</string>
    <string name="pick_multiple_images">사진 여러 장 선택하기</string>
    <string name="init">초기화</string>
</resources>

<!--en strings.xml-->
<?xml version="1.0" encoding="UTF-8" ?>
<resources>
    <string name="pick_single_image">Pick Single Images</string>
    <string name="pick_multiple_images">Pick Multiple Images</string>
    <string name="init">Init</string>
</resources>

rebuild 를 해주면 다음과 같이

MR actual object 내에 지정했던 string 들이 추가된 것을 확인할 수 있다.

init 만 따로 꺽쇄가 추가 되어있는 것을 확인 할 수 있는데, 내가 직접 생성하지 않은, 이미 기존에 존재하는 String key 중 init 이 존재하기 때문에 그것과 구분을 위한 것으로 추측된다. 실제로 value를 Init 이 아닌 init 으로 설정할 경우 아래와 같은 에러가 출력되는 것을 확인할 수 있었다.

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ':composeApp:generateMRcommonMain'.
    dev.icerock.gradle.generator.EqualStringKeysException: Can't process keys which equals their value: "init"

(꺼림직 해서 추후에 reset 이라는 key 로 바꿔주었다.)

따라서 그대로 Init 대문자로 해주고, 이제 이를 component 에 적용하면 되는데

        ImagePickerButton(
        	onClick = {
            	singlePhotoPicker.launch()
            },
            text = stringResource(MR.strings.pick_single_image), // <-
            	modifier = Modifier
                	.fillMaxWidth()
                    .height(56.dp)
                    .padding(horizontal = 24.dp),
         )
         Spacer(modifier = Modifier.height(16.dp))
         ImagePickerButton(
         	onClick = {
            	multiplePhotoPicker.launch()
            },
            text = stringResource(MR.strings.pick_multiple_images), // <- 
            modifier = Modifier
            	.fillMaxWidth()
                .height(56.dp)
                .padding(horizontal = 24.dp),
            )
         Spacer(modifier = Modifier.height(16.dp))
         ImagePickerButton(
         	onClick = {
            	images = emptyList()
            },
            text = stringResource(MR.strings.init), // <-
            modifier = Modifier
              	.fillMaxWidth()
                .height(56.dp)
                .padding(horizontal = 24.dp), 
          )

Compose multiplatform 의 경우 Android, iOS 에서 각각 다른 함수를 사용할 필요없이 Compose 에서 사용했던 stringResource 함수와 비슷한 함수를 제공하여 해당 함수를 사용하면 된다.

package dev.icerock.moko.resources.compose

import androidx.compose.runtime.Composable
import dev.icerock.moko.resources.PluralsResource
import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.desc.Plural
import dev.icerock.moko.resources.desc.PluralFormatted
import dev.icerock.moko.resources.desc.Resource
import dev.icerock.moko.resources.desc.ResourceFormatted
import dev.icerock.moko.resources.desc.StringDesc

@Composable
actual fun stringResource(resource: StringResource): String =
    StringDesc.Resource(resource).localized()

@Composable
actual fun stringResource(resource: StringResource, vararg args: Any): String =
    StringDesc.ResourceFormatted(resource, *args).localized()

@Composable
actual fun stringResource(resource: PluralsResource, quantity: Int): String =
    StringDesc.Plural(resource, quantity).localized()

@Composable
actual fun stringResource(resource: PluralsResource, quantity: Int, vararg args: Any): String =
    StringDesc.PluralFormatted(resource, quantity, *args).localized()

결과

koen

시스템 설정 언어를 바꿔가며 결과를 확인해봤을 때, 성공적으로 국제화가 적용된 것을 확인할 수 있었다.

Android 측 라이브러리 이슈?

다만 기존의 Android 시스템에서는 시스템 언어 설정을 변경하고 나서, 앱으로 다시 돌아올 경우 Configuration Change 가 발생하여, 곧 바로 변경된 언어로 앱을 확인할 수 있었는데, 현재는 라이브러리의 문제인지, Configuration Change 가 발생하지 않아, 앱을 종료 하고, 다시 실행해야만 변경된 시스템 언어가 반영되는 것을 확인할 수 있었다. 해당 이슈가 이미 등록되어 있는지 확인 해보고 없다면 등록, 있다면 추후에 해결 해볼 예정이다.

확인 결과 해당 이슈가 트래킹 되고 있는 것을 확인할 수 있었다. 0.24.0 milestone 에 포함되어 있으니, 다음 버전이 나와 업데이트가 되면, 해결이 될 문제인 듯 하다.

iOS setup

iOS 의 경우 Xcode 프로젝트 레포리토리 내에 info.plist 에 해당 설정을 추가해주어야 한다.
많은 디폴트 설정들이 존재하는데 최하단에 아래 코드를 추가해주었다.

	...
    <key>CFBundleLocalizations</key>
    <array>
    	<string>ko</string>
        <string>en</string>
    </array>

문제 발생?

iOSApp 에는 따로 swift 로 작성한 ui 가 없으므로 바로 실행을 해보았는데 다음과 같은 에러가 발생하는 것을 확인할 수 있었다.


??? 뭣? assembly 언어? 뭔 CoroutineExceptionHandlerImpl? coroutine 쓴적이 없는데?

문제 해결

사실 moko-resources 설정과 관련한 이슈는 아니었구 error message 들을 확인해본 결과, 최초의 발생 지점은

Uncaught Kotlin exception: kotlin.NotImplementedError: An operation is not implemented.
라는 에러 였고, 이를 구글링 해본 결과, 함수를 TODO() 로 남겨뒀는데, 구현하지 않아서 발생하는 에러였다.
expect 함수를 구현하는 actual 함수 중 아직 구현하지 않았던게 남아있어서 발생한 오류였다.

actual 함수를 구현한 결과 Android 에서 처럼 정상적으로 국제화가 정상적으로 적용된 것을 확인할 수 있었다!

어디서 어떤 에러가 발생하는 상세하게 알려주는 건 좋은데, 그 정도가 너무 과한 것 같다는 생각이 든다. Android Studio 의 logcat 이 그리워지는 순간이지만, Xcode 의 에러를 확인하는 방법도 계속 학습해봐야겠다.

실행 결과)

koen

아래의 레포에서 전체 코드를 확인해볼 수 있습니다.

https://github.com/KwonDae/ImagePicker

참고)
https://github.com/icerockdev/moko-resources
https://usa4060.tistory.com/3
https://phrase.com/blog/posts/kotlin-multiplatform-mobile-i18n-android-ios/

profile
실력은 고통의 총합이다. Android Developer

1개의 댓글

comment-user-thumbnail
2024년 4월 4일

https://www.jetbrains.com/help/kotlin-multiplatform-dev/whats-new-compose-1-6-0.html#improved-resources-api-all-platforms
-> 1.6.0 패치 이후로 이제는 moko-resources 를 사용하지 않아도 resources 들을 플랫폼간에 공유할 수 있게 되었다! moko-resources ㅂㅂ2

답글 달기