在Android应用中切换语言


有一种简单的方法可以在单一活动应用程序中实现语言切换。 这种方法中的屏幕堆栈不会被重置,用户将停留在切换语言的位置。 当用户转到以前的屏幕时,它们会立即显示为已翻译。 数字本地化的结果,金钱和利息总和可能令设计师感到惊讶。


Hebrew, motherfucker! Do you speak it?!


将讨论什么而不会讨论什么?


此外,将没有任何关于:


  • 字符串格式化输出的基础理论 ,以及处理该问题的库的实现细节。 也就是说,可以帮助您编写库的内容。
  • 资源字符串,向量等。 关于要使用的资源限定符,应该以从右到左的顺序显示哪些阿拉伯语图片,哪些不应该显示,以及其他一些细微之处。
  • 所有平台的资源集中翻译的过程 。 如何组织它,使每个人都过得很好,甚至包括iOS昵称。

我们将谈论:


  • 练习 考虑问题,其局限性以及使用图表,示例和代码片段的解决方案。
  • 用于此解决方案的SDK API
  • 设计人员应了解的不同区域标准的数值格式设置功能

我们想做什么?


假设我们的应用程序中有一个设置屏幕,并且我们要向其中添加几个新项目,其中一个将允许您切换应用程序的语言,另一个可以更改显示金额的货币。 以下是一些示例。


Telegram.  .


Telegram.  .


除了翻译文本和从右到左显示排版外,这些设置还应影响数值的显示格式。 必须根据所选区域设置显示所有内容。


.


建筑解决方案


想象一下,我们的应用程序是根据“ 单一活动”方法编写的。 然后可以如下实现语言切换机制。


.


SettingsInteractor是当前语言值的来源。 它允许您订阅此值,同步接收它并且仅订阅更新。 如有必要,您可以根据接口分离原理SettingsInteractor引入其他抽象。 在该图中,省略了不相关的细节。


AppActivity在创建时将上下文替换为新的上下文,以便应用程序使用所选语言的资源。


 override fun attachBaseContext(base: Context) { super.attachBaseContext(applySelectedAppLanguage(base)) } private fun applySelectedAppLanguage(context: Context): Context { val locale = settingsInteractor.getUserSelectedLanguageBlocking() val newConfig = Configuration(context.resources.configuration) Locale.setDefault(locale) newConfig.setLocale(locale) return context.createConfigurationContext(newConfig) } 

AppPresenter订阅语言更新,并将更改通知给View。


 override fun onFirstViewAttach() { super.onFirstViewAttach() subscribeToLanguageUpdates() } private fun subscribeToLanguageUpdates() { settingsInteractor .getUserSelectedLanguageUpdates() .subscribe( { newLang -> viewState.applyNewAppLanguage(newLang) }, { error -> errorHandler.handle(error) } ) .disposeOnDestroy() } 

收到语言更改通知时,将重新创建AppActivity


 override fun applyNewAppLanguage(lang: Locale) = recreate() 

.


AppActivity是应用程序中仅有的一个。 所有其他屏幕均以片段形式实现。 因此,在重新创建活动时,屏幕堆栈由系统保存。 如果返回到先前的屏幕,它们将被重新初始化并显示为已翻译。 用户将保留在语言选择列表中,并立即看到其选择的结果。


格式化数字,现金和利息


除了替换上下文外,还需要格式化数据-数字,金钱,利息。 让每个View将此任务委托给一个单独的组件,我们将其UiLocalizer


UiLocalizer.


UiLocalizer使用适当的NumberFormat实例将数字转换为字符串。


 private var numberFormat = NumberFormat.getNumberInstance(lang) private var percentFormat = NumberFormat.getPercentInstance(lang) private fun getNumberFormatForCurrency(currency: Currency) = NumberFormat .getCurrencyInstance(lang) .also { it.currency = currency } 

请注意,货币必须单独设置。


如果节省CPU周期和内存位,并且切换货币和语言是应用程序的主要功能和经常使用的功能,则当然需要缓存。


语言和货币的表示


