Android Jetpack は複数の言語をサポート

#### Jetpack Compose で複数の言語をサポート

アプリケーションの開発中に 1 つの言語オプションだけを使用するだけでは十分ではありません。 私たちがアプリケーションを公開する場合、別の言語で使用したいユーザーや、開発した言語を知らないユーザーがいる可能性があります。 したがって、アプリケーションが複数の言語をサポートしているという事実により、アプリケーションは常にさらに便利になります。

#### 設定:

#### <ins>プロジェクトに使うデペンデンシーライブラリー:</ins>
```build.gradle.kts
dependencies {
//...Jetpack composeの必要なデペンデンシーライブラリー
     //Navigation
    implementation("androidx.navigation:navigation-compose:2.7.5")
    //Hilt Navigation
    implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
    // Use Dagger Hilt
    implementation ("com.google.dagger:hilt-android:2.48.1")
    kapt ("com.google.dagger:hilt-android-compiler:2.48")
    // ViewModel
    implementation ("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
    // LiveData
    implementation ("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
    implementation("androidx.compose.runtime:runtime:1.6.0-beta01")
    implementation("androidx.compose.runtime:runtime-livedata:1.6.0-beta01")
    implementation("androidx.compose.runtime:runtime-rxjava2:1.6.0-beta01")

    //kotlin Coroutines
    implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
    implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")

    // Preferences DataStore
    implementation ("androidx.datastore:datastore-preferences:1.0.0")
}
```

#### <ins>build.gradle.kts(Project)</ins>
```build.gradle.kts
plugins {
    id("com.android.application") version "8.1.2" apply false
    id("org.jetbrains.kotlin.android") version "1.8.10" apply false
    id("com.google.devtools.ksp") version "1.9.10-1.0.13" apply false
    id ("com.google.dagger.hilt.android") version "2.48" apply false
}
```
#### <ins>build.gradle.kts(Module)</ins>
```build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id ("kotlin-kapt")
    id ("com.google.dagger.hilt.android")
}
```
#### <ins>Dependency Injection And Compose View: </ins>
#### <ins>Step 1:</ins> BaseApp.kt
```BaseApp.kt
@HiltAndroidApp
class BaseApp : Application()
```
#### <ins>Step 2:</ins> AndroidManifest.ktに```android:name```を追加
```AndroidManifest.kt
 <application
        android:name=".BaseApp"
        .
        .
        .
 </application>
```
#### <ins>Step 3:</ins> PrefDataStore.ktに```setLanguage/getLanguage```をkey追加

