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

将讨论什么而不会讨论什么?
此外,将没有任何关于:
- 字符串格式化输出的基础理论 ,以及处理该问题的库的实现细节。 也就是说,可以帮助您编写库的内容。
- 资源字符串,向量等。 关于要使用的资源限定符,应该以从右到左的顺序显示哪些阿拉伯语图片,哪些不应该显示,以及其他一些细微之处。
- 所有平台的资源集中翻译的过程 。 如何组织它,使每个人都过得很好,甚至包括iOS昵称。
我们将谈论:
- 练习 考虑问题,其局限性以及使用图表,示例和代码片段的解决方案。
- 用于此解决方案的SDK API 。
- 设计人员应了解的不同区域标准的数值格式设置功能 。
我们想做什么?
假设我们的应用程序中有一个设置屏幕,并且我们要向其中添加几个新项目,其中一个将允许您切换应用程序的语言,另一个可以更改显示金额的货币。 以下是一些示例。


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

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

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
使用适当的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代码 。 以这种形式,必须对语言和货币进行序列化以保存到磁盘或通过网络进行传输,这样才可以。 这里有一些例子。
根据地区标准格式化数字的结果可能与预期的不同。 货币符号或其不同语言的三字母代码将以不同方式显示。 货币价值负号的减号将出现在意外的位置,而在某些地方,将显示方括号。 百分号可能不完全是我们习惯的符号。
实际上,从区域模式的角度来看,最后一行由正数和负数的前缀和后缀,千位分隔符和十进制分隔符组成,并且对于不同的区域它们是不同的。
数字
货币
利息
此外,Android SDK和JDK的格式化结果可能会有所不同。 而且,所有选项都是正确的,每个选项都在某些情况下使用。

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

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

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

AppActivity
生命周期是整个应用程序的生命周期。 因此,只需重新创建它即可重新启动整个应用程序并应用所选语言。 而且,由于只有一项活动,因此足以将用于更改语言的订阅保留在一个位置AppPresenter
。
正如我们所看到的,用于输出数字的区域格式并非易事。 您不应在所有情况下都严格设置单个模板。 最好委托SDK设置格式,并同意数字将根据标准显示,而不是按照布局显示。
有什么更容易测试的? (奖金)
为了节省时间,可以使用以下标志。
android { ... buildTypes { debug { pseudoLocalesEnabled true } } ... }
在电话设置中选择所需的伪语言环境。

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

可以在文档中找到更多信息。
值得注意的是,如果您更改上下文,则伪语言环境将不起作用,如上述解决方案中所示。 您正在更改上下文。 因此,必须将en-XA
和ar-XB
到应用程序内的语言选择列表中。
仅此而已。 拥有良好的本地化和良好的心情!
