#android #multilingual
#Android #многоязычный
Вопрос:
У меня есть приложение с фрагментом для выбора языка (FR_LanguageSelection), который вы можете увидеть здесь:
package com.example.td.bapp;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.td.bapp.databinding.FragmentLanguageSelectionBinding;
/**
Fragment for selecting the language of the app via ImageButtons
*
*/
public class FR_LanguageSelection extends Fragment implements View.OnClickListener {
/*
String specifying the language of the App
*/
public static String currentLanguageOfTheApp;
public static final String LANGUAGE_GERMAN = "German";
public static final String LANGUAGE_ENGLISH = "English";
public FR_LanguageSelection() {
// Required empty public constructor
}
public static FR_LanguageSelection newInstance(String param1, String param2) {
FR_LanguageSelection fragment = new FR_LanguageSelection();
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
private FragmentLanguageSelectionBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
binding = FragmentLanguageSelectionBinding.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);
}
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
@Override
public void onClick(View view) {
if(view.getId() == R.id.imageButtonGermany) {
this.currentLanguageOfTheApp = LANGUAGE_GERMAN;
Navigation.findNavController(view).navigate
(FR_LanguageSelectionDirections.actionFRLanguageSelectionToFRMenu());
}
if(view.getId() == R.id.imageButtonUK) {
this.currentLanguageOfTheApp = LANGUAGE_ENGLISH;
Navigation.findNavController(view).navigate(FR_LanguageSelectionDirections.actionFRLanguageSelectionToFRMenu());
}
}
}
Таким образом, пользователь может просто выбрать язык, а затем язык выбирается в статической переменной, которая мне нужна в других фрагментах, например, для запросов к базе данных. В принципе, это работает так, как и должно быть. Однако я создал файлы XML-макета, которые используют строковые ресурсы для двух разных языков. Вот пример текстового представления со строковым ресурсом «android:text=»@string /size»»
<TextView
android:id="@ id/textViewS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/size"
android:textColor="#000000"
android:textSize="@dimen/_15ssp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@ id/radioGroup_Size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.025"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@ id/radioGroup_Size"
app:layout_constraintVertical_bias="0.50" />
Теперь мой вопрос в том, как я могу сообщить этому layoutfile, что он должен использовать строковый ресурс на основе выбранного языка пользователя? Могу ли я сделать это из FR_LanguageSelection напрямую или я должен делать это в классе Java каждого фрагмента программно (что, я думаю, не имеет смысла, потому что если да, то зачем мне определять строковые ресурсы)?
Я был бы признателен за каждый комментарий и буду очень благодарен за вашу помощь.
Обновить:
Я просто скопировал предложенный код «SlothCoding» (см. Его Ответ) в свой фрагмент выбора языка. Поэтому, когда пользователь выбирает немецкий язык с помощью ImageButton в методе onClick, код должен быть выполнен (и, следовательно, должен быть установлен текущий язык приложения). К сожалению, я получаю сообщение об ошибке компилятора: «Не удается разрешить символ ‘activity'». Может быть, ошибка возникает из-за того, что я использую подход с одним действием и несколькими фрагментами?
Вот обновленный класс:
package com.example.td.bapp;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.td.bapp.databinding.FragmentLanguageSelectionBinding;
import java.util.Locale;
/**
Fragment for selecting the language of the app via ImageButtons
*
*/
public class FR_LanguageSelection extends Fragment implements View.OnClickListener {
/*
String specifying the language of the App
*/
public static String currentLanguageOfTheApp;
public static final String LANGUAGE_GERMAN = "German";
public static final String LANGUAGE_ENGLISH = "English";
public FR_LanguageSelection() {
// Required empty public constructor
}
public static FR_LanguageSelection newInstance(String param1, String param2) {
FR_LanguageSelection fragment = new FR_LanguageSelection();
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
private FragmentLanguageSelectionBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
binding = FragmentLanguageSelectionBinding.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);
}
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) {
this.currentLanguageOfTheApp = LANGUAGE_GERMAN;
Locale locale;
locale = new Locale("de", "DE");
Configuration config = new Configuration(activity.getBaseContext().getResources().getConfiguration());
Locale.setDefault(locale);
config.setLocale(locale);
activity.getBaseContext().getResources().updateConfiguration(config,
activity.getBaseContext().getResources().getDisplayMetrics());
Navigation.findNavController(view).navigate
(FR_LanguageSelectionDirections.actionFRLanguageSelectionToFRMenu());
}
if(view.getId() == R.id.imageButtonUK) {
this.currentLanguageOfTheApp = LANGUAGE_ENGLISH;
Navigation.findNavController(view).navigate(FR_LanguageSelectionDirections.actionFRLanguageSelectionToFRMenu());
}
}
}
Ответ №1:
Это не лучший способ сделать это, поскольку Android должен использовать настройки локали с телефона, чтобы установить язык по умолчанию для вашего приложения. Но мне пришлось сделать то же самое в моем приложении год назад, и я был вынужден использовать конфигурацию локали внутри приложения. Вот как это происходит. Сначала я прошу пользователя выбрать язык, который он хочет, и сохранить его в my SharedPreferences
. У меня есть список языков, которые я создаю, поскольку JSONObjects
они имеют
- имя_языка (например, английский)
- language_code (например, ru)
- language_script (например, US)
Вот как я это делаю:
language_object = new JSONObject();
language_object.put("language_name", "Deutsch");
language_object.put("language_code", "de");
language_object.put("language_script", "LT");
languages.put(language_object);
language_object = new JSONObject();
language_object.put("language_name", "English");
language_object.put("language_code", "en");
language_object.put("language_script", "US");
languages.put(language_object);
Теперь я показываю список этих языков, использующих ListView
и обрабатывающих клики внутри LanguageListAdapter
. Когда пользователь выбирает язык, который он хочет, я делаю это внутри ListAdapter
:
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
if(convertView == null) {
LayoutInflater layout_inflater = LayoutInflater.from(_context);
convertView = layout_inflater.inflate(R.layout.item_language, parent, false);
}
try {
JSONObject object = _data_set.getJSONObject(position);
TextView lang_name = convertView.findViewById(R.id.language_name);
lang_name.setText(object.getString("language_name"));
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
SharedPreferences shared_preferences = activity.getBaseContext().getSharedPreferences("MY_PREFERENCES", MODE_PRIVATE);
SharedPreferences.Editor preferences_editor = shared_preferences.edit();
preferences_editor.putString("MY_LANGUAGE", object.toString());
preferences_editor.apply();
common.setLocale(activity);
Intent intent = new Intent(activity, LoadingActivity.class);
activity.startActivity(intent);
activity.finish();
} catch (Exception e) {
e.getMessage();
}
}
});
} catch (Exception e) {
e.getMessage();
}
return convertView;
}
И моя функция setLocale(Activity activity)
находится внутри отдельного класса с именем common
. Я использую этот класс для хранения функций, которые я часто использую в своем приложении. И вот как это выглядит:
public static void setLocale(Activity activity){
try {
SharedPreferences shared_preferences = activity.getBaseContext().getSharedPreferences("MY_PREFERENCES", MODE_PRIVATE);
String language = shared_preferences.getString("MY_LANGUAGE", "");
JSONObject object = new JSONObject(language);
Locale locale;
locale = new Locale(object.getString("language_code"), object.getString("language_script"));
Configuration config = new Configuration(activity.getBaseContext().getResources().getConfiguration());
Locale.setDefault(locale);
config.setLocale(locale);
activity.getBaseContext().getResources().updateConfiguration(config,
activity.getBaseContext().getResources().getDisplayMetrics());
} catch (Exception e) {
e.getMessage();
}
}
Это задает язык, который я хочу, и использует strings.xml
на основе language_code
и language_script
. Итак, в моем res > values
примере у меня есть это, например:
strings.xml
— язык по умолчаниюstrings.xml(en-rUS)
— Английский языкstrings.xml(de-rLT)
— Пользовательский немецкий язык (под пользовательским я подразумеваю пользовательский код-тег скрипта)
Теперь, когда пользователь выбирает, скажем, немецкий язык, я сохраняю «de-LT» как код для языка и использую его внутри setLocale
, чтобы приложение могло читать из этого strings.xml
ресурса.
РЕДАКТИРОВАТЬ: как создать strings.xml
для каждого языка?
Итак, для этого вам нужно сначала перейти в res > values
папку. Там у вас будет ваш strings.xml
файл по умолчанию. Этот используется, когда вы не определили strings.xml
для пользователей языковые настройки телефона. По умолчанию это должен быть английский, если приложение можно использовать на международном уровне, причина проста, и вы, вероятно, знаете почему.
В этом strings.xml
вы определяете все свои строки, которые вы используете в своем приложении, и делаете это следующим образом:
<resources>
<string name="app_name">TempProject</string>
<string name="Size">Size</string>
</resources>
Теперь вы хотите создать перевод, скажем, для немецкого и хорватского языков. Вам нужно это сделать. Щелкните правой кнопкой мыши папку значений> Создать> Файл ресурсов значений
Теперь откроется новое окно, и вам нужно заполнить его следующим образом:
Итак, давайте двигаться медленно, поле за полем.
- Имя файла:
strings.xml
— То же имя, что и у файла по умолчанию - Имя каталога:
values-de-rDE
— Это определяет имя каталога с кодом немецкой страны, так компилятор узнает, из какого файла читать заданную вами строку. Если вы не знаете коды стран, вы можете просто использоватьAvailable qualifiers
и выбрать Locale> В языковом разделе выберите «de: немецкий»> Из определенного региона выберите только то, что вы хотите, в моем случае я использовал «DE: Germany». Это автоматически сгенерирует то же имя каталога, что и выше:
Теперь у вас есть новый strings.xml
файл, но у него есть дополнительный тег справа, вот так (de-rDE)
. Теперь внутри этого файла просто определите ту же строку, что и по умолчанию strings.xml
:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="Size">Größe</string>
</resources>
Теперь вы можете видеть, что у меня нет определенной строки для имени приложения внутри этого файла ресурсов, и вы получите предупреждение об этом. Когда вы переходите к умолчанию strings.xml вы увидите это:
Поэтому просто убедитесь, что вы определили одни и те же строки во всех файлах из strings.xml
files.
Я последовал всему вышесказанному и добавил дополнительные переводы для хорватского, а также для английского языков, помимо файла по умолчанию. Итак, теперь у меня есть это:
Теперь вы можете использовать мою функцию setLocale()
и внутри этой строки просто указать код страны:
Locale locale;
locale = new Locale("de", "DE");
Configuration config = new Configuration(activity.getBaseContext().getResources().getConfiguration());
Locale.setDefault(locale);
config.setLocale(locale);
activity.getBaseContext().getResources().updateConfiguration(config,
activity.getBaseContext().getResources().getDisplayMetrics());
Вы можете видеть, что для второго аргумента я ввел только «DE», а не «rDE». Это установит языковой стандарт на немецкий язык, и ваше приложение будет использовать strings.xml (de-rDE)
file для строковых ресурсов.
Комментарии:
1. Спасибо за ваш ответ SlothCoding. В принципе, я забыл упомянуть, что на одном устройстве будут разные пользователи, поэтому я думаю, что не смогу использовать общие настройки. Кроме того, с вашим подходом нужно было бы реализовать отображение элементов пользовательского интерфейса в каждом фрагменте, зависящем от языка, хотя я использовал строковые ресурсы при создании макета. Итак, какая польза от использования этих строковых ресурсов, если мне все равно нужно программно устанавливать текст элементов пользовательского интерфейса?
2. Большое спасибо SlothCoding за вашу большую помощь. Теперь, похоже, все работает так, как задумано :-). Я действительно ценю ваши усилия. Я поддержал и принял ваш ответ.
3. Да, это сработает, как я уже сказал. Если у вас его нет с этим тегом, он будет использоваться по умолчанию. Это именно то, что вам больше всего нравится, и как вы хотите это сделать. В любом случае, счастливого кодирования!
4. На самом деле ничего, это будет почти то же самое, за исключением того, что у него будет тег (en-rUS), скажем. Это просто для удобства чтения кода, поэтому, когда кто-то просматривает ваш код, он может сказать: «Хорошо, сейчас он устанавливает английский»
5. @VanessaF Я не уверен, почему это так, но у меня было странное поведение при использовании WebView в качестве дочернего элемента в моем макете. Сразу после того, как я поработал с web view в своем коде, я просто снова вызвал функцию setLocale(), и она работала нормально. Я гуглил около недели, чтобы решить эту проблему, но я предполагаю, что это то, что должно быть исправлено Google, и я не смог найти решение только у группы людей с той же проблемой. Возможно, попробуйте поискать в Google что-то вроде «Строковый ресурс не работает с нижней панелью навигации» или что-то подобное.