I have an app with string resources for German and English. I defined a separate Fragment for changing the language that you can see here
public class FR_Options extends Fragment implements View.OnClickListener {
/*
String specifying the language of the App
*/
public static final String LANGUAGE_GERMAN = "German";
public static final String LANGUAGE_ENGLISH = "English";
//Set the default language to GERMAN
public static String currentLanguageOfTheApp = LANGUAGE_ENGLISH;
public FR_Options() {
// Required empty public constructor
}
public static FR_Options newInstance(String param1, String param2) {
FR_Options fragment = new FR_Options();
return fragment;
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
private FragmentOptionsBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
binding = FragmentOptionsBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.imageButtonGermany.setOnClickListener(this);
binding.imageButtonUK.setOnClickListener(this);
if(currentLanguageOfTheApp.equals(LANGUAGE_ENGLISH)) {
binding.textViewCurrentLanguageValue.setText(LANGUAGE_ENGLISH);
binding.imageButtonGermany.setAlpha(0.5f);
binding.imageButtonUK.setAlpha(1.0f);
}
if(currentLanguageOfTheApp.equals(LANGUAGE_GERMAN)) {
binding.textViewCurrentLanguageValue.setText(LANGUAGE_GERMAN);
binding.imageButtonGermany.setAlpha(1.0f);
binding.imageButtonUK.setAlpha(0.5f);
}
}
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void onClick(View view) {
if(view.getId() == R.id.imageButtonGermany) {
/*
Set the language to "German" for other fragments and database queries
*/
this.currentLanguageOfTheApp = LANGUAGE_GERMAN;
/*
Set the language to "German" for the XML-layout files
*/
Locale locale;
locale = new Locale("de", "DE");
Configuration config = new Configuration(getActivity().getBaseContext().getResources().getConfiguration());
Locale.setDefault(locale);
config.setLocale(locale);
getActivity().recreate();
getActivity().getBaseContext().getResources().updateConfiguration(config,
getActivity().getBaseContext().getResources().getDisplayMetrics());
}
if(view.getId() == R.id.imageButtonUK) {
/*
Set the language to "English" for other fragments and database queries
*/
this.currentLanguageOfTheApp = LANGUAGE_ENGLISH;
/*
Set the language to "English" for the XML-layout files
*/
Locale locale;
locale = new Locale("en", "EN");
Configuration config = new Configuration(getActivity().getBaseContext().getResources().getConfiguration());
Locale.setDefault(locale);
config.setLocale(locale);
getActivity().recreate();
getActivity().getBaseContext().getResources().updateConfiguration(config,
getActivity().getBaseContext().getResources().getDisplayMetrics());
}
}
}
Now when I navigate to a Test fragment whose Java file looks like this
public class Test extends Fragment {
int widthDisplay;
int heightDisplay;
private FragmentTestBinding binding;
private ConstraintLayout constraintLayout;
ConstraintSet constraintSet ;
private boolean fragmentViewHasBeenCreated = false;
int helpUpdateCounterProgressBar = 0;//Just for testing
boolean animationIsWindBladRotating = false;
private boolean sunIsShiningForImagewViews = false;
private boolean helpSolarGameRectangleCorrectlyCaughtPreviously = false;
public Test() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentTestBinding.inflate(inflater, container, false);
WindowManager wm = (WindowManager) getActivity().getWindowManager();
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
widthDisplay = size.x;
heightDisplay = size.y;
//Test to set the string resources programmatically
String goalText = getString(R.string.goal);
String timeText = getString(R.string.time);
binding.textViewGoal.setText(goalText);
binding.textView3.setText(timeText);
container.getContext();
constraintLayout= binding.constraintLayout;
fragmentViewHasBeenCreated = true;
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
constraintLayout = binding.constraintLayout;
constraintSet = new ConstraintSet();
return binding.getRoot();
}//end onCreateView
@Override
public void onDestroyView() {
super.onDestroyView();
// Reset your variable to false
fragmentViewHasBeenCreated = false;
}
}
with the corrsponding xml layout file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView_Goal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/goal"
android:textSize="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/time"
android:textSize="24dp"
app:layout_constraintEnd_toEndOf="@+id/textView_Goal"
app:layout_constraintStart_toStartOf="@+id/textView_Goal"
app:layout_constraintTop_toBottomOf="@+id/textView_Goal" />
</androidx.constraintlayout.widget.ConstraintLayout>
The languages of the string resources android:text="@string/time"
and android:text="@string/goal"
never change and always remain English which is the default language.
In the folder values/string/strings.xml there are the two entries
" <string name="goal">Goal</string>
<string name="time">Time</string>"
while in the folder values/string/strings.mxl (de-rDE) there are the two entries "
<string name="goal">Ziel</string>
<string name="time">Zeit</string>"
still the laguage is not changes in the Test class no matter what I do in the FR_Options fragment class.
Update: I found out that when changing the language in the FR_Options class and I navigate back to my FR_Menu class which looks like this
public class FR_Menu extends Fragment implements View.OnClickListener{
private FragmentMenuBinding binding;
public FR_Menu() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentMenuBinding.inflate(inflater, container, false);
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
binding.buttonGame.setOnClickListener(this);
binding.buttonOptions.setOnClickListener(this);
binding.buttonHighscores.setOnClickListener(this);
binding.buttonFacts.setOnClickListener(this);
binding.buttonExit.setOnClickListener(this);
binding.buttonTest.setOnClickListener(this);
Log.e("LogTag_Menu", "Method onCreateView - this: " + this);
return binding.getRoot();
}
@Override
public void onClick(View view) {
if(view.getId() == R.id.button_game) {
Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToFRGame());
}
if(view.getId() == R.id.button_highscores) {
Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToFRHighScores());
}
if(view.getId() == R.id.button_facts) {
//Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToFRInterestingFacts());
Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToFRRVLevelSelectionMenu());
}
if(view.getId() == R.id.button_options) {
Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToFROptions());
}
if(view.getId() == R.id.button_test) {
Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToTest());
}
if(view.getId() == R.id.button_exit) {
getActivity().finishAndRemoveTask();
}
}
}
the language of the string resources are correctly changed. However, when navigating from the FR_Menu class to another class, the language of the string resources changes back to the default (English) again. Why is this happening?
Reminder: Does anybody have an idea as to why this is happening and how to solve this problem?
currentLanguageOfTheApp
is a local variable to FR_Options
; so it's not globally saved in persistent storage.
You need to save it persistently typically in SharedPrefs
or DataStore
, so when the activity is recreated you can get last saved language by the user.
With SharedPreference
, something like
private static final String LANGUAGE = "LANGUAGE";
private static final String SHARED_PREFS_NAME= "SHARED_PREFS_NAME";
private static String getLanguage(Context context) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
return prefs.getString(LANGUAGE, "en");
}
private static String setLanguage(Context context, String language) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(LANGUAGE, language);
editor.apply();
}
And when changing the language by the user, set that to the SharedPrefs, and restart the activity:
@Override
public void onClick(View view) {
if(view.getId() == R.id.imageButtonGermany) {
setLanguage(requireContext(), "de");
}
if(view.getId() == R.id.imageButtonUK) {
setLanguage(requireContext(), "en");
}
// restart the activity
requireActivity().finish();
requireActivity().startActivity(new Intent(requireActivity(), MainActivity.class));
}
So far this will just save the new languange in a persistent storage, and restarted the activity, but to apply the new language when the activity is recreated, the new baseContext
needs to map the string file of the new language instead of the default language String file. So, you need a context wrapper to wrap the new language in the new configuration of the baseContext
when the activity is recreated.
In code, you'd create the below ContextWrapper
:
import android.annotation.TargetApi;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Build;
import java.util.Locale;
public class LanguageContextWrapper extends ContextWrapper {
public LanguageContextWrapper(Context base) {
super(base);
}
public static ContextWrapper wrap(Context context, String language) {
Configuration config = context.getResources().getConfiguration();
Locale sysLocale;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
sysLocale = getSystemLocale(config);
} else {
sysLocale = getSystemLocaleLegacy(config);
}
if (!language.isEmpty() && !sysLocale.getLanguage().equals(language)) {
Locale locale = new Locale(language);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setSystemLocale(config, locale);
} else {
setSystemLocaleLegacy(config, locale);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context = context.createConfigurationContext(config);
} else {
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
}
return new LanguageContextWrapper(context);
}
public static Locale getSystemLocaleLegacy(Configuration config) {
return config.locale;
}
@TargetApi(Build.VERSION_CODES.N)
public static Locale getSystemLocale(Configuration config) {
return config.getLocales().get(0);
}
public static void setSystemLocaleLegacy(Configuration config, Locale locale) {
config.locale = locale;
}
@TargetApi(Build.VERSION_CODES.N)
public static void setSystemLocale(Configuration config, Locale locale) {
config.setLocale(locale);
}
}
And use it in activity's attachBaseContext()
callback:
class MyActivity extends AppCompatActivity {
@Override
protected void attachBaseContext(Context newBase) {
String language = getLanguage(newBase);
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, language));
setLocale(getLanguage(newBase));
}
private void setLocale(String language) {
Locale locale = new Locale(language);
Resources resources = getBaseContext().getResources();
Configuration conf = resources.getConfiguration();
conf.setLocale(locale);
resources.updateConfiguration(conf, resources.getDisplayMetrics());
}
// ........ reset of your activity
}