【问题标题】:How to Force different locale language for android app?如何为 Android 应用强制使用不同的语言环境?
【发布时间】:2020-05-06 09:47:29
【问题描述】:

无论设备操作系统语言如何,我都需要强制应用程序区域设置满足我的需求,即:翻译的字符串,以及每个应用程序变体的布局 rtl/ltr。
我想知道如何正确地做到这一点。 正如我们所看到的,网络上有很多混乱,并且没有正式的答案。

【问题讨论】:

    标签: android


    【解决方案1】:

    这样做最正确的方法是首先理解以下概念:

    1. Locale
    2. Configuration
    3. Resources
    4. BaseContextContextWrapper

    请注意: [Context.getResources] 层有 3 种类型:

    1. 顶级资源(例如:清单活动名称)
    2. 应用资源
    3. 活动资源

    因此,更改 Application 层不会影响 Activity 层。

    还有:
    如果您希望支持 Android 6,您应该使用 .apk 文件 而不是 .aab(Android 应用程序包)。 这是因为在 Android 6 中,您只能在设置中选择一种默认语言环境,然后 .aab 将下载所需的配置资源:
    假设我们使用英文的 strings.xml 作为我们的默认语言,并使用其他 strings-iw.xml 将其翻译成希伯来语
    如果 Android 6 设备配置为英语作为其主要且唯一的语言,.aab 将仅提供常规英语 strings.xml 资源。 或者:
    如果可能,您可以只使用一个默认的 strings.xml 和所需的语言。

    解决办法:

    在您的 BaseActivity 和您的 Application 类中:

    abstract class BaseActivity : AppCompatActivity() {
    
        override fun attachBaseContext(newBase: Context) {
            val constrainedBaseCtx = LocaleUtil.constrainConfigurationLocale(newBase)
            super.attachBaseContext(constrainedBaseCtx)
        }
    
        override fun onConfigurationChanged(newConfig: Configuration) {
            val constrainedConfiguration = LocaleUtil.constrainConfigurationLocale(newConfig)
            super.onConfigurationChanged(constrainedConfiguration)
        }
    
        override fun createConfigurationContext(overrideConfiguration: Configuration): Context {
            val constrainedConf = LocaleUtil.constrainConfigurationLocale(overrideConfiguration)
            return super.createConfigurationContext(constrainedConf)
        }
    }
    

    使用您的区域设置工具:

    /**
     * Helps to change locales configuration for [Context] objects.
     * Remember that there are 3 types of [Context.getResources] layers:
     *
     * 1. Top-level resources (ex: manifest activity name)
     *
     * 2. Application resources
     *
     * 3. Activity resources
     *
     * So, changing the Application layer won't affect the activity layer.
     */
    object LocaleUtil {
    
    const val DEFAULT_LANGUAGE = "iw"
    const val DEFAULT_COUNTRY = "il"
    
    /**
     * Constraint This [context] Locale.
     * @param constrainedCountry - the country to match the activity for
     * @param constrainedLanguage - the language inside that country to match the activity for
     * @return new / same instance configured [Context]. (depends on Android OS version)
     */
    fun constrainConfigurationLocale(
        context: Context,
        constrainedCountry: String = DEFAULT_COUNTRY,
        constrainedLanguage: String = DEFAULT_LANGUAGE
    ) : Context {
        val newConf = constrainConfigurationLocale(
            context.resources.configuration,
            constrainedCountry,
            constrainedLanguage
        )
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
            context.createConfigurationContext(newConf)
        else
            context
    }
    
    /**
     * Constraint This [configuration] Locale.
     * @param constrainedCountry - the country to match the activity for
     * @param constrainedLanguage - the language inside that country to match the activity for
     * @return new / same instance of [Configuration]. (depends on Android OS version)
     */
    fun constrainConfigurationLocale(
        currentConfiguration: Configuration,
        constrainedCountry: String = DEFAULT_COUNTRY,
        constrainedLanguage: String = DEFAULT_LANGUAGE
    ) : Configuration {
    
        val configuration = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
            Configuration(currentConfiguration)
        else
            currentConfiguration
        val synthesizedLocale = Locale(constrainedLanguage, constrainedCountry)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val oldLocales = configuration.locales
            @Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
            if (!oldLocales.isEmpty) {
                if (!oldLocales[0].language!!.contentEquals(synthesizedLocale.language)) {
                    var newLocales = arrayOfNulls<Locale>(oldLocales.size() + 1)
                    newLocales[0] = synthesizedLocale // first locale determines layout direction
                    var deductionCount = 0
                    for (i in 0 until oldLocales.size()) {
                        if (newLocales[0]?.language?.contentEquals(oldLocales[i].language) != true) // add only different locale if not null
                            newLocales[i + 1 - deductionCount] = oldLocales[i]
                        else {
                            val temp = arrayOfNulls<Locale>(newLocales.size - 1)
                            for (j in 0..i) {
                                temp[j] = newLocales[j]
                            }
                            newLocales = temp
                            deductionCount++
                        }
                    }
                    configuration.locales = LocaleList(*newLocales)
                }
            } else {
                configuration.locales = LocaleList(synthesizedLocale)
            }
        } else {
            @Suppress("DEPRECATION", "UNNECESSARY_NOT_NULL_ASSERTION")
            if (configuration.locale == null || !synthesizedLocale.language!!.contentEquals(configuration.locale.language!!))
                configuration.setLocale(synthesizedLocale)
        }
        return configuration
    }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-24
      • 2015-05-15
      • 1970-01-01
      相关资源
      最近更新 更多