Skip to content
This repository was archived by the owner on Jul 11, 2025. It is now read-only.

Commit c7e852f

Browse files
neelanshsahaiashnoheNeelansh Sahai
authored
Add sample code for Per-App Language Preferences (#389)
* PerAppLanguages demo using Compose + Shows in-app language picker + Shows Settings App language picker video: https://photos.app.goo.gl/mgP8ZXUaiE5SpveN7 Patchset 1 can be ignored, this is boilerplate code from the New Project Wizard Patchset 2 and above contains code required to use the per-app language preferences with Compose Patchset 3: license files Patchset 4 -- 6: ack code review comments Change-Id: Ide4b342283ba2443b67d16870459c27ad50f2c89 * PerAppLanguages Compose Sample README Change-Id: I3b2e20431493299db3dc16da963e5a83cc34f490 * move per app language preferences compose app to compose_app directory to make way for an additional app Change-Id: I008e73eac8f71964f8f5f68e3893420f5ca3c42c * Add Per App Language Preferences sample in views Change-Id: Ie59890cf181a23ac6f60fabf7b7b8ea0fd71dcd6 Co-authored-by: Ash Nohe <[email protected]> Co-authored-by: Neelansh Sahai <[email protected]>
1 parent bab8c83 commit c7e852f

37 files changed

+1212
-0
lines changed

PerAppLanguages/views_app/LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright (C) 2022 The Android Open Source Project
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

PerAppLanguages/views_app/README.md

Whitespace-only changes.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright (C) 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
plugins {
18+
id 'com.android.application'
19+
id 'kotlin-android'
20+
}
21+
22+
android {
23+
compileSdk 33
24+
25+
defaultConfig {
26+
applicationId "com.example.views_app"
27+
minSdk 21
28+
targetSdk 33
29+
versionCode 1
30+
versionName "1.0"
31+
32+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
33+
}
34+
35+
buildTypes {
36+
release {
37+
minifyEnabled false
38+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
39+
}
40+
}
41+
compileOptions {
42+
sourceCompatibility JavaVersion.VERSION_1_8
43+
targetCompatibility JavaVersion.VERSION_1_8
44+
}
45+
kotlinOptions {
46+
jvmTarget = '1.8'
47+
}
48+
viewBinding {
49+
enabled = true
50+
}
51+
}
52+
53+
dependencies {
54+
55+
implementation 'androidx.core:core-ktx:1.9.0'
56+
implementation "androidx.appcompat:appcompat:1.6.0-rc01"
57+
implementation "androidx.appcompat:appcompat-resources:1.6.0-rc01"
58+
implementation 'com.google.android.material:material:1.6.1'
59+
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
60+
testImplementation 'junit:junit:4.13.2'
61+
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
62+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
63+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright (C) 2022 The Android Open Source Project
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
18+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
19+
package="com.example.views_app">
20+
21+
<application
22+
android:icon="@mipmap/ic_launcher"
23+
android:label="@string/app_name"
24+
android:localeConfig="@xml/locale_configs"
25+
android:roundIcon="@mipmap/ic_launcher_round"
26+
android:supportsRtl="true"
27+
android:theme="@style/Theme.ViewsApp">
28+
<activity
29+
android:name=".MainActivity"
30+
android:exported="true">
31+
<intent-filter>
32+
<action android:name="android.intent.action.MAIN" />
33+
34+
<category android:name="android.intent.category.LAUNCHER" />
35+
</intent-filter>
36+
</activity>
37+
38+
<!-- Let AndroidX handle auto-store locales for pre-T devices to hold the user's selected locale -->
39+
<service
40+
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
41+
android:enabled="false">
42+
<meta-data
43+
android:name="autoStoreLocales"
44+
android:value="true" />
45+
</service>
46+
47+
</application>
48+
49+
</manifest>
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright (C) 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.views_app
18+
19+
import android.content.Context
20+
import android.os.Bundle
21+
import androidx.appcompat.app.AppCompatActivity
22+
import androidx.appcompat.app.AppCompatDelegate
23+
import androidx.core.os.LocaleListCompat
24+
import com.example.views_app.databinding.ActivityMainBinding
25+
import java.util.*
26+
27+
class MainActivity : AppCompatActivity() {
28+
29+
companion object {
30+
const val PREFERENCE_NAME = "shared_preference"
31+
const val PREFERENCE_MODE = Context.MODE_PRIVATE
32+
33+
const val FIRST_TIME_MIGRATION = "first_time_migration"
34+
const val SELECTED_LANGUAGE = "selected_language"
35+
36+
const val STATUS_DONE = "status_done"
37+
}
38+
39+
/**
40+
* This is a sample code that explains the use of getter and setter APIs for Locales introduced
41+
* in the Per-App language preferences. Here is an example use of the AndroidX Support Library
42+
* */
43+
override fun onCreate(savedInstanceState: Bundle?) {
44+
super.onCreate(savedInstanceState)
45+
val binding = ActivityMainBinding.inflate(layoutInflater)
46+
val view = binding.root
47+
setContentView(view)
48+
49+
/* NOTE: If you were handling the locale storage on you own earlier, you will need to add a
50+
one time migration for switching this storage from a custom way to the AndroidX storage.
51+
52+
This can be done in the following manner. Lets say earlier the locale preference was
53+
stored in a SharedPreference */
54+
55+
// Check if the migration has already been done or not
56+
if (getString(FIRST_TIME_MIGRATION) != STATUS_DONE) {
57+
// Fetch the selected language from wherever it was stored. In this case its SharedPref
58+
getString(SELECTED_LANGUAGE)?.let {
59+
// Set this locale using the AndroidX library that will handle the storage itself
60+
val localeList = LocaleListCompat.forLanguageTags(it)
61+
AppCompatDelegate.setApplicationLocales(localeList)
62+
// Set the migration flag to ensure that this is executed only once
63+
putString(FIRST_TIME_MIGRATION, STATUS_DONE)
64+
}
65+
}
66+
67+
// Fetching the current application locale using the AndroidX support Library
68+
val currentLocaleName = if (!AppCompatDelegate.getApplicationLocales().isEmpty) {
69+
// Fetches the current Application Locale from the list
70+
AppCompatDelegate.getApplicationLocales()[0]?.displayName
71+
} else {
72+
// Fetches the default System Locale
73+
Locale.getDefault().displayName
74+
}
75+
76+
// Displaying the selected locale on screen
77+
binding.tvSelectedLanguage.text = currentLocaleName
78+
79+
// Setting app language to "English" in-app using the AndroidX support library
80+
binding.btnSelectEnglish.setOnClickListener {
81+
val localeList = LocaleListCompat.forLanguageTags("en")
82+
AppCompatDelegate.setApplicationLocales(localeList)
83+
}
84+
85+
// Setting app language to "Hindi" in-app using the AndroidX support library
86+
binding.btnSelectHindi.setOnClickListener {
87+
val localeList = LocaleListCompat.forLanguageTags("hi")
88+
AppCompatDelegate.setApplicationLocales(localeList)
89+
}
90+
91+
// Setting app language to "Arabic" in-app using the AndroidX support Library
92+
// NOTE: Here the screen orientation is reversed to RTL
93+
binding.btnSelectArabic.setOnClickListener {
94+
val localeList = LocaleListCompat.forLanguageTags("ar")
95+
AppCompatDelegate.setApplicationLocales(localeList)
96+
}
97+
98+
// Setting app language to "Japanese" in-app using the AndroidX support library
99+
binding.btnSelectJapanese.setOnClickListener {
100+
val localeList = LocaleListCompat.forLanguageTags("ja")
101+
AppCompatDelegate.setApplicationLocales(localeList)
102+
}
103+
104+
// Setting app language to "Spanish" in-app using the AndroidX support library
105+
binding.btnSelectSpanish.setOnClickListener {
106+
val localeList = LocaleListCompat.forLanguageTags("es")
107+
AppCompatDelegate.setApplicationLocales(localeList)
108+
}
109+
110+
// Setting app language to Traditional Chinese in-app using the AndroidX support Library
111+
binding.btnSelectXxYy.setOnClickListener {
112+
val localeList = LocaleListCompat.forLanguageTags("zh-Hant")
113+
AppCompatDelegate.setApplicationLocales(localeList)
114+
}
115+
}
116+
117+
private fun putString(key: String, value: String) {
118+
val editor = getSharedPreferences(PREFERENCE_NAME, PREFERENCE_MODE).edit()
119+
editor.putString(key, value)
120+
editor.apply()
121+
}
122+
123+
private fun getString(key: String): String? {
124+
val preference = getSharedPreferences(PREFERENCE_NAME, PREFERENCE_MODE)
125+
return preference.getString(key, null)
126+
}
127+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!--
2+
~ Copyright (C) 2022 The Android Open Source Project
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
18+
xmlns:aapt="http://schemas.android.com/aapt"
19+
android:width="108dp"
20+
android:height="108dp"
21+
android:viewportWidth="108"
22+
android:viewportHeight="108">
23+
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
24+
<aapt:attr name="android:fillColor">
25+
<gradient
26+
android:endX="85.84757"
27+
android:endY="92.4963"
28+
android:startX="42.9492"
29+
android:startY="49.59793"
30+
android:type="linear">
31+
<item
32+
android:color="#44000000"
33+
android:offset="0.0" />
34+
<item
35+
android:color="#00000000"
36+
android:offset="1.0" />
37+
</gradient>
38+
</aapt:attr>
39+
</path>
40+
<path
41+
android:fillColor="#FFFFFF"
42+
android:fillType="nonZero"
43+
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
44+
android:strokeWidth="1"
45+
android:strokeColor="#00000000" />
46+
</vector>

0 commit comments

Comments
 (0)