Android не меняет язык строковых ресурсов во фрагменте

У меня есть приложение со строковыми ресурсами для немецкого и английского языков. Я определил отдельный фрагмент для изменения языка, который вы можете увидеть здесь.

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());


        }


    }


}

Теперь, когда я перехожу к тестовому фрагменту, файл Java которого выглядит так

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;

    }
}

с соответствующим файлом макета XML

<?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>

Языки строковых ресурсов android:text = "@string/time" и android:text = "@string/goal" никогда не меняются и всегда остаются английскими, которые являются языком по умолчанию.

В папкеvalues/string/strings.xml есть две записи

"    <string name = "goal">Goal</string>
    <string name = "time">Time</string>"

а в папкеvalues/string/strings.mxl(de-rDE) есть две записи "

    <string name = "goal">Ziel</string> 
<string name = "time">Zeit</string>"

тем не менее, язык не меняется в классе Test, независимо от того, что я делаю в классе фрагмента FR_Options.

Обновление: я обнаружил, что при смене языка в классе FR_Options я возвращаюсь к своему классу FR_Menu, который выглядит следующим образом.

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();
        }

    }
}

язык строковых ресурсов изменен правильно. Однако при переходе из класса FR_Menu в другой класс язык строковых ресурсов снова меняется на язык по умолчанию (английский). Почему это происходит?

Напоминание: есть ли у кого-нибудь идеи, почему это происходит и как решить эту проблему?

Почему вы обрабатываете изменение языка в самом фрагменте, а не делегируете его действию, в котором размещен фрагмент?

tomerpacific 27.04.2024 14:02

@tomerpacific: Спасибо, Томер, за твой комментарий. Что вы подразумеваете под «делегированием действия, в котором размещен фрагмент»? Насколько я понимаю, я делаю это с помощью кода getActivity().getBaseContext().getResources().updateConfigur‌​ation(config, getActivity().getBaseContext().getResources().getDisplayMetr‌​ics());, но, к сожалению, это не меняет язык в других фрагментах.

VanessaF 01.05.2024 09:50

@tomerpacific: есть какие-нибудь комментарии к моему последнему комментарию? Я буду очень признателен за любые дальнейшие комментарии от вас

VanessaF 03.05.2024 15:34

Вам нужна контекстная оболочка, чтобы обернуть новый язык в новую конфигурацию при воссоздании действия.

Zain 09.05.2024 22:19

@Zain: Спасибо за ваш комментарий. Что вы на самом деле подразумеваете под «контекстной оберткой» и как я могу использовать ее при воссоздании активности? Может быть, вы можете привести мне пример того, как это может выглядеть в моем коде?

VanessaF 10.05.2024 10:39

@VanessaF, почему бы вместо этого не использовать официально рекомендованный API языка для каждого приложения? Это проще и понятнее реализовать. Developer.android.com/guide/topics/resources/app-languages#j‌​ava

ltp 15.05.2024 16:19

@Itp: Спасибо за ваш комментарий. Как я могу использовать этот подход для изменения языка?

VanessaF 15.05.2024 22:06
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
7
227
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

currentLanguageOfTheApp — локальная переменная для FR_Options; поэтому он не сохраняется глобально в постоянном хранилище.

Вам необходимо постоянно сохранять его, обычно в SharedPrefs или DataStore, чтобы при воссоздании действия вы могли получить последний сохраненный пользователем язык.

С SharedPreference что-то вроде

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();
}   

А при изменении языка пользователем установите для него значение SharedPrefs и перезапустите действие:

@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));

}

На данный момент это просто сохранит новый язык в постоянном хранилище и перезапустит действие, но чтобы применить новый язык при воссоздании действия, новый baseContext должен сопоставить строковый файл нового языка вместо строки языка по умолчанию. файл. Итак, вам нужна контекстная оболочка, чтобы обернуть новый язык в новую конфигурацию baseContext при воссоздании активности.

В коде вы должны создать следующее 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);
    }
}

И используйте его в обратном вызове attachBaseContext() активности:

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
    
}

Спасибо за Ваш ответ. К сожалению, у меня проблемы с пониманием этого, поскольку я не знаю, куда добавить этот код. Например, методы get и setLanguage, должны ли они находиться во фрагменте FR_Options или во фрагменте основного действия?

VanessaF 14.05.2024 17:19

Нет ли более простого способа сделать это? В другом приложении я просто делаю как в этом приложении. Никакой разницы нет вообще (я на самом деле скопировал и вставил код), и там все работает нормально без всех этих подходов Shared Preferences и Wrapper, которые вы предлагаете. Но я так и не понял, почему в этом приложении это не работает. Может быть, вы можете придумать другой подход, как сделать это без оболочки и общих настроек?

VanessaF 14.05.2024 17:20

@VanessaF Как сохранить новый язык при следующем запуске другого приложения? вы не используете постоянное хранилище?

Zain 14.05.2024 22:15

Нет, я не использую подход с общими предпочтениями. Кажется, это вносит ненужную сложность. В другом моем приложении пользователь должен при каждом запуске решать, какой язык выбрать. Если бы я хотел иметь постоянное хранилище, я бы предпочел, например, использовать базу данных sql. Таким образом, это намного проще, чем использование ContextWrappers и SharedPreferences.

VanessaF 15.05.2024 22:05

Где в предложенном вами коде должны быть методы get и setLanguage? Это отдельный класс или они должны быть в основной деятельности?

VanessaF 15.05.2024 22:08

Это во фрагменте FR_Options, как он только что оттуда и называется.

Zain 15.05.2024 22:37

