Skip to content
This repository has been archived by the owner on Jun 17, 2024. It is now read-only.

Commit

Permalink
[components] Closes mozilla-mobile/android-components#3584: Api for s…
Browse files Browse the repository at this point in the history
…upporting language switching.
  • Loading branch information
Amejia481 authored and pocmo committed Jul 5, 2019
1 parent a102c01 commit ce4d369
Show file tree
Hide file tree
Showing 12 changed files with 325 additions and 0 deletions.
4 changes: 4 additions & 0 deletions android-components/.buildconfig.yml
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ projects:
path: components/support/rustlog
description: 'A bridge allowing log messages from Rust code to be sent to the log system in support-base'
publish: true
support-locale:
path: components/support/locale
description: 'A component to allow apps to change the system defined language by their custom one'
publish: true
lib-crash:
path: components/lib/crash
description: 'A generic crash reporter library that can report crashes to multiple services.'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<string name="mozac_support_base_locale_preference_key_locale" translatable="false">mozac_support_base_locale_preference_key_locale</string>
</resources>
19 changes: 19 additions & 0 deletions android-components/components/support/locale/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# [Android Components](../../../README.md) > Support > Locale

A component to allow apps to change the system defined language by their custom one.

## Usage

### Setting up the dependency

Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)):

```Groovy
implementation "org.mozilla.components:support-locale:{latest-version}"
```

## License

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/
42 changes: 42 additions & 0 deletions android-components/components/support/locale/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion config.compileSdkVersion

defaultConfig {
minSdkVersion config.minSdkVersion
targetSdkVersion config.targetSdkVersion

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation Dependencies.kotlin_stdlib
implementation Dependencies.androidx_core
implementation Dependencies.androidx_core_ktx

implementation project(':support-base')
implementation project(':support-utils')

testImplementation project(':support-test')
testImplementation Dependencies.androidx_test_core
testImplementation Dependencies.androidx_test_junit
testImplementation Dependencies.testing_robolectric
testImplementation Dependencies.testing_mockito
}

apply from: '../../../publish.gradle'
ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# TODO remove
android.enableUnitTestBinaryResources=false
21 changes: 21 additions & 0 deletions android-components/components/support/locale/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="mozilla.components.support.locale" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.support.locale

import java.util.Locale

fun String.toLocale(): Locale {
val index: Int = if (contains('-')) {
indexOf('-')
} else {
indexOf('_')
}
return if (index != -1) {
val langCode = substring(0, index)
val countryCode = substring(index + 1)
Locale(langCode, countryCode)
} else {
Locale(this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.support.locale

import android.content.Context
import androidx.appcompat.app.AppCompatActivity

/**
* Base activity for apps that want to customized the system defined language by their own.
*/
open class LocaleAwareAppCompatActivity : AppCompatActivity() {
override fun attachBaseContext(base: Context) {
val context = LocaleManager.updateResources(base)
super.attachBaseContext(context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.support.locale

import android.app.Application
import android.content.Context
import android.content.res.Configuration

/**
* Base application for apps that want to customized the system defined language by their own.
*/
open class LocaleAwareApplication : Application() {

override fun attachBaseContext(base: Context) {
val context = LocaleManager.updateResources(base)
super.attachBaseContext(context)
}

override fun onConfigurationChanged(config: Configuration) {
super.onConfigurationChanged(config)
LocaleManager.updateResources(this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.support.locale

import android.content.Context
import android.content.SharedPreferences
import android.content.res.Configuration
import androidx.appcompat.app.AppCompatActivity
import mozilla.components.support.base.R
import mozilla.components.support.base.log.logger.Logger
import java.util.Locale

/**
* Helper for apps that want to change locale defined by the system.
*/
object LocaleManager {
private val logger = Logger("LocaleManager")

/**
* Change the system defined locale to the indicated in the [language] parameter.
* This new [language] will be stored and will be the new current locale returned by [getCurrentLocale].
*
* After calling this function, to visualize the locale changes you have to make sure all your visible activities
* get recreated. If your app is using the single activity approach, this will be trivial just call
* [AppCompatActivity.recreate]. On the other hand, if you have multiple activity this could be tricky, one
* alternative could be restarting your application process see https://github.com/JakeWharton/ProcessPhoenix
* @return A new Context object for whose resources are adjusted to match the new [language].
*/
fun setNewLocale(context: Context, language: String): Context {
Storage.save(context, language)
return updateResources(context)
}

/**
* The latest stored locale saved by [setNewLocale].
*/
fun getCurrentLocale(context: Context): Locale? {
var currentLocale: Locale? = null

if (currentLocale == null) {
val locale = Storage.getLocale(context)
if (locale != null) {
currentLocale = locale.toLocale()
}
}
return currentLocale
}

internal fun updateResources(baseContext: Context): Context {
val locale = getCurrentLocale(baseContext)
return if (locale != null) {
updateSystemLocale(locale)
updateConfiguration(baseContext, locale)
} else {
baseContext
}
}

private fun updateConfiguration(context: Context, locale: Locale): Context {
val configuration = Configuration(context.resources.configuration)
configuration.setLocale(locale)
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
}

private fun updateSystemLocale(locale: Locale) {
Locale.setDefault(locale)
}

internal fun clear(context: Context) {
Storage.clear(context)
}

private object Storage {
private const val PREFERENCE_FILE = "mozac_support_base_locale_manager_preference"
private var currentLocal: String? = null

fun getLocale(context: Context): String? {
return if (currentLocal == null) {
val settings = getSharedPreferences(context)
val key = context.getString(R.string.mozac_support_base_locale_preference_key_locale)
currentLocal = settings.getString(key, null)
currentLocal
} else {
currentLocal
}
}

@Synchronized
fun save(context: Context, localeCode: String) {
val settings = getSharedPreferences(context)
val key = context.getString(R.string.mozac_support_base_locale_preference_key_locale)
settings.edit().putString(key, localeCode).apply()
currentLocal = localeCode
}

fun clear(context: Context) {
val settings = getSharedPreferences(context)
settings.edit().clear().apply()
currentLocal = null
}

private fun getSharedPreferences(context: Context): SharedPreferences {
return context.getSharedPreferences(PREFERENCE_FILE, Context.MODE_PRIVATE)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.support.locale

import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.util.Locale

@RunWith(AndroidJUnit4::class)
class LocaleManagerTest {

@Before
fun setup() {
LocaleManager.clear(testContext)
Locale.setDefault("en_US".toLocale())
}

@Test
fun `changing the language to Spanish must change the system locale to Spanish and change the configurations`() {

var currentLocale = LocaleManager.getCurrentLocale(testContext)

assertNull(currentLocale)

val newContext = LocaleManager.setNewLocale(testContext, "es")

assertNotEquals(testContext, newContext)

currentLocale = LocaleManager.getCurrentLocale(testContext)

assertEquals(currentLocale, "es".toLocale())
assertEquals(currentLocale, Locale.getDefault())
}

@Test
fun `when calling updateResources with none current language must not change the system locale neither change configurations`() {
val previousSystemLocale = Locale.getDefault()
val context = LocaleManager.updateResources(testContext)

assertEquals(testContext, context)
assertEquals(previousSystemLocale, Locale.getDefault())
}
}

0 comments on commit ce4d369

Please sign in to comment.