Locale类的实例由language标记创建,该标记由两个字母的语言代码和两个字母的区域代码组成。 Currency类的实例基于三个字母的ISO代码 。 以这种形式,必须对语言和货币进行序列化以保存到磁盘或通过网络进行传输,这样才可以。 这里有一些例子。


 // IETF BCP 47 language tag string. private val langs = arrayOf( Locale.forLanguageTag("ru-RU"), Locale.forLanguageTag("en-US"), Locale.forLanguageTag("en-GB"), Locale.forLanguageTag("he-IL"), Locale.forLanguageTag("ar-SA"), Locale.forLanguageTag("ar-AE"), Locale.forLanguageTag("fr-FR"), Locale.forLanguageTag("fr-CH"), Locale.forLanguageTag("de-DE"), Locale.forLanguageTag("de-CH"), Locale.forLanguageTag("da-DK") ) // ISO 4217 code of the currency. private val currencies = arrayListOf( Currency.getInstance("RUB"), Currency.getInstance("USD"), Currency.getInstance("GBP"), Currency.getInstance("ILS"), Currency.getInstance("SAR"), Currency.getInstance("AED"), Currency.getInstance("EUR"), Currency.getInstance("CHF"), Currency.getInstance("DKK") ) 

格式化数值的功能


根据地区标准格式化数字的结果可能与预期的不同。 货币符号或其不同语言的三字母代码将以不同方式显示。 货币价值负号的减号将出现在意外的位置,而在某些地方,将显示方括号。 百分号可能不完全是我们习惯的符号。


实际上,从区域模式的角度来看,最后一行由正数和负数的前缀和后缀,千位分隔符和十进制分隔符组成,并且对于不同的区域它们是不同的。


数字


语言能力负前缀负后缀正前缀正后缀分组分隔符小数点分隔符
RU-RU“-”“,”
zh-CN“-”“,”“。”
白细胞介素“-”“,”“。”
AE“-”“,”“。”
fr-FR“-”“,”
德德“-”“。”“,”
去CH“-”“”“。”
达克“-”“。”“,”

货币


语言能力负前缀负后缀正前缀正后缀分组分隔符小数点分隔符
RU-RU“-”“₽”“₽”“,”
zh-CN“-$”“ $”“,”“。”
白细胞介素“-”“₪”“₪”“,”“。”
AE“-”“د.إ。”“د.إ。”“,”“。”
fr-FR“-”“€”“€”“,”
德德“-”“€”“€”“。”“,”
去CH“ CHF-”瑞士法郎“”“。”
达克“-”“ kr。”“ kr。”“。”“,”

利息


语言能力负前缀负后缀正前缀正后缀分组分隔符小数点分隔符
RU-RU“-”“%”“%”“,”
zh-CN“-”“%”“%”“,”“。”
白细胞介素“-”“%”“%”“,”“。”
AE“-”“%”“%”“,”“。”
fr-FR“-”“%”“%”“,”
德德“-”“%”“%”“。”“,”
去CH“-”“%”“%”“”“。”
达克“-”“%”“%”“。”“,”

此外,Android SDK和JDK的格式化结果可能会有所不同。 而且,所有选项都是正确的,每个选项都在某些情况下使用。


Android  JDK.


十进制格式


当我们创建NumberFormat以格式化某些值时,我们将获得由不同模板简单配置的DecimalFormat类的对象。 通过DecimalFormat对象转换为DecimalFormat类型并使用其接口,可以更改模板的一部分以破坏所有内容。 但是,最好崇拜奉献。


DecimalFormat.


您还可以编写测试来享受多样性。 并非在所有语言环境中,相同的货币都会显示一个符号。


.


最后


该解决方案的一般方案如下。


.


AppActivity生命周期是整个应用程序的生命周期。 因此,只需重新创建它即可重新启动整个应用程序并应用所选语言。 而且,由于只有一项活动,因此足以将用于更改语言的订阅保留在一个位置AppPresenter


正如我们所看到的,用于输出数字的区域格式并非易事。 您不应在所有情况下都严格设置单个模板。 最好委托SDK设置格式,并同意数字将根据标准显示,而不是按照布局显示。


有什么更容易测试的? (奖金)


为了节省时间,可以使用以下标志。


 android { ... buildTypes { debug { pseudoLocalesEnabled true } } ... } 

在电话设置中选择所需的伪语言环境。


.


并注意由于文本较长而导致布局如何变化,并且UI的某些元素顽固地不希望从右到左显示。


.


可以在文档中找到更多信息。


值得注意的是,如果您更改上下文,则伪语言环境将不起作用,如上述解决方案中所示。 您正在更改上下文。 因此,必须将en-XAar-XB到应用程序内的语言选择列表中。


仅此而已。 拥有良好的本地化和良好的心情!


Thanks

Source: https://habr.com/ru/post/zh-CN461085/


All Articles