Большое спасибо Зейн за ваш ответ. Теперь это работает, и я наградил вас наградой :-). В коде небольшая ошибка. В методе private static String setLanguage(Context context, String language) { вы ничего не возвращаете. Поэтому мне пришлось изменить объявление метода на void

VanessaF 17.05.2024 12:14

Просто из любопытства я хотел бы спросить, какие преимущества имеет предлагаемый вами подход с общими настройками по сравнению с простым использованием простого запроса к базе данных sql для постоянного хранения используемого в данный момент языка? При использовании запроса sql вам потребуется гораздо меньше кода, и это снизит сложность, особенно из-за того, что не потребуется никаких дополнительных классов.

VanessaF 19.05.2024 11:03

Что ж, в этом отношении вы правы... но база данных SQL в основном используется для хранения набора структурированных данных с разными полями и столбцами. Но вам просто нужно сохранить одно значение для языка. Вероятно, поддержание базы данных SQL также потребует некоторых усилий.

Zain 19.05.2024 11:42

Большое спасибо за ваш комментарий. Я использую базы данных SQLite для всего, поскольку это очень просто, быстро и снижает сложность. Вы написали: «Но вам просто нужно сохранить одно значение для языка» --> Я бы сказал наоборот. Если мне просто нужно сохранить одно значение, почему я должен использовать такой очень сложный подход с общими настройками и дополнительными классами, который просто вносит ненужную сложность и много-много строк дополнительного кода, в то время как в SQLite вам просто нужны 2 очень маленьких get и установить методы.

VanessaF 19.05.2024 11:50

Под капотом SQLite есть накладные расходы на создание базы данных, ее открытие и закрытие, а не просто так, как вы ее кодируете; для доступа требуется даже фоновый поток. Также SharedPreference теперь заменен на DataStore. Я только что добавил SharedPrefs в этот вопрос в качестве примера решения для сохранения данных; но каждое приложение имеет свое собственное управление сохранением данных, которое выходит за рамки этого вопроса. Суть в том, что вам нужен какой-то способ постоянного хранения языка, чтобы его можно было получить при следующем запуске приложения.

Zain 19.05.2024 13:23

Спасибо за ваш ответ и усилия, Зейн. У меня 2 уточняющих вопроса: 1) В предложенном вами решении я не понимаю, зачем нужен LanguageContextWrapper класс и что там вообще делается? Как уже говорилось ранее, в другом моем приложении в чем-то подобном нет необходимости. При изменении языка он остается таким, каким должен быть, независимо от того, к какому фрагменту вы переходите. 2) Я заметил, что вы используете много методов static (как в LanguageContextWrapper , так и в FR_Options). Мне 1000 раз говорили, что никогда не следует использовать статические методы, поскольку это считается плохим стилем.

VanessaF 20.05.2024 11:10

У меня еще третий вопрос: 3) Можно ли сменить язык без перезапуска активности? Таким образом, пользователь может просто выбрать язык в классе FR_Options, и после нажатия кнопки языка приложение не должно переходить к другому фрагменту.

VanessaF 20.05.2024 12:12

Для получения значения строкового ресурса требуется context, и этот контекст связан только с 1 строковым ресурсом, который загружается при запуске действия. Когда вы меняете язык, вам также необходимо отразить это изменение в контексте, чтобы выбрать новый строковый ресурс этот язык; это то, что делает LanguageContextWrapper .. Я не могу рассказать больше о другом приложении, так как понятия не имел, как оно работает .. если вы дадите мне образец рабочей облегченной версии, было бы полезно

Zain 20.05.2024 16:17
Is it possible to change the language without restarting the activity?Насколько я знаю, нет, нельзя. Вы перезапускаете активность в другом приложении?
Zain 20.05.2024 16:19

Спасибо за ответ и извините за поздний ответ (я был в отпуске). В другом моем приложении я действительно не знаю, перезапускается ли действие снова. По крайней мере, я остаюсь в том же фрагменте, чтобы вы могли изменить язык и остаться в том же фрагменте. Возможно ли с вашим подходом сменить язык и остаться в том же фрагменте? Далее вы написали: «Вам также необходимо отразить это изменение в контексте, чтобы выбрать новый строковый ресурс этого языка; это то, что LanguageContextWrapper» -> в другом моем приложении в этом нет необходимости. Он просто меняет язык и отлично работает

VanessaF 31.05.2024 16:42

Спасибо за ваши ответы. Есть комментарии к моему последнему комментарию? Я буду очень признателен за каждый дальнейший комментарий от вас.

VanessaF 05.06.2024 09:08

Другое ваше приложение является многофрагментным? если да, то другой фрагмент предоставляет новый языковой ресурс?>>>> Is it possible with your approach to change the language and stay in the same fragment? Я не думаю, что без ручных усилий не обойтись, признаю, что мне придется тщательно искать обходной путь

Zain 05.06.2024 10:36

Спасибо за ваш комментарий. Другое мое приложение имеет одно основное действие и несколько фрагментов. Я использую navGraph для навигации между ними. Когда я меняю язык в FR_Options (код которого вы можете увидеть выше в моей первой публикации кода), язык меняется для каждого фрагмента (включая строковые ресурсы). Это работает без проблем

VanessaF 05.06.2024 11:07

Просто обновите порядок этих строк в своем коде.

//here the activity reload its resources based on the new configuration
getActivity().getBaseContext().getResources().updateConfiguration(config,getActivity().getBaseContext().getResources().getDisplayMetrics());

// restart the current activity 
getActivity().recreate();
       

Спасибо за суровый ответ. Я попробовал, но это ничего не изменило. Однако сейчас я использую решение от Zain, которое работает нормально. Тем не менее, я высоко ценю ваши усилия.

VanessaF 17.05.2024 14:35

Другие вопросы по теме