Site icon AppTractor

Локализация в Jetpack Compose

Локализация относится к изменению текста приложения, форматов валют и других визуальных элементов для соответствия локали пользователя на основе его региональных предпочтений.

С введением языковых предпочтений для каждого приложения (Per-app Language Preference) в Android 13 (API уровня 33) управление языками, специфичными для приложений, стало намного проще.

Настройки языка для приложения

Настройки языка для каждого приложения позволяют пользователям менять язык приложения, не влияя на весь язык системы. Этот новый API упрощает процесс локализации, устраняя необходимость в шаблонном коде и предлагая удобный подход к управлению определенными настройками языка приложения.

Давайте начнем.

Шаг 1: Добавление поддержки нескольких языков

Давайте добавим новый язык в наш каталог ресурсов values-* в папке res.

res > New Resource File > Select Locale qualifier > Language > Select Country Language Options> Введите имя файла «strings», чтобы добавить файл strings.xml для нужного языка.

Локализация в Jetpack Compose

// English Default (values/strings.xml)
<resources>
    <string name="app_name">Firefly</string>
    <string name="label_email">Email</string>
    <string name="label_password">Password</string>
    <string name="label_name">Name</string>
</resources>

// Nepali (values-ne/strings.xml)
<resources>
    <string name="app_name">Firefly</string>
    <string name="label_email">ईमेल</string>
    <string name="label_password">पासवर्ड</string>
    <string name="label_name">नाम</string>
</resources>

Шаг 2: Автоматическая генерация Locale Config

Используйте Gradle для автоматической генерации поддерживаемых локалей. Это позволяет нашему приложению получить ресурсы, необходимые для поддерживаемых локалей на основе strings.xml, которые мы предоставили.

android {
...
  androidResources{
      generateLocaleConfig = true
  }
...
}

В качестве альтернативы мы можем создать файл с именем res/xml/locales_config.xml и вручную указать языки нашего приложения.

--- locales_config.xml---

<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
   <locale android:name="en-US"/>
   <locale android:name="en-GB"/>
   <locale android:name="fr"/>
   <locale android:name="ja"/>
</locale-config>

И укажите ссылку на него в AndroidManifest.xml:

<manifest>
    ...
    <application
        ...
        android:localeConfig="@xml/locales_config">
    </application>
</manifest>

Примечание: Google рекомендует автоматически генерировать конфигурацию локали. Это означает, что мы можем пропустить альтернативный шаг выше.

Шаг 3: Установка локали по умолчанию

Чтобы указать язык по умолчанию, создайте файл resources.properties внутри папки res:

unqualifiedResLocale = en

Добавив это, наше приложение не будет сходить с ума, если не выбран ни один язык.

Шаг 4: включите функцию Auto Store Locales

Когда этот флаг включен, приложение автоматически запоминает выбор языка пользователем, поэтому нет необходимости вручную сохранять его в Preferences или DataStore.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" > 
  <application
       <service
            android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
            android:enabled="false"
            android:exported="false">
            <meta-data
                android:name="autoStoreLocales"
                android:value="true" />
        </service>

  </application>

</manifest>

И готово, теперь мы можем легко изменить язык приложения из настроек.

Перейдите в Настройки > Система > Языки и ввод > Языки приложений > (выберите приложение) и измените нужный язык.

На этом этапе мы можем изменить язык нашего приложения напрямую из настроек, не создавая отдельный выбор языка внутри.

Кастомный выбор языка

Если мы хотим добавить выбор языка в приложение, чтобы пользователи могли легко переключать языки, не выходя из приложения или не переходя в настроqrb, нам нужно вручную это сделать и уведомить per-app language API о том, что выбранный язык теперь является языком по умолчанию.

Шаг 5: Менеджер локали приложения

Давайте создадим вспомогательный класс, который обрабатывает выбор языка и уведомляет о любых изменениях Per-App Language API.

import android.app.LocaleManager
import android.content.Context
import android.os.Build
import android.os.LocaleList
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat

data class Language(
    val code: String,
    val displayLanguage: String
)

val appLanguages = listOf(
    Language("en", "English"), // default language
    Language("ne", "नेपाली"),
    Language("hi", "हिन्दी")
)

class AppLocaleManager {

    fun changeLanguage(context: Context, languageCode: String) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            context.getSystemService(LocaleManager::class.java).applicationLocales =
                LocaleList.forLanguageTags(languageCode)
        } else {
            AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(languageCode))
        }
    }
    
    fun getLanguageCode(context: Context,): String {
        val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            context.getSystemService(LocaleManager::class.java)
                ?.applicationLocales
                ?.get(0)
        } else {
            AppCompatDelegate.getApplicationLocales().get(0)
        }
        return locale?.language ?: getDefaultLanguageCode()
    }

    private fun getDefaultLanguageCode(): String {
        return  appLanguages.first().code
    }
}

Теперь, когда у нас есть AppLocaleManager, мы можем создать собственный пикер языка.

Шаг 6: Интеграция с ViewModel

Мы также можем напрямую инжектировать AppLocaleManager с помощью Hilt, если это необходимо. А пока давайте рассмотрим самый простой способ использования AppLocaleManager с ViewModel.

@HiltViewModel
class SettingViewModel @Inject constructor(
    @ApplicationContext private val context: Context
) : BaseViewModel() {

    private val appLocaleManager = AppLocaleManager()
    private val _settingState = MutableStateFlow(SettingState())
    val settingState: StateFlow<SettingState> = _settingState
    
    init {
        loadInitialLanguage()
    }

    private fun loadInitialLanguage() {
        val currentLanguage = appLocaleManager.getLanguageCode(context)
        _settingState.value = _settingState.value.copy(selectedLanguage = currentLanguage)
    }

    fun changeLanguage(languageCode: String) {
        appLocaleManager.changeLanguage(context, languageCode)
        _settingState.value = _settingState.value.copy(selectedLanguage = languageCode)
    }
    
}

data class SettingState(
    ...
    val selectedLanguage: String = ""
)

Шаг 7: Создание пользовательского интерфейса

Теперь давайте создадим пользовательский интерфейс для экрана настроек. Композабл SettingContent отображает список доступных языков в LazyColumn, выделяя выбранный.

@Composable
fun SettingScreen(
    settingViewModel: SettingViewModel = hiltViewModel(),
    navigateBack: () -> Unit,
) {
    val settingState by settingViewModel.settingState.collectAsStateLifecycleAware()
    val onAppLanguageChanged: (String) -> Unit = { newLanguage ->
        settingViewModel.changeLanguage(newLanguage)
    }
    SettingContent(
        selectedLanguage = settingState.selectedLanguage,
        onAppLanguageChanged
    ) {
        navigateBack()
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingContent(
    selectedLanguage: String,
    onAppLanguageChanged: (String) -> Unit,
    onNavigateBack: () -> Unit
) {
    val barScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
    Scaffold(topBar = {
        ChildAppTopBar(
            stringResource(R.string.settings),
            barScrollBehavior
        ) {
            onNavigateBack()
        }
    }) { paddingValues ->
        
        Box(modifier = Modifier.padding(paddingValues)) {
            LazyColumn {
                items(appLanguages.size) { index ->
                    LanguageRow(appLanguages[index], appLanguages[index].code == selectedLanguage) {
                        onAppLanguageChanged(it.code)
                    }
                }
            }
        }

    }
}

Спасибо, что прочитали! Я надеюсь, что это руководство поможет вам создавать лучшие приложения со многими языками.

Источник

Exit mobile version