
Hay una manera fácil de implementar el cambio de idioma en una aplicación de actividad única. La pila de pantallas en este enfoque no se restablece, el usuario permanece donde cambió el idioma. Cuando el usuario va a las pantallas anteriores, se muestran inmediatamente traducidas. Y el resultado de la localización de números, sumas de dinero e intereses puede sorprender a los diseñadores.

¿Qué se discutirá y qué no se discutirá?
Además no habrá nada sobre:
- La teoría que subyace en la salida formateada de las cadenas y los detalles de implementación de las bibliotecas que se ocupan de esto. Es decir, algo que te ayudaría a escribir tu biblioteca.
- Recursos de cadena, vector y otros. Sobre qué calificadores de recursos usar, qué imágenes en árabe se deben mostrar de derecha a izquierda y cuáles no, y otras sutilezas.
- El proceso de traducción centralizada de recursos para todas las plataformas. Cómo organizarlo para que todos vivan bien, incluso los apodos de iOS.
Y hablaremos de:
- Practica Considere el problema, sus limitaciones y su solución con diagramas, ejemplos y fragmentos de código.
- La API del SDK que se usó para esta solución.
- Características del formato de valores numéricos para diferentes estándares regionales, que los diseñadores deben conocer.
Que queremos hacer
Deje que haya una pantalla con configuraciones en nuestra aplicación, y queremos agregarle un par de elementos nuevos, uno de los cuales le permitiría cambiar el idioma de la aplicación y el otro para cambiar la moneda en la que se muestran las cantidades de dinero. Aquí hay algunos ejemplos de cómo se vería esto.


Además de traducir el texto y mostrar el diseño de derecha a izquierda, esta configuración debería afectar el formato para mostrar valores numéricos. Es necesario que todo se muestre de acuerdo con la configuración regional seleccionada.

Solución arquitectónica
Imagine que nuestra aplicación está escrita de acuerdo con el enfoque de actividad única . Entonces el mecanismo de cambio de idioma se puede implementar de la siguiente manera.

SettingsInteractor
es la fuente del valor de idioma actual. Le permite suscribirse a este valor, recibirlo sincrónicamente y solo suscribirse a las actualizaciones. Si es necesario, puede introducir una abstracción adicional sobre SettingsInteractor
acuerdo con el principio de separación de la interfaz . En el diagrama, se omiten detalles irrelevantes.
AppActivity
en la creación reemplaza el contexto con uno nuevo para que la aplicación use recursos para el idioma seleccionado.
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
a su vez, se suscribe a las actualizaciones de idioma y notifica la Vista de los cambios.
override fun onFirstViewAttach() { super.onFirstViewAttach() subscribeToLanguageUpdates() } private fun subscribeToLanguageUpdates() { settingsInteractor .getUserSelectedLanguageUpdates() .subscribe( { newLang -> viewState.applyNewAppLanguage(newLang) }, { error -> errorHandler.handle(error) } ) .disposeOnDestroy() }
AppActivity
vuelve a crear cuando se recibe una notificación de un cambio de idioma.
override fun applyNewAppLanguage(lang: Locale) = recreate()

AppActivity
es el único en la aplicación. Todas las demás pantallas se implementan en fragmentos. Por lo tanto, al recrear la actividad, el sistema guarda la pila de pantalla. Si regresa a las pantallas anteriores, se reiniciarán y se mostrarán traducidas. El usuario permanecerá en la lista de selección de idioma y verá el resultado de su elección al instante.
Además de reemplazar el contexto, es necesario formatear los datos: números, dinero, intereses. Deje que cada Vista delegue esta tarea a un componente separado, llamémoslo UiLocalizer
.

UiLocalizer
usa las instancias adecuadas de NumberFormat
para convertir un número en una cadena.
private var numberFormat = NumberFormat.getNumberInstance(lang) private var percentFormat = NumberFormat.getPercentInstance(lang) private fun getNumberFormatForCurrency(currency: Currency) = NumberFormat .getCurrencyInstance(lang) .also { it.currency = currency }
Tenga en cuenta que la moneda debe establecerse por separado.
Si guarda ciclos de CPU y bits de memoria, y cambiar la moneda y el idioma es la función principal y más utilizada de su aplicación, entonces, por supuesto, necesita un caché.
Representación de idiomas y monedas.
Las instancias de la clase Locale
se crean mediante una etiqueta de idioma , que consta de un código de idioma de dos letras y un código de región de dos letras. Y las instancias de la clase Currency
se basan en un código ISO de tres letras . De esta forma, el idioma y la moneda se deben serializar para guardar en el disco o transferir a través de la red, y luego será bueno. Aquí hay algunos ejemplos.
El resultado de formatear números de acuerdo con los estándares regionales puede diferir de lo esperado. El símbolo de moneda o su código de tres letras en diferentes idiomas se mostrará de diferentes maneras. Aparecerán signos negativos para valores monetarios negativos en lugares inesperados, y en algunos lugares se mostrarán paréntesis. El signo de porcentaje puede no ser exactamente el signo al que estamos acostumbrados.
El hecho es que, desde el punto de vista de los patrones regionales, la línea final consiste en un prefijo y sufijo para números positivos y negativos, un separador de miles y un separador decimal, y son diferentes para diferentes lugares.
Los numeros
Monedas
Interés
Además, los resultados de formato para Android SDK y JDK pueden ser diferentes. Además, todas las opciones son correctas, cada una de ellas se usa en ciertos contextos.

Cuando creamos NumberFormat
para formatear ciertos valores, obtenemos objetos de la clase DecimalFormat
que simplemente están configurados por diferentes plantillas. Al DecimalFormat
objeto al tipo DecimalFormat
y usar su interfaz, puede cambiar partes de la plantilla para romper todo. Pero es mejor adorar la entrega.

También puedes escribir una prueba para disfrutar de la variedad. No para todas las configuraciones regionales, la misma moneda se muestra con un símbolo.

Al final
El esquema general de la solución es el siguiente.

AppActivity
Life Cycle es el ciclo de vida de toda la aplicación. Por lo tanto, es suficiente recrearlo para reiniciar toda la aplicación y aplicar el idioma seleccionado. Y dado que solo hay una actividad, es suficiente mantener la suscripción para cambiar el idioma en un solo lugar: en AppPresenter
.
Como vimos, los formatos regionales para generar números no son triviales. No debe establecer rígidamente una plantilla única para todas las ocasiones. Es mejor confiar el formato del SDK y aceptar que los números se mostrarán de acuerdo con el estándar, y no como se dibuja en los diseños.
¿Qué es más fácil de probar? (bonificación)
Para ahorrar tiempo, puede usar la siguiente bandera.
android { ... buildTypes { debug { pseudoLocalesEnabled true } } ... }
Seleccione el pseudo-locale deseado en la configuración del teléfono.

Y observe cómo va el diseño debido al texto largo, y algunos elementos de la IU no quieren mostrarse obstinadamente de derecha a izquierda.

Se puede encontrar más información en la documentación .
Vale la pena señalar que las pseudo-configuraciones regionales no funcionarán si cambia el contexto, como en la solución anterior. Estás cambiando el contexto. Por lo tanto, debe agregar en-XA
y ar-XB
a la lista de selección de idioma dentro de la aplicación.
Eso es todo. ¡Que tengas una buena localización y buen humor!