[https://gist.github.com/ihridoydas/0aae959fc4bdd3a45f07255cdfddfc13](https://gist.github.com/ihridoydas/0aae959fc4bdd3a45f07255cdfddfc13)


```PrefDataStore.kt
/**
 * Preference data store class
 * @param context get activity context
 */
class PrefDataStore(private val context: Context) {

    private val defaultLanguage = 1

    // to make sure there's only one instance
    companion object {
        private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("appPref")
        //For MultiLanguage
        val PREF_LANGUAGE = intPreferencesKey("language")

    }
    //set and get value

    //For MultiLanguage
    suspend fun setLanguage(language: Int) {
        context.dataStore.edit { preferences ->
            preferences[PREF_LANGUAGE] = language
        }
    }

    val getLanguage: Flow<Int> = context.dataStore.data
        .map { preferences ->
            preferences[PREF_LANGUAGE] ?: defaultLanguage
        }
}

```


#### <ins>Step 4:</ins> AppModule.ktに```@Module```をdiに提供する（providePreferencesDatastore）
[https://gist.github.com/ihridoydas/1099c9b5d3d8de7889e0e2bc4506f991](https://gist.github.com/ihridoydas/1099c9b5d3d8de7889e0e2bc4506f991)

```AppModule.kt
@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Singleton
    @Provides
    fun providePreferencesDatastore(@ApplicationContext appContext: Context): PrefDataStore = PrefDataStore(appContext)

}
```

#### <ins>Step 5:</ins> ```viewmodel```LanguageViewModel.kt

[https://gist.github.com/ihridoydas/95e3545d6178306b8198efa2c93b370d](https://gist.github.com/ihridoydas/95e3545d6178306b8198efa2c93b370d)

```LanguageViewModel.kt
@HiltViewModel
class LanguageViewModel @Inject constructor(
    private val prefDataStore: PrefDataStore
):ViewModel(){
    private val _language = MutableLiveData(1)
    var language: LiveData<Int> = _language

    init {
        viewModelScope.launch {
            prefDataStore.getLanguage.collect {
                _language.value = it
            }
        }
    }

    suspend fun saveLanguage(language: Int) {
        prefDataStore.setLanguage(language)
    }
}
```
#### <ins>Step 6:</ins> ```values/strings.xml```string resourcesを追加

こちらは、以下のような3言語のresourcesを追加します。["English","Japanese","Bangla"]
![strings.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619500/8a12b3d5-b3fd-675d-582d-c843922c2e54.gif)

#### <ins>English:</ins> strings.xml

```strings.xml
<resources>
    <string name="app_name">AndroidBlog</string>

    <!--    For MultiLanguage-->
    <string name="hello">Hello</string>
    <string name="love">Love</string>
    <string name="android">Android</string>
    <string name="content">Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.</string>

</resources>
```strings.xml
#### <ins>Japanese:</ins> strings.xml

```strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Androidブログ</string>

    <!--    For MultiLanguage-->
    <string name="hello">こんにちは</string>
    <string name="love">愛する</string>
    <string name="android">アンドロイド</string>
    <string name="content">Jetpack Compose は、ネイティブ UI を構築するための Android の最新ツールキットです。 Android での UI 開発を簡素化し、加速します。 少ないコード、強力なツール、直感的な Kotlin API を使用して、アプリをすばやく実現します。</string>

</resources>
```

#### <ins>Bangla:</ins> strings.xml

```strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">অ্যান্ড্রয়েডব্লগ</string>

    <!--    For MultiLanguage-->
    <string name="hello">হ্যালো</string>
    <string name="love">ভালবাসা</string>
    <string name="android">অ্যান্ড্রয়েড</string>
    <string name="content">জেটপ্যাক কম্পোজ হল নেটিভ UI তৈরির জন্য অ্যান্ড্রয়েডের আধুনিক টুলকিট। এটি অ্যান্ড্রয়েডে UI ডেভেলপমেন্টকে সহজ করে এবং ত্বরান্বিত করে। কম কোড, শক্তিশালী টুল এবং স্বজ্ঞাত Kotlin API-এর সাহায্যে আপনার অ্যাপকে দ্রুত প্রাণবন্ত করুন।</string>

</resources>
```


#### <ins>Step 7:</ins> ```Compose View```MultiLanguageScreen.kt
[https://gist.github.com/ihridoydas/e9078b49c6373edb447db75968edc92f](https://gist.github.com/ihridoydas/e9078b49c6373edb447db75968edc92f)


```MultiLanguageScreen.kt
private val language = listOf("English", "Japanese","Bangla")

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MultiLanguage(
    viewModel: LanguageViewModel = hiltViewModel(),
    //navController: NavController,
    //onBackPress : ()->Unit,
) {
    val scope = rememberCoroutineScope()
    val currentLanguage = viewModel.language.observeAsState().value
    val menuExpanded = remember { mutableStateOf(false) }

    SetLanguage(position = currentLanguage!!)
    Scaffold(
        modifier = Modifier.fillMaxSize(),
        topBar = {
            TopAppBar(
                title = {
                    Text(
                        text = "Multi Language",
                        fontSize = 20.sp,
                        modifier = Modifier.fillMaxWidth(),
                        textAlign = TextAlign.Center
                    )
                },
                navigationIcon = {
                    IconButton(
                        onClick = {
                            //onBackPress()
                        },
                        modifier = Modifier
                    ) {
                        Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
                    }
                },
                actions = {
                    IconButton(onClick = { menuExpanded.value = true }) {
                        Icon(
                            Icons.Filled.MoreVert,
                            contentDescription = "Menu",
                            tint = Color.Black
                        )
                    }
                    Column(
                        modifier = Modifier.wrapContentSize(Alignment.TopStart)
                    ) {
                        DropdownMenu(
                            expanded = menuExpanded.value,
                            onDismissRequest = {
                                menuExpanded.value = false
                            },
                            modifier = Modifier
                                .width(150.dp)
                                .wrapContentSize(Alignment.TopStart)
                        ) {

                            DropdownMenuItem(
                                text = {Text(text = stringResource(id = R.string.hello))},
                                onClick = { /*TODO*/ })
                            DropdownMenuItem(
                                text = {Text(text = stringResource(id = R.string.love))},
                                onClick = { /*TODO*/ })
                            DropdownMenuItem(
                                text = {Text(text = stringResource(id = R.string.android))},
                                onClick = { /*TODO*/ })

                        }
                    }
                }
            )
        }
    ) {
        Column(
            modifier = Modifier
                .padding(it)
                .verticalScroll(rememberScrollState(0))
                .fillMaxSize()
        ) {
            Spacer(modifier = Modifier.height(8.dp))
            LanguagePicker(currentLanguage) { selected ->
                scope.launch {
                    viewModel.saveLanguage(selected)
                }
            }
        }
    }
}

@Composable
fun LanguagePicker(
    selectedPosition: Int,
    onLanguageSelected: (Int) -> Unit
) {
    Card(
        modifier = Modifier.fillMaxSize(),
        shape = RoundedCornerShape(16.dp),
    ) {
        LanguageContentPortrait(selectedPosition, onLanguageSelected)
    }
}

@Composable
fun LanguageContentPortrait(
    selectedPosition: Int,
    onLanguageSelected: (Int) -> Unit
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        ToggleGroup(selectedPosition = selectedPosition, onClick = onLanguageSelected)
        Spacer(modifier = Modifier.height(10.dp))
        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Text(
                text = stringResource(id = R.string.content),
                modifier = Modifier.fillMaxSize(),
                textAlign = TextAlign.Center
            )
        }
    }
}

@Composable
private fun SetLanguage(position: Int) {
    val locale = Locale(
        when(position){
            1-> "ja"
            2-> "bn"
            else -> {"en"}
        }
    )
    val configuration = LocalConfiguration.current
    configuration.setLocale(locale)
    val resources = LocalContext.current.resources
    resources.updateConfiguration(configuration, resources.displayMetrics)
}

@Composable
private fun ToggleGroup(
    selectedPosition: Int,
    onClick: (Int) -> Unit
) {
    val shape = RoundedCornerShape(4.dp)
    Row(
        modifier = Modifier
            .padding(vertical = 8.dp)
            .clip(shape)
            .border(1.dp, Color(0xFFAAAAAA), shape)
    ) {
        language.forEachIndexed { index, element ->
            val verticalPadding = if (index == selectedPosition) 8.dp else 0.dp
            Text(
                text = element,
                color = if (index != selectedPosition) Color.Black else Color.White,
                modifier = Modifier
                    .align(Alignment.CenterVertically)
                    .run {
                        if (index != selectedPosition) this
                        else background(MaterialTheme.colorScheme.primary).border(1.dp, Color.Gray)
                    }
                    .clickable(
                        onClick = { onClick(index) },
                        role = Role.RadioButton
                    )
                    .padding(horizontal = 16.dp, vertical = verticalPadding)
            )
        }
    }
}

```
#### <ins>Step 7:</ins> ```Main View```MainActivity.kt

```MainActivity.kt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val navController = rememberNavController()
            AndroidBlogTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    MultiLanguage()
                }
            }
        }
    }
}
```
#### <ins>結果:</ins> [GitHub サンプル](https://github.com/ihridoydas/AndroidBlog/tree/blog1/multiLanguage)

<img width="400" alt="Screen_recording_20231123_132136.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619500/4f9fbd56-c217-cce7-f4a6-17c87513c1a3.gif">