Merge pull request #4261 from keymanapp/feat/android/set-app-locale

feat(android/app): Add option to change display language
This commit is contained in:
Darcy Wong 2021-01-20 12:11:01 +07:00 committed by GitHub
commit ceea611456
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 298 additions and 15 deletions

View file

@ -261,6 +261,12 @@
android:parentActivityName=".KeymanSettingsActivity"
android:launchMode="singleTask"
android:theme="@style/AppTheme.Base" />
<activity
android:name=".KeymanSettingsLocalizeActivity"
android:label="@string/app_name"
android:parentActivityName=".KeymanSettingsActivity"
android:launchMode="singleTask"
android:theme="@style/AppTheme.Base" />
<activity
android:name=".LanguagesSettingsActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"

View file

@ -37,9 +37,10 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import com.tavultesoft.kmea.BaseActivity;
import com.tavultesoft.kmea.util.KMLog;
public class BookmarksActivity extends AppCompatActivity {
public class BookmarksActivity extends BaseActivity {
private static final String TAG = "BookmarksActivity";
private static ListView listView;
private static ArrayList<HashMap<String, String>> list = null;

View file

@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.tavultesoft.kmea.BaseActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.data.Keyboard;
@ -29,7 +30,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.widget.TextView;
public class GetStartedActivity extends AppCompatActivity {
public class GetStartedActivity extends BaseActivity {
private static ListView listView = null;
private static ArrayList<HashMap<String, String>> list = null;

View file

@ -4,6 +4,7 @@
package com.tavultesoft.kmapro;
import com.tavultesoft.kmea.BaseActivity;
import com.tavultesoft.kmea.BuildConfig;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.KMManager.FormFactor;
@ -20,7 +21,7 @@ import android.widget.TextView;
import android.annotation.SuppressLint;
import androidx.appcompat.app.AppCompatActivity;
public class InfoActivity extends AppCompatActivity {
public class InfoActivity extends BaseActivity {
private final static String TAG = "InfoActivity";
private WebView webView;
private final String HELP_PRODUCTION_HOST = "help.keyman.com";

View file

@ -19,6 +19,7 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.AppCompatActivity;
import com.tavultesoft.kmea.BaseActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.util.KMPLink;
@ -27,7 +28,7 @@ import java.util.regex.Pattern;
import static android.app.Application.getProcessName;
public class KMPBrowserActivity extends AppCompatActivity {
public class KMPBrowserActivity extends BaseActivity {
private static final String TAG = "KMPBrowserActivity";
// URL for keyboard search web page presented to user when they add a keyboard in the app.

View file

@ -2,10 +2,12 @@ package com.tavultesoft.kmapro;
import android.os.Bundle;
import com.tavultesoft.kmea.BaseActivity;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
public class KeymanSettingsActivity extends AppCompatActivity {
public class KeymanSettingsActivity extends BaseActivity {
protected static final String installedLanguagesKey = "InstalledLanguages";
protected static final String installKeyboardOrDictionaryKey = "InstallKeyboardOrDictionary";
protected static final String showBannerKey = "ShowBanner";
@ -20,6 +22,7 @@ public class KeymanSettingsActivity extends AppCompatActivity {
setContentView(R.layout.keyman_settings_layout);
Toolbar toolbar = (Toolbar) findViewById(R.id.keyman_settings_toolbar);
toolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
toolbar.setTitle(R.string.keyman_settings);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);

View file

@ -1,3 +1,7 @@
/**
* Copyright (C) 2020-2021 SIL International. All rights reserved.
*/
package com.tavultesoft.kmapro;
import android.content.Context;
@ -12,6 +16,7 @@ import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.tavultesoft.kmea.DisplayLanguages;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.data.Dataset;
@ -19,7 +24,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
private static final String TAG = "SettingsFragment";
private static Context context;
private Preference languagesPreference, installKeyboardOrDictionary;
private Preference languagesPreference, installKeyboardOrDictionary, displayLanguagePreference;
private CheckBoxPreference setSystemKeyboardPreference;
private CheckBoxPreference setDefaultKeyboardPreference;
@ -46,6 +51,13 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
Intent installIntent = new Intent(context, KeymanSettingsInstallActivity.class);
installKeyboardOrDictionary.setIntent(installIntent);
displayLanguagePreference = new Preference(context);
displayLanguagePreference.setKey(DisplayLanguages.displayLanguageKey);
displayLanguagePreference.setTitle(getString(R.string.change_display_language));
displayLanguagePreference.setWidgetLayoutResource(R.layout.preference_translate_icon_layout);
Intent displayLanguageIntent = new Intent(context, KeymanSettingsLocalizeActivity.class);
displayLanguagePreference.setIntent(displayLanguageIntent);
/*
Automatically does the following:
SharedPreferences.Editor editor = prefs.edit();
@ -106,6 +118,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
screen.addPreference(languagesPreference);
screen.addPreference(installKeyboardOrDictionary);
screen.addPreference(displayLanguagePreference);
screen.addPreference(setSystemKeyboardPreference);
screen.addPreference(setDefaultKeyboardPreference);

View file

@ -0,0 +1,106 @@
/**
* Copyright (C) 2021 SIL International. All rights reserved.
*/
package com.tavultesoft.kmapro;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Typeface;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.preference.PreferenceManager;
import com.tavultesoft.kmea.DisplayLanguages;
import java.util.ArrayList;
import java.util.HashMap;
public class KeymanSettingsLocalizeActivity extends AppCompatActivity {
private static final String TAG = "SettingsLocalizeActivity";
private static ArrayList<HashMap<String, String>> localizeOptionList = null;
private static Typeface titleFont = null;
private static final String titleKey = "title";
private static final String subtitleKey = "subtitle";
private static final String iconKey = "icon";
private static Context context;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
context = this;
setContentView(R.layout.activity_list_layout);
final Toolbar toolbar = findViewById(R.id.list_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setDisplayShowTitleEnabled(false);
final ListView listView = findViewById(com.tavultesoft.kmea.R.id.listView);
Bundle bundle = getIntent().getExtras();
final TextView textView = findViewById(R.id.bar_title);
textView.setText(getString(R.string.change_display_language));
if (titleFont != null) {
textView.setTypeface(titleFont, Typeface.BOLD);
}
localizeOptionList = new ArrayList<>();
final String noIcon = "0";
for(DisplayLanguages.DisplayLanguageType l: DisplayLanguages.DisplayLanguages) {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(titleKey, l.getLanguageName());
hashMap.put(iconKey, noIcon);
localizeOptionList.add(hashMap);
}
String[] from = new String[]{titleKey, subtitleKey, iconKey};
int[] to = new int[]{com.tavultesoft.kmea.R.id.text1, com.tavultesoft.kmea.R.id.image1};
ListAdapter adapter = new SimpleAdapter(context, localizeOptionList, com.tavultesoft.kmea.R.layout.list_row_layout1, from, to) {
};
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Store the BCP-47 language tag in shared preference
if (position >= 0 && position < DisplayLanguages.DisplayLanguages.length) {
DisplayLanguages.DisplayLanguageType l = DisplayLanguages.DisplayLanguages[position];
String languageTag = l.getLanguageTag();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(DisplayLanguages.displayLanguageKey, languageTag);
editor.commit();
// Restart the app to use selected locale
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
});
}
@Override
public boolean onSupportNavigateUp() {
super.onBackPressed();
return true;
}
}

View file

@ -1,5 +1,5 @@
/**
* Copyright (C) 2018-2020 SIL International. All rights reserved.
* Copyright (C) 2018-2021 SIL International. All rights reserved.
*/
package com.tavultesoft.kmapro;
@ -17,6 +17,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tavultesoft.kmea.BaseActivity;
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.KMManager.KeyboardType;
@ -92,7 +93,7 @@ import android.widget.Toast;
import io.sentry.android.core.SentryAndroid;
public class MainActivity extends AppCompatActivity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener,
public class MainActivity extends BaseActivity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener,
ActivityCompat.OnRequestPermissionsResultCallback {
public static Context context;

View file

@ -10,6 +10,7 @@ import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import com.tavultesoft.kmea.BaseActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.util.KMPLink;
@ -48,7 +49,7 @@ import androidx.appcompat.app.AppCompatActivity;
import static android.app.Application.getProcessName;
public class WebBrowserActivity extends AppCompatActivity {
public class WebBrowserActivity extends BaseActivity {
private static final String TAG = "WebBrowserActivity";
private WebView webView;
private EditText addressField;

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/preference_icon"
android:layout_height="@dimen/preference_icon"
android:src="@drawable/ic_translate" />

View file

@ -95,6 +95,9 @@
<!-- Context: Keyman Settings menu -->
<string name="install_keyboard_or_dictionary" comment="Menu item to install keyboard or dictionary">Install Keyboard or Dictionary</string>
<!-- Context: Keyman Settings menu -->
<string name="change_display_language" comment="Menu action to change interface language">Change display language</string>
<!-- Context: Keyman Settings menu -->
<string name="show_banner" comment="text suggestions banner">Always show banner</string>

View file

@ -66,6 +66,7 @@ dependencies {
implementation 'com.google.android.material:material:1.2.1'
implementation 'commons-io:commons-io:2.6'
implementation 'io.sentry:sentry-android:3.1.0'
implementation 'androidx.preference:preference:1.1.1'
// Robolectric
testImplementation 'androidx.test:core:1.2.0'

View file

@ -0,0 +1,43 @@
/**
* Copyright (C) 2021 SIL International. All rights reserved.
*/
package com.tavultesoft.kmea;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.LocaleList;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
import com.tavultesoft.kmea.util.ContextUtils;
import java.util.Locale;
public class BaseActivity extends AppCompatActivity {
@Override
protected void attachBaseContext(Context newBase) {
// Override the app locale using the BCP 47 tag from shared preferences
// Using PreferenceManager because this is before onCreate()
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(newBase);
String languageTag = prefs.getString(DisplayLanguages.displayLanguageKey, DisplayLanguages.unspecifiedLocale);
Locale localeToSwitchTo;
if (languageTag == null || languageTag.equals(DisplayLanguages.unspecifiedLocale)) {
// If display language not specified, use the default locale
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
super.attachBaseContext(newBase);
return;
}
localeToSwitchTo = LocaleList.getDefault().get(0);
} else {
localeToSwitchTo = Locale.forLanguageTag(languageTag);
}
ContextWrapper localeUpdatedContext = ContextUtils.updateLocale(newBase, localeToSwitchTo);
super.attachBaseContext(localeUpdatedContext);
}
}

View file

@ -0,0 +1,40 @@
/**
* Copyright (C) 2021 SIL International. All rights reserved.
*/
package com.tavultesoft.kmea;
public class DisplayLanguages {
private static final String TAG = "DisplayLanguages";
public static final String displayLanguageKey = "DisplayLanguage";
public static final String unspecifiedLocale = ""; // empty string for app to use device's default locale
public static class DisplayLanguageType {
String languageTag;
String languageName;
public DisplayLanguageType(String languageTag, String languageName) {
this.languageTag = languageTag;
this.languageName = languageName;
}
public String getLanguageTag() {
return this.languageTag;
}
public String getLanguageName() {
return this.languageName;
}
}
// Display Language types (as named in translate.keyman.com)
// Order doesn't matter since we're storing BCP-47 tags in the settings preference
public static final DisplayLanguageType[] DisplayLanguages =
{
new DisplayLanguageType(unspecifiedLocale, "Default Locale"),
new DisplayLanguageType("de-DE", "German"),
new DisplayLanguageType("km-KH", "Khmer"),
new DisplayLanguageType("ann", "Obolo")
};
}

View file

@ -22,7 +22,7 @@ import java.util.List;
import static com.tavultesoft.kmea.ConfirmDialogFragment.DialogType.DIALOG_TYPE_DOWNLOAD_KEYBOARD;
import static com.tavultesoft.kmea.ConfirmDialogFragment.DialogType.DIALOG_TYPE_DOWNLOAD_MODEL;
public class KMKeyboardDownloaderActivity extends AppCompatActivity {
public class KMKeyboardDownloaderActivity extends BaseActivity {
// Bundle Keys
// Cloud

View file

@ -36,7 +36,7 @@ import com.tavultesoft.kmea.util.MapCompat;
import com.tavultesoft.kmea.util.QRCodeUtil;
// Public access is necessary to avoid IllegalAccessException
public final class KeyboardInfoActivity extends AppCompatActivity {
public final class KeyboardInfoActivity extends BaseActivity {
private static final String TAG = "KeyboardInfoActivity";
private static Toolbar toolbar = null;

View file

@ -45,7 +45,7 @@ import android.widget.Toast;
import androidx.appcompat.widget.Toolbar;
public final class KeyboardPickerActivity extends AppCompatActivity {
public final class KeyboardPickerActivity extends BaseActivity {
//TODO: view instances should not be static
private static Toolbar toolbar = null;

View file

@ -38,7 +38,7 @@ import com.tavultesoft.kmea.util.MapCompat;
import static com.tavultesoft.kmea.ConfirmDialogFragment.DialogType.DIALOG_TYPE_DELETE_MODEL;
// Public access is necessary to avoid IllegalAccessException
public final class ModelInfoActivity extends AppCompatActivity {
public final class ModelInfoActivity extends BaseActivity {
private static ArrayList<HashMap<String, String>> infoList = null;
private final String titleKey = "title";

View file

@ -40,7 +40,7 @@ import java.util.Map;
* Gets the list of installable lexical models from Keyman cloud and allows user to download a model.
* Displays a list of available models for a language ID.
*/
public final class ModelPickerActivity extends AppCompatActivity {
public final class ModelPickerActivity extends BaseActivity {
private Context context;
private static Toolbar toolbar = null;

View file

@ -0,0 +1,48 @@
/**
* Copyright (C) 2021 SIL International. All rights reserved.
*/
package com.tavultesoft.kmea.util;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import java.util.Locale;
/**
* Class to programmatically change the application locale (#4236)
* References
* Tutorial from https://lokalise.com/blog/android-app-localization/#Change_Application_Locale_Programmatically
* https://github.com/nirm2009/lokalise/tree/master/android-i18n
* https://stackoverflow.com/questions/39705739/android-n-change-language-programmatically
*/
public class ContextUtils extends ContextWrapper {
public ContextUtils(Context base) {
super(base);
}
public static ContextWrapper updateLocale(Context context, Locale localeToSwitchTo) {
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
LocaleList localeList = new LocaleList(localeToSwitchTo);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
} else {
configuration.locale = localeToSwitchTo;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
context = context.createConfigurationContext(configuration);
} else {
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
}
return new ContextUtils(context);
}
}

View file

@ -1,6 +1,6 @@
# Localization Maintenance
Localization for Keyman is maintained at https://crowdin.com/project/keyman
Localization for Keyman is maintained at https://translate.keyman.com
Downloading and updating files between Keyman and Crowdin happens automatically
on GitHub by way of the Crowdin git integration. The configuration file for all platforms
@ -69,6 +69,9 @@ To download latest translations for the specific language:
crowdin download -b master -l {language_code}
```
Note: the Crowdin API doesn't handle custom languages so those will need to be manually synced.
See https://support.crowdin.com/api/language-codes/
To display a list of latest translations from Crowdin:
```bash
@ -83,6 +86,12 @@ To upload source files to Crowdin:
crowdin upload sources
```
### Updating DisplayLanguages.java in Keyman Engine for Android
In Keyman for Android, the settings menu for changing display languages is maintained in
android/KMEA/app/src/main/java/com/tavultesoft/kmea/DisplayLangugages.java
For the BCP-47 language tags to use in that file, don't include script names since the Android locales only handle language ID and region.
## Tip
Microsoft offers a website that allows to check how terms used in Windows are translated