diff --git a/app/src/main/java/com/ahmedjazzar/rosetta/LanguageSwitcher.java b/app/src/main/java/com/ahmedjazzar/rosetta/LanguageSwitcher.java new file mode 100644 index 0000000..53d286e --- /dev/null +++ b/app/src/main/java/com/ahmedjazzar/rosetta/LanguageSwitcher.java @@ -0,0 +1,179 @@ +package com.ahmedjazzar.rosetta; + +import android.app.Activity; +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; + +import java.util.HashSet; +import java.util.Locale; + +/** + * This class is the application door to this Library. It handles the ongoing and outgoing requests, + * initializations, preferences, .. + * I think that there's no need for logging here because other classes already handle logs for these + * actions based on their returned results. + * + * Created by ahmedjazzar on 1/16/16. + */ +public class LanguageSwitcher { + + private Context mContext; + private LocalesPreferenceManager mLocalesPreferences; + private final String TAG = LanguageSwitcher.class.getName(); + + /** + * A constructor that accepts context and sets the base and first launch locales to en_US + * @param context the context of the dealer + */ + public LanguageSwitcher(@NonNull Context context) { + this(context, Locale.US); + } + + /** + * A constructor that accepts context and sets the base and first launch locales to + * firstLaunchLocale. + * + * NOTE: Please do not use unless: + * 1. You wanna set your locales by calling {@link LanguageSwitcher#setSupportedLocales} + * 2. You know for sure that the preferred locale is as same as your base locale + * + * @param context the context of the dealer + * @param firstLaunchLocale the locale that owner wanna use at its first launch + */ + public LanguageSwitcher(@NonNull Context context, Locale firstLaunchLocale) { + this(context, firstLaunchLocale, firstLaunchLocale); + } + + /** + * This is supposed to be more specific; It has three parameters cover all owner needs + * @param context the context of the dealer + * @param firstLaunchLocale the locale that owner wanna use at its first launch + * @param baseLocale the locale that used in the main xml strings file (most likely 'en') + */ + public LanguageSwitcher(@NonNull Context context, Locale firstLaunchLocale, Locale baseLocale) { + this.mContext = context.getApplicationContext(); + + this.mLocalesPreferences = + new LocalesPreferenceManager(context, firstLaunchLocale, baseLocale); + + // initializing Locales utils needed objects (detector, preferences) + LocalesUtils.setDetector(new LocalesDetector(this.mContext)); + LocalesUtils.setLocalesPreferenceManager(mLocalesPreferences); + + // Setting app locale to match the user preferred one + LocalesUtils.setAppLocale(mContext, + mLocalesPreferences + .getPreferredLocale(LocalesPreferenceManager.USER_PREFERRED_LOCALE)); + } + + /** + * Responsible for displaying Change dialog fragment + */ + public void showChangeLanguageDialog(FragmentActivity activity) { + new LanguagesListDialogFragment() + .show(activity.getSupportFragmentManager(), TAG); + } + + /** + * + * @return the application supported locales + */ + public HashSet getLocales() { + return LocalesUtils.getLocales(); + } + + /** + * Sets the app locales from a string Set + * @param sLocales supported locales in a String form + */ + public void setSupportedStringLocales(HashSet sLocales) { + + HashSet locales = new HashSet<>(); + for (String sLocale: sLocales) { + locales.add(new Locale(sLocale)); + } + this.setSupportedLocales(locales); + } + + /** + * set supported locales from the given Set + * @param locales supported locales + */ + public void setSupportedLocales(HashSet locales) { + LocalesUtils.setSupportedLocales(locales); + } + + /** + * Sets the supported locales after fetching there availability using fetchAvailableLocales + * method + * @param stringId the string that this library gonna use to detect current app available + * locales + */ + public void setSupportedLocales(int stringId) { + this.setSupportedLocales(this.fetchAvailableLocales(stringId)); + } + + /** + * Fetching the application available locales inside the resources folder dynamically + * @param stringId the string that this library gonna use to detect current app available + * locales + * @return a set of detected application locales + */ + public HashSet fetchAvailableLocales(int stringId) { + return LocalesUtils.fetchAvailableLocales(stringId); + } + + /** + * Setting the application locale manually + * @param newLocale the locale in a string format + * @param activity the current activity in order to refresh the app + * + * @return true if the operation succeed, false otherwise + */ + public boolean setLocale(String newLocale, Activity activity) { + return setLocale(new Locale(newLocale), activity); + } + + /** + * Setting the application locale manually + * @param newLocale the desired locale + * @param activity the current activity in order to refresh the app + * + * @return true if the operation succeed, false otherwise + */ + public boolean setLocale(Locale newLocale, Activity activity) { + + return LocalesUtils.setLocale(newLocale, activity); + } + + /** + * + * @return the first launch locale + */ + public Locale getLaunchLocale() { + + return LocalesUtils.getLaunchLocale(); + } + + /** + * + * @return the current locale + */ + public Locale getCurrentLocale() { + + return LocalesUtils.getCurrentLocale(this.mContext); + } + + /** + * Return to the first launch locale + * @param activity the current activity in order to refresh the app + * + * @return true if the operation succeed, false otherwise + */ + public boolean switchToLaunch(Activity activity) { + + return setLocale(getLaunchLocale(), activity); + } +} diff --git a/app/src/main/java/com/ahmedjazzar/rosetta/LanguagesListDialogFragment.java b/app/src/main/java/com/ahmedjazzar/rosetta/LanguagesListDialogFragment.java new file mode 100644 index 0000000..fdfe3a4 --- /dev/null +++ b/app/src/main/java/com/ahmedjazzar/rosetta/LanguagesListDialogFragment.java @@ -0,0 +1,183 @@ + +package com.ahmedjazzar.rosetta; + +import android.app.Dialog; +import android.os.Bundle; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentActivity; + +import java.util.ArrayList; +import java.util.Locale; +import java.util.Objects; + +import com.fox2code.mmm.R; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +/** + * This fragment is responsible for displaying the supported locales and performing any necessary + * action that allows user to select, cancel, and commit changes. + * + * Created by ahmedjazzar on 1/19/16. + */ + +public class LanguagesListDialogFragment extends DialogFragment { + + private final int DIALOG_TITLE_ID = R.string.language; + private final int DIALOG_POSITIVE_ID = R.string.ok; + private final int DIALOG_NEGATIVE_ID = R.string.cancel; + + private int mSelectedLanguage = -1; + private final Logger mLogger; + + public LanguagesListDialogFragment() { + String TAG = LanguagesListDialogFragment.class.getName(); + this.mLogger = new Logger(TAG); + } + + /** + * @return a Dialog fragment + */ + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); + mLogger.debug("Building DialogFragment."); + + builder.setTitle(getString(DIALOG_TITLE_ID)) + .setSingleChoiceItems( + getLanguages(), + getCurrentLocaleIndex(), + (dialogInterface, which) -> onLanguageSelectedLocalized(which)) + .setPositiveButton( + getString(DIALOG_POSITIVE_ID).toUpperCase(), + (dialogInterface, which) -> onPositiveClick()) + .setNegativeButton( + getString(DIALOG_NEGATIVE_ID).toUpperCase(), + (dialogInterface, which) -> onNegativeClick()); + + mLogger.verbose("DialogFragment built."); + return builder.create(); + } + + /** + * @param which the position of the selected locale + */ + protected void onLanguageSelected(int which) { + // just update the selected locale + mSelectedLanguage = which; + } + + /** + * Localizing the dialog buttons and title + * @param which the position of the selected locale + */ + protected void onLanguageSelectedLocalized(int which) { + + // update the selected locale + mSelectedLanguage = which; + AlertDialog dialog = (AlertDialog) getDialog(); + + mLogger.debug("Displaying dialog main strings in the selected " + + "locale"); + + onLanguageSelectedLocalized( + which, + null, + dialog.getButton(AlertDialog.BUTTON_POSITIVE), + dialog.getButton(AlertDialog.BUTTON_NEGATIVE)); + } + + /** + * the position of the selected locale given the ids + * @param which the position of the selected locale + * @param titleView dialog's title text view + * @param positiveButton positive button + * @param negativeButton negative button + */ + protected void onLanguageSelectedLocalized(int which, TextView titleView, Button positiveButton, + Button negativeButton) { + + // update the selected locale + mSelectedLanguage = which; + Locale locale = LocalesUtils.getLocaleFromIndex(mSelectedLanguage); + AlertDialog dialog = (AlertDialog) getDialog(); + FragmentActivity activity = getActivity(); + + mLogger.debug("Displaying dialog main strings in the selected " + + "locale"); + + assert activity != null; + String LocalizedTitle = LocalesUtils.getInSpecificLocale(activity, locale, DIALOG_TITLE_ID); + if(titleView == null) { + // Display dialog title in the selected locale + assert dialog != null; + dialog.setTitle(LocalizedTitle); + } else { + titleView.setText(LocalizedTitle); + } + + // Display positive button text in the selected locale + positiveButton.setText(LocalesUtils.getInSpecificLocale( + activity, locale, DIALOG_POSITIVE_ID)); + + // Display negative button text in the selected locale + negativeButton.setText(LocalesUtils.getInSpecificLocale( + activity, locale, DIALOG_NEGATIVE_ID)); + } + + /** + * called when the user approved changing locale + */ + protected void onPositiveClick() { + + // if the user did not select the same locale go ahead, else ignore + if (mSelectedLanguage != -1 && + mSelectedLanguage != LocalesUtils.getCurrentLocaleIndex()) { + + // Try changing the locale + if (LocalesUtils.setAppLocale( + getActivity(), mSelectedLanguage)) { + + mLogger.info("App locale changed successfully."); + LocalesUtils.refreshApplication(requireActivity()); + } else { + mLogger.error("Unsuccessful trial to change the App locale."); + // TODO: notify the user that his request not placed + } + } else { + dismiss(); + } + } + + /** + * called when the user discarded changing locale + */ + protected void onNegativeClick() { + mLogger.verbose("User discarded changing language."); + mLogger.debug("Return to the original locale."); + this.onLanguageSelectedLocalized(this.getCurrentLocaleIndex()); + } + + /** + * + * @return available languages + */ + protected String[] getLanguages() { + ArrayList languages = LocalesUtils.getLocalesWithDisplayName(); + return languages.toArray(new String[languages.size()]); + } + + /** + * + * @return the index of the locale that app is using now + */ + protected int getCurrentLocaleIndex() { + return LocalesUtils.getCurrentLocaleIndex(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/ahmedjazzar/rosetta/LocalesDetector.java b/app/src/main/java/com/ahmedjazzar/rosetta/LocalesDetector.java new file mode 100644 index 0000000..61e3171 --- /dev/null +++ b/app/src/main/java/com/ahmedjazzar/rosetta/LocalesDetector.java @@ -0,0 +1,153 @@ +package com.ahmedjazzar.rosetta; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Build; +import android.util.DisplayMetrics; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; + +/** + * This class detects the application available locales inside the resources based on a string id, + * it's not so accurate and expects another methodologies. Next release may hold a better algorithms + * for detecting strings' languages and availability inside apps. + * + * Created by ahmedjazzar on 1/16/16. + */ +class LocalesDetector { + + private final Context mContext; + private Logger mLogger; + private final String TAG = LocalesDetector.class.getName(); + + LocalesDetector(Context context) { + this.mContext = context; + this.mLogger = new Logger(TAG); + } + + /** + * this method takes an experimental string id to see if it's exists in other available + * locales inside the app than default locale. + * NOTE: Even if you have a folder named values-ar it doesn't mean you have any resources + * there + * + * @param stringId experimental string id to discover locales + * @return the discovered locales + */ + HashSet fetchAvailableLocales(int stringId) { + + DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); + Configuration conf = mContext.getResources().getConfiguration(); + Locale originalLocale = conf.locale; + Locale baseLocale = LocalesUtils.getBaseLocale(); + conf.locale = baseLocale; + + ArrayList references = new ArrayList<>(); + references.add(new Resources(mContext.getAssets(), dm, conf).getString(stringId)); + + HashSet result = new HashSet<>(); + result.add(baseLocale); + + for(String loc : mContext.getAssets().getLocales()) { + if(loc.isEmpty()){ + continue; + } + + Locale l; + boolean referencesUpdateLock = false; + + l = Locale.forLanguageTag(loc); + + conf.locale = l; + + //TODO: put it in a method + String tmpString = new Resources(mContext.getAssets(), dm, conf).getString(stringId); + for (String reference: references) { + if(reference.equals(tmpString)){ + // TODO: check its original locale + referencesUpdateLock = true; + break; + } + } + + if(!referencesUpdateLock) { + result.add(l); + references.add(tmpString); + } + } + + conf.locale = originalLocale; // to restore our guy initial state + return result; + } + + /** + * TODO: return the selected one instead + * @return application current locale + */ + Locale getCurrentLocale() { + return mContext.getResources().getConfiguration().locale; + } + + /** + * TODO: what if a user didn't provide a closer email at all? + * TODO: check the closest locale not the first identified + * + * This method should provide a locale that is close to the given one in the parameter, it's + * currently checking the language only if in case the detector detects the string in other + * language. + * + * @param locale mostly the locale that's not detected or provided + * @return the index of the most close locale to the given locale. -1 if not detected + */ + int detectMostClosestLocale(Locale locale) { + + mLogger.debug("Start detecting a close locale to: "); + + int index = 0; + for (Locale loc: LocalesUtils.getLocales()) { + if(loc.getDisplayLanguage().equals(locale.getDisplayLanguage())) { + mLogger.info("The locale: '" + loc + "' has been detected as a closer locale to: '" + + locale + "'"); + return index; + } + index++; + } + + mLogger.debug("No closer locales founded."); + return -1; + } + + /** + * This method validate locales by checking if they are available of they contain wrong letter + * case and adding the valid ones in a clean set. + * @param locales to be checked + * @return valid locales + */ + HashSet validateLocales(HashSet locales) { + + mLogger.debug("Validating given locales.."); + + for (Locale l:LocalesUtils.getPseudoLocales()) { + if(locales.remove(l)) { + mLogger.info("Pseudo locale '" + l + "' has been removed."); + } + } + + HashSet cleanLocales = new HashSet<>(); + Locale[] androidLocales = Locale.getAvailableLocales(); + for (Locale locale: locales) { + if (Arrays.asList(androidLocales).contains(locale)) { + cleanLocales.add(locale); + } else { + mLogger.error("Invalid passed locale: " + locale); + mLogger.warn("Invalid specified locale: '" + locale + "', has been discarded"); + } + } + mLogger.debug("passing validated locales."); + return cleanLocales; + } +} diff --git a/app/src/main/java/com/ahmedjazzar/rosetta/LocalesPreferenceManager.java b/app/src/main/java/com/ahmedjazzar/rosetta/LocalesPreferenceManager.java new file mode 100644 index 0000000..7228a3b --- /dev/null +++ b/app/src/main/java/com/ahmedjazzar/rosetta/LocalesPreferenceManager.java @@ -0,0 +1,161 @@ +package com.ahmedjazzar.rosetta; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import java.util.Locale; + +/** + * This class is responsible for setting and getting the preferred locale and manage any related + * actions. I think that there's no need for logging here because the utils class already handles + * logs for these actions based on their returned results. + * + * Created by ahmedjazzar on 1/22/16. + */ +class LocalesPreferenceManager { + + private SharedPreferences mSharedPreferences; + private SharedPreferences.Editor mEditor; + + static final int BASE_LOCALE = 1; + private final String BASE_LANGUAGE_KEY = "base_language"; + private final String BASE_COUNTRY_KEY = "base_country"; + + static final int LAUNCH_LOCALE = 2; + private final String LAUNCH_LANGUAGE_KEY = "launch_language"; + private final String LAUNCH_COUNTRY_KEY = "launch_country"; + + static final int USER_PREFERRED_LOCALE = 3; + private final String USER_PREFERRED_LANGUAGE_KEY = "user_preferred_language"; + private final String USER_PREFERRED_COUNTRY_KEY = "user_preferred_country"; + + LocalesPreferenceManager(Context context, Locale firstLaunchLocale, Locale baseLocale) { + + this.mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + this.mEditor = this.mSharedPreferences.edit(); + + if (!isLocaleExists(BASE_LOCALE)) { + this.setPreferredLocale(BASE_LOCALE, baseLocale); + } + + if (!isLocaleExists(LAUNCH_LOCALE)) { + this.setPreferredLocale(LAUNCH_LOCALE, firstLaunchLocale); + } + + if (!isLocaleExists(USER_PREFERRED_LOCALE)) { + this.setPreferredLocale(USER_PREFERRED_LOCALE, firstLaunchLocale); + } + } + + boolean isLocaleExists(int key) { + + switch (key) { + case BASE_LOCALE: + return mSharedPreferences.contains(this.BASE_LANGUAGE_KEY); + case LAUNCH_LOCALE: + return mSharedPreferences.contains(this.LAUNCH_LANGUAGE_KEY); + case USER_PREFERRED_LOCALE: + return mSharedPreferences.contains(this.USER_PREFERRED_LANGUAGE_KEY); + default: + return false; + } + } + + /** + * Sets user preferred locale + * + * @param locale user desired locale + * @return true if the preference updated + */ + boolean setPreferredLocale(int key, Locale locale) { + return this.setPreferredLocale(key, locale.getLanguage(), locale.getCountry()); + } + + /** + * + * @return preferred locale after concatenating language and country + */ + Locale getPreferredLocale(int key) { + + String languageKey; + String countryKey; + + switch (key) { + case BASE_LOCALE: + languageKey = this.BASE_LANGUAGE_KEY; + countryKey = this.BASE_COUNTRY_KEY; + break; + case LAUNCH_LOCALE: + languageKey = this.LAUNCH_LANGUAGE_KEY; + countryKey = this.LAUNCH_COUNTRY_KEY; + break; + case USER_PREFERRED_LOCALE: + languageKey = this.USER_PREFERRED_LANGUAGE_KEY; + countryKey = this.USER_PREFERRED_COUNTRY_KEY; + break; + default: + return null; + } + + String language = getPreferredLanguage(languageKey); + String country = getPreferredCountry(countryKey); + + if (language == null) { + return null; + } + + return new Locale(language, country); + } + + /** + * Sets user preferred locale by setting a language preference and a country preference since + * there's no supported preferences for locales + * @param language of the locale; ex. en + * @param country of the locale; ex. US + * @return true if the preferences updated + */ + private boolean setPreferredLocale(int key, String language, String country) { + + String languageKey; + String countryKey; + + switch (key) { + case BASE_LOCALE: + languageKey = this.BASE_LANGUAGE_KEY; + countryKey = this.BASE_COUNTRY_KEY; + break; + case LAUNCH_LOCALE: + languageKey = this.LAUNCH_LANGUAGE_KEY; + countryKey = this.LAUNCH_COUNTRY_KEY; + break; + case USER_PREFERRED_LOCALE: + languageKey = this.USER_PREFERRED_LANGUAGE_KEY; + countryKey = this.USER_PREFERRED_COUNTRY_KEY; + break; + default: + return false; + } + + mEditor.putString(languageKey, language); + mEditor.putString(countryKey, country); + + return mEditor.commit(); + } + + /** + * + * @return preferred language + */ + private String getPreferredLanguage(String key) { + return mSharedPreferences.getString(key, null); + } + + /** + * + * @return preferred country + */ + private String getPreferredCountry(String key) { + return mSharedPreferences.getString(key, null); + } +} diff --git a/app/src/main/java/com/ahmedjazzar/rosetta/LocalesUtils.java b/app/src/main/java/com/ahmedjazzar/rosetta/LocalesUtils.java new file mode 100644 index 0000000..c93c18c --- /dev/null +++ b/app/src/main/java/com/ahmedjazzar/rosetta/LocalesUtils.java @@ -0,0 +1,302 @@ +package com.ahmedjazzar.rosetta; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Build; +import android.util.DisplayMetrics; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; + +/** + * This class is a helper class that connects all library classes activities together and make it + * easier for every class in the library to use and look at the shared info without a need to + * initialize a new object from the desired class + * + * Created by ahmedjazzar on 1/19/16. + */ +final class LocalesUtils { + + @SuppressLint("StaticFieldLeak") + private static LocalesDetector sDetector; + private static LocalesPreferenceManager sLocalesPreferenceManager; + private static HashSet sLocales; + private static final Locale[] PSEUDO_LOCALES = { + new Locale("en", "XA"), + new Locale("ar", "XB") + }; + private static final String TAG = LocalesDetector.class.getName(); + private static Logger sLogger = new Logger(TAG); + + /** + * + * @param detector just a setter because I don't want to declare any constructors in this class + */ + static void setDetector(@NonNull LocalesDetector detector) { + LocalesUtils.sDetector = detector; + } + + /** + * + * @param localesPreferenceManager just a setter because I don't want to declare any + * constructors in this class + */ + static void setLocalesPreferenceManager( + @NonNull LocalesPreferenceManager localesPreferenceManager) { + + LocalesUtils.sLocalesPreferenceManager = localesPreferenceManager; + } + + /** + * + * @param stringId a string to start discovering sLocales in + * @return a HashSet of discovered sLocales + */ + static HashSet fetchAvailableLocales(int stringId) { + return sDetector.fetchAvailableLocales(stringId); + } + + /** + * + * @param localesSet sLocales user wanna use + */ + static void setSupportedLocales(HashSet localesSet) { + LocalesUtils.sLocales = sDetector.validateLocales(localesSet); + sLogger.debug("Locales have been changed"); + } + + /** + * + * @return a HashSet of the available sLocales discovered in the application + */ + static HashSet getLocales() { + return LocalesUtils.sLocales; + } + + /** + * + * @return a list of locales for displaying on the layout purposes + */ + static ArrayList getLocalesWithDisplayName() { + ArrayList stringLocales = new ArrayList<>(); + + for (Locale loc: LocalesUtils.getLocales()) { + String langDisplay = loc.getDisplayName(loc); + stringLocales.add(langDisplay.substring(0, 1).toUpperCase() + langDisplay.substring(1).toLowerCase()); + } + return stringLocales; + } + + /** + * + * @return the index of the current app locale + */ + static int getCurrentLocaleIndex() { + Locale locale = LocalesUtils.getCurrentLocale(); + int index = -1; + int itr = 0; + + for (Locale l : sLocales) { + if(locale.equals(l)) { + index = itr; + break; + } + itr++; + } + + if (index == -1) { + //TODO: change the index to the most closer available locale + sLogger.warn("Current device locale '" + locale.toString() + + "' does not appear in your given supported locales"); + + index = sDetector.detectMostClosestLocale(locale); + if(index == -1) { + index = 0; + sLogger.warn("Current locale index changed to 0 as the current locale '" + + locale + + "' not supported." + ); + } + } + + return index; + } + + /** + * + * @see Pseudolocalization for + * more information about pseudo localization + * @return pseudo locales list + */ + static List getPseudoLocales() { + return Arrays.asList(LocalesUtils.PSEUDO_LOCALES); + } + + /** + * + * @return the locale at the given index + */ + static Locale getLocaleFromIndex(int index) { + return LocalesUtils.sLocales.toArray(new Locale[LocalesUtils.sLocales.size()])[index]; + } + + /** + * + * @param context + * @param index the selected locale position + * @return true if the application locale changed + */ + static boolean setAppLocale(Context context, int index) { + return setAppLocale(context, getLocaleFromIndex(index)); + } + + /** + * + * @return true if the application locale changed + */ + static boolean setAppLocale(Context context, Locale newLocale) { + + Resources resources = context.getResources(); + DisplayMetrics displayMetrics = resources.getDisplayMetrics(); + Configuration configuration = resources.getConfiguration(); + + Locale oldLocale = new Locale(configuration.locale.getLanguage(), configuration.locale.getCountry()); + configuration.locale = newLocale; + // Sets the layout direction from the Locale + sLogger.debug("Setting the layout direction"); + configuration.setLayoutDirection(newLocale); + resources.updateConfiguration(configuration, displayMetrics); + + if(oldLocale.equals(newLocale)) { + return false; + } + + if (LocalesUtils.updatePreferredLocale(newLocale)) { + sLogger.info("Locale preferences updated to: " + newLocale); + Locale.setDefault(newLocale); + } else { + sLogger.error("Failed to update locale preferences."); + } + + return true; + } + + /** + * + * @return application's base locale + */ + static Locale getBaseLocale() { + return LocalesUtils.sLocalesPreferenceManager.getPreferredLocale(LocalesPreferenceManager.BASE_LOCALE); + } + + /** + * + * @param stringId the target string + * @return a localized string + */ + static String getInSpecificLocale(FragmentActivity activity, Locale locale, int stringId) { + + Configuration conf = activity.getResources().getConfiguration(); + Locale old = conf.locale; + + conf.locale = locale; + DisplayMetrics metrics = new DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + Resources resources = new Resources(activity.getAssets(), metrics, conf); + conf.locale = old; + + return resources.getString(stringId); + } + + /** + * Refreshing the application so no weired results occurred after changing the locale. + */ + static void refreshApplication(Activity activity) { + + Intent app = activity.getBaseContext().getPackageManager() + .getLaunchIntentForPackage(activity.getBaseContext().getPackageName()); + app.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK); + + Intent current = new Intent(activity, activity.getClass()); + sLogger.debug("Refreshing the application: " + + activity.getBaseContext().getPackageName()); + + sLogger.debug("Finishing current activity."); + activity.finish(); + + sLogger.debug("Start the application"); + activity.startActivity(app); + activity.startActivity(current); + + sLogger.debug("Application refreshed"); + } + + /** + * + * @return the first launch locale + */ + static Locale getLaunchLocale() { + + return sLocalesPreferenceManager.getPreferredLocale(LocalesPreferenceManager.LAUNCH_LOCALE); + } + + /** + * Setting the application locale manually + * @param newLocale the desired locale + * @param activity the current activity in order to refresh the app + * + * @return true if the operation succeed, false otherwise + */ + static boolean setLocale(Locale newLocale, Activity activity) { + if (newLocale == null || !getLocales().contains(newLocale)) { + return false; + } + + if (LocalesUtils.setAppLocale(activity.getApplicationContext(), newLocale)) { + LocalesUtils.refreshApplication(activity); + return true; + } + + return false; + } + + /** + * @param context application base context + * @return the current locale + */ + public static Locale getCurrentLocale(Context context) { + Resources resources = context.getResources(); + Configuration configuration = resources.getConfiguration(); + + return new Locale(configuration.locale.getLanguage(), configuration.locale.getCountry()); + } + + /** + * + * @param locale the new preferred locale + * @return true if the preferred locale updated + */ + private static boolean updatePreferredLocale(Locale locale) { + + return LocalesUtils.sLocalesPreferenceManager + .setPreferredLocale(LocalesPreferenceManager.USER_PREFERRED_LOCALE, locale); + } + + /** + * + * @return current application locale + */ + private static Locale getCurrentLocale() { + return sDetector.getCurrentLocale(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ahmedjazzar/rosetta/Logger.java b/app/src/main/java/com/ahmedjazzar/rosetta/Logger.java new file mode 100644 index 0000000..3ea6fa2 --- /dev/null +++ b/app/src/main/java/com/ahmedjazzar/rosetta/Logger.java @@ -0,0 +1,38 @@ +package com.ahmedjazzar.rosetta; + +import android.util.Log; + +/** + * This class helps logging app events without a need to rewrite the tag name in every time + * Created by ahmedjazzar on 1/16/16. + */ + +class Logger { + + private String mTag; + + Logger(String tag) { + this.mTag = tag; + this.verbose("Object from " + this.mTag + " has been created."); + } + + void error(String log) { + Log.e(this.mTag, log); + } + + void warn(String log) { + Log.w(this.mTag, log); + } + + void debug(String log) { + Log.d(this.mTag, log); + } + + void info(String log) { + Log.i(this.mTag, log); + } + + void verbose(String log) { + Log.v(this.mTag, log); + } +} diff --git a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java index 749f2ec..2a01a0a 100644 --- a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java +++ b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java @@ -13,7 +13,6 @@ import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.TwoStatePreference; -import com.ahmedjazzar.rosetta.LanguageSwitcher; import com.fox2code.mmm.AppUpdateManager; import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.Constants; @@ -21,12 +20,13 @@ import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.OverScrollManager; import com.fox2code.mmm.R; import com.fox2code.mmm.compat.CompatActivity; -import com.fox2code.mmm.compat.CompatThemeWrapper; import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.repo.RepoData; import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.IntentHelper; + +import com.ahmedjazzar.rosetta.LanguageSwitcher; import com.mikepenz.aboutlibraries.LibsBuilder; import com.topjohnwu.superuser.internal.UiThreadHandler; @@ -249,7 +249,7 @@ public class SettingsActivity extends CompatActivity { preference.setTitle(R.string.repo_disabled); preference.setEnabled(false); } else { - ((TwoStatePreference)preference).setChecked(repoData.isEnabled()); + ((TwoStatePreference) preference).setChecked(repoData.isEnabled()); preference.setTitle(repoData.isEnabled() ? R.string.repo_enabled : R.string.repo_disabled); preference.setOnPreferenceChangeListener((p, newValue) -> {