Impostare la localizzazione a livello di programmazione
-
13-11-2019 - |
Domanda
La mia app supporta 3 (presto 4) lingue. Dato che diversi locali sono abbastanza simili, vorrei dare all'utente la possibilità di cambiare la localizzazione nella mia domanda, ad esempio una persona italiana potrebbe preferire lo spagnolo rispetto all'inglese.
Esiste un modo per l'utente di selezionare tra i locali disponibili per l'applicazione e quindi cambiare quale locale viene utilizzato? Non lo vedo come un problema per impostare la località per ogni attività poiché è un compito semplice da eseguire in una classe base.
Soluzione
Per le persone ancora alla ricerca di questa risposta, da allora configuration.locale
è stato deprecato dall'API 24, ora puoi usare:
configuration.setLocale(locale);
Prendi in considerazione che il minskdversion per questo metodo è API 17.
Codice di esempio completo:
@SuppressWarnings("deprecation")
private void setLocale(Locale locale){
SharedPrefUtils.saveLocale(locale); // optional - Helper method to save the selected language to SharedPreferences in case you might need to attach to activity context (you will need to code this)
Resources resources = getResources();
Configuration configuration = resources.getConfiguration();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
configuration.setLocale(locale);
} else{
configuration.locale=locale;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
getApplicationContext().createConfigurationContext(configuration);
} else {
resources.updateConfiguration(configuration,displayMetrics);
}
}
Non dimenticarlo, se si cambia il locale con un'attività in esecuzione, dovrai riavviarlo per avere effetto le modifiche.
Modifica l'11 maggio 2018
Come dal post di @Cookiemonster, potresti avere problemi a mantenere la modifica del locale nelle versioni API più alte. In tal caso, aggiungi il seguente codice alla tua attività di base in modo da aggiornare la posizione di contesto su ogni creazione di attività:
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(updateBaseContextLocale(base));
}
private Context updateBaseContextLocale(Context context) {
String language = SharedPrefUtils.getSavedLanguage(); // Helper method to get saved language from SharedPreferences
Locale locale = new Locale(language);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResourcesLocale(context, locale);
}
return updateResourcesLocaleLegacy(context, locale);
}
@TargetApi(Build.VERSION_CODES.N)
private Context updateResourcesLocale(Context context, Locale locale) {
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
return context.createConfigurationContext(configuration);
}
@SuppressWarnings("deprecation")
private Context updateResourcesLocaleLegacy(Context context, Locale locale) {
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
configuration.locale = locale;
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return context;
}
Se lo usi, non dimenticare di salvare la lingua in preferenze condivise quando si imposta il locale con setLocate(locale)
Altri suggerimenti
Spero che questo aiuto (in OnResume):
Locale locale = new Locale("ru");
Locale.setDefault(locale);
Configuration config = getBaseContext().getResources().getConfiguration();
config.locale = locale;
getBaseContext().getResources().updateConfiguration(config,
getBaseContext().getResources().getDisplayMetrics());
Poiché nessuna risposta è completa per il modo attuale per risolvere questo problema, cerco di fornire istruzioni per una soluzione completa. Si prega di commentare se manca qualcosa o potrebbe essere fatto meglio.
Informazione Generale
Innanzitutto, esistono alcune librerie che vogliono risolvere il problema ma sembrano tutte obsolete o mancano alcune funzionalità:
- https://github.com/delight-im/android-guages
- Obsoleto (vedi questioni)
- Quando si seleziona una lingua nell'interfaccia utente, mostra sempre tutte le lingue (codificata in biblioteca), non solo quelli in cui esistono traduzioni
- https://github.com/akexorcist/android-calizationActivity
- Sembra abbastanza sofisticato e potrebbe usare un approccio simile a quello mostrato di seguito
Inoltre, penso che scrivere una biblioteca potrebbe non essere un modo buono/semplice per risolvere questo problema perché non c'è molto da fare, e ciò che deve essere fatto è piuttosto cambiare il codice esistente piuttosto che usare qualcosa di completamente disaccoppiato. Pertanto ho composto le seguenti istruzioni che dovrebbero essere complete.
La mia soluzione si basa principalmente su https://github.com/gunhansancar/changelanguageexample (come già collegato a Localhost). È il codice migliore che ho trovato per orientarmi. Alcune osservazioni:
- Se necessario, fornisce diverse implementazioni per modificare i locali per Android N (e oltre) e inferiore
- Utilizza un metodo
updateViews()
In ogni attività per aggiornare manualmente tutte le stringhe dopo aver modificato la localizzazione (usando il solitogetString(id)
) che non è necessario nell'approccio mostrato di seguito - Supporta solo le lingue e non i locali completi (che includono anche i codici della regione (paese) e delle varianti)
L'ho cambiato un po ', disaccoppiando la parte che persiste il locale scelto (come si potrebbe voler farlo separatamente, come suggerito di seguito).
Soluzione
La soluzione è costituita dai seguenti due passaggi:
- Modifica in modo permanente il locale per essere utilizzato dall'app
- Fai in modo che l'app utilizza il set di locali personalizzato, senza riavvio
Passaggio 1: modificare il locale
Usa la classe LocaleHelper
, basato su Gunhansancar's Localihelper:
- Aggiungere un
ListPreference
in unPreferenceFragment
con le lingue disponibili (deve essere mantenuto quando le lingue devono essere aggiunte in seguito)
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.preference.PreferenceManager;
import java.util.Locale;
import mypackage.SettingsFragment;
/**
* Manages setting of the app's locale.
*/
public class LocaleHelper {
public static Context onAttach(Context context) {
String locale = getPersistedLocale(context);
return setLocale(context, locale);
}
public static String getPersistedLocale(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getString(SettingsFragment.KEY_PREF_LANGUAGE, "");
}
/**
* Set the app's locale to the one specified by the given String.
*
* @param context
* @param localeSpec a locale specification as used for Android resources (NOTE: does not
* support country and variant codes so far); the special string "system" sets
* the locale to the locale specified in system settings
* @return
*/
public static Context setLocale(Context context, String localeSpec) {
Locale locale;
if (localeSpec.equals("system")) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
locale = Resources.getSystem().getConfiguration().getLocales().get(0);
} else {
//noinspection deprecation
locale = Resources.getSystem().getConfiguration().locale;
}
} else {
locale = new Locale(localeSpec);
}
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResources(context, locale);
} else {
return updateResourcesLegacy(context, locale);
}
}
@TargetApi(Build.VERSION_CODES.N)
private static Context updateResources(Context context, Locale locale) {
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
configuration.setLayoutDirection(locale);
return context.createConfigurationContext(configuration);
}
@SuppressWarnings("deprecation")
private static Context updateResourcesLegacy(Context context, Locale locale) {
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
configuration.locale = locale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLayoutDirection(locale);
}
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return context;
}
}
Creare un SettingsFragment
Come i seguenti:
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import mypackage.LocaleHelper;
import mypackage.R;
/**
* Fragment containing the app's main settings.
*/
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String KEY_PREF_LANGUAGE = "pref_key_language";
public SettingsFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_settings, container, false);
return view;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
switch (key) {
case KEY_PREF_LANGUAGE:
LocaleHelper.setLocale(getContext(), PreferenceManager.getDefaultSharedPreferences(getContext()).getString(key, ""));
getActivity().recreate(); // necessary here because this Activity is currently running and thus a recreate() in onResume() would be too late
break;
}
}
@Override
public void onResume() {
super.onResume();
// documentation requires that a reference to the listener is kept as long as it may be called, which is the case as it can only be called from this Fragment
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
}
Crea una risorsa locales.xml
Elencare tutte le località con traduzioni disponibili nel modo seguente (Elenco dei codici locali):
<!-- Lists available locales used for setting the locale manually.
For now only language codes (locale codes without country and variant) are supported.
Has to be in sync with "settings_language_values" in strings.xml (the entries must correspond).
-->
<resources>
<string name="system_locale" translatable="false">system</string>
<string name="default_locale" translatable="false"></string>
<string-array name="locales">
<item>@string/system_locale</item> <!-- system setting -->
<item>@string/default_locale</item> <!-- default locale -->
<item>de</item>
</string-array>
</resources>
Nel tuo PreferenceScreen
È possibile utilizzare la sezione seguente per consentire all'utente di selezionare le lingue disponibili:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/preferences_category_general">
<ListPreference
android:key="pref_key_language"
android:title="@string/preferences_language"
android:dialogTitle="@string/preferences_language"
android:entries="@array/settings_language_values"
android:entryValues="@array/locales"
android:defaultValue="@string/system_locale"
android:summary="%s">
</ListPreference>
</PreferenceCategory>
</PreferenceScreen>
che utilizza le seguenti stringhe da strings.xml
:
<string name="preferences_category_general">General</string>
<string name="preferences_language">Language</string>
<!-- NOTE: Has to correspond to array "locales" in locales.xml (elements in same orderwith) -->
<string-array name="settings_language_values">
<item>Default (System setting)</item>
<item>English</item>
<item>German</item>
</string-array>
Passaggio 2: effettuare l'app Utilizzare il locale personalizzato
Ora imposta ogni attività per utilizzare il set di locali personalizzato. Il modo più semplice per raggiungere questo obiettivo è avere una classe base comune per tutte le attività con il seguente codice (in cui si trova il codice importante attachBaseContext(Context base)
e onResume()
):
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import mypackage.LocaleHelper;
import mypackage.R;
/**
* {@link AppCompatActivity} with main menu in the action bar. Automatically recreates
* the activity when the locale has changed.
*/
public class MenuAppCompatActivity extends AppCompatActivity {
private String initialLocale;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initialLocale = LocaleHelper.getPersistedLocale(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_settings:
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleHelper.onAttach(base));
}
@Override
protected void onResume() {
super.onResume();
if (initialLocale != null && !initialLocale.equals(LocaleHelper.getPersistedLocale(this))) {
recreate();
}
}
}
Quello che fa è
- Oltrepassare
attachBaseContext(Context base)
Per utilizzare la locale precedentemente persistita conLocaleHelper
- Rileva un cambio del locale e ricreare l'attività per aggiornare le sue stringhe
Note su questa soluzione
La ricreazione di un'attività non aggiorna il titolo di Actionbar (come già osservato qui: https://github.com/gunhansancar/changelanguageexample/issues/1).
- Questo può essere ottenuto semplicemente avendo un
setTitle(R.string.mytitle)
nelonCreate()
metodo di ogni attività.
- Questo può essere ottenuto semplicemente avendo un
Consente all'utente di scegliere il sistema predefinito del sistema, nonché il locale predefinito dell'app (che può essere nominato, in questo caso "inglese").
Solo codici linguistici, nessun regione (paese) e codici varianti (come
fr-rCA
) sono supportati finora. Per supportare le specifiche complete della localizzazione, un parser simile a quello del Biblioteca Android-Languages può essere utilizzato (che supporta la regione ma nessun codice variante).- Se qualcuno trova o ha scritto un buon parser, aggiungi un commento in modo da poterlo includere nella soluzione.
Ho avuto un problema con l'impostazione di una locale a livello di programmazione con i dispositivi che hanno Android OS n e superiore. Per me la soluzione era scrivere questo codice nella mia attività di base:
(Se non hai un'attività di base, allora dovresti apportare questi cambiamenti in tutte le tue attività)
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(updateBaseContextLocale(base));
}
private Context updateBaseContextLocale(Context context) {
String language = SharedPref.getInstance().getSavedLanguage();
Locale locale = new Locale(language);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResourcesLocale(context, locale);
}
return updateResourcesLocaleLegacy(context, locale);
}
@TargetApi(Build.VERSION_CODES.N)
private Context updateResourcesLocale(Context context, Locale locale) {
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
return context.createConfigurationContext(configuration);
}
@SuppressWarnings("deprecation")
private Context updateResourcesLocaleLegacy(Context context, Locale locale) {
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
configuration.locale = locale;
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return context;
}
Nota che qui non è sufficiente chiamare
createConfigurationContext(configuration)
È inoltre necessario ottenere il contesto che questo metodo restituisce e quindi impostare questo contesto in attachBaseContext
metodo.
@SuppressWarnings("deprecation")
public static void forceLocale(Context context, String localeCode) {
String localeCodeLowerCase = localeCode.toLowerCase();
Resources resources = context.getApplicationContext().getResources();
Configuration overrideConfiguration = resources.getConfiguration();
Locale overrideLocale = new Locale(localeCodeLowerCase);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
overrideConfiguration.setLocale(overrideLocale);
} else {
overrideConfiguration.locale = overrideLocale;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context.getApplicationContext().createConfigurationContext(overrideConfiguration);
} else {
resources.updateConfiguration(overrideConfiguration, null);
}
}
Basta usare questo metodo Helper per forzare il locale specifico.
Udpate 22 agosto 2017. migliore utilizzo questo approccio.
Aggiungi una classe helper con il seguente metodo:
public class LanguageHelper {
public static final void setAppLocale(String language, Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Resources resources = activity.getResources();
Configuration configuration = resources.getConfiguration();
configuration.setLocale(new Locale(language));
activity.getApplicationContext().createConfigurationContext(configuration);
} else {
Locale locale = new Locale(language);
Locale.setDefault(locale);
Configuration config = activity.getResources().getConfiguration();
config.locale = locale;
activity.getResources().updateConfiguration(config,
activity.getResources().getDisplayMetrics());
}
}
}
E chiamalo nella tua attività di avvio, come MainActivity.java
:
public void onCreate(Bundle savedInstanceState) {
...
LanguageHelper.setAppLocale("fa", this);
...
}
Semplice e facile
Locale locale = new Locale("en", "US");
Resources res = getResources();
DisplayMetrics dm = res.getDisplayMetrics();
Configuration conf = res.getConfiguration();
conf.locale = locale;
res.updateConfiguration(conf, dm);
dove "en" è il codice linguistico e "noi" è il codice del paese.
/**
* Requests the system to update the list of system locales.
* Note that the system looks halted for a while during the Locale migration,
* so the caller need to take care of it.
*/
public static void updateLocales(LocaleList locales) {
try {
final IActivityManager am = ActivityManager.getService();
final Configuration config = am.getConfiguration();
config.setLocales(locales);
config.userSetLocale = true;
am.updatePersistentConfiguration(config);
} catch (RemoteException e) {
// Intentionally left blank
}
}
C'è un modo super semplice.
In basiattività, attività o frammento di sovraccarico di allegateContext
override fun attachBaseContext(context: Context) {
super.attachBaseContext(context.changeLocale("tr"))
}
estensione
fun Context.changeLocale(language:String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val config = this.resources.configuration
config.setLocale(locale)
return createConfigurationContext(config)
}
Valido per API16 a API28 Basta posizionare questo metodo in cui:
Context newContext = context;
Locale locale = new Locale(languageCode);
Locale.setDefault(locale);
Resources resources = context.getResources();
Configuration config = new Configuration(resources.getConfiguration());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
config.setLocale(locale);
newContext = context.createConfigurationContext(config);
} else {
config.locale = locale;
resources.updateConfiguration(config, resources.getDisplayMetrics());
}
return newContext;
}
Inserisci questo codice in tutte le tue attività usando:
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(localeUpdateResources(base, "<-- language code -->"));
}
Oppure chiama LocaleupDateSources su frammenti, adattatori, ecc. Dove hai bisogno del nuovo contesto.
Crediti: Yaroslav Berezanskyi
Metti questo codice nella tua attività
if (id==R.id.uz)
{
LocaleHelper.setLocale(MainActivity.this, mLanguageCode);
//It is required to recreate the activity to reflect the change in UI.
recreate();
return true;
}
if (id == R.id.ru) {
LocaleHelper.setLocale(MainActivity.this, mLanguageCode);
//It is required to recreate the activity to reflect the change in UI.
recreate();
}