Alteração da localidade no próprio aplicativo

197

Meus usuários podem alterar a localidade no aplicativo (eles podem manter as configurações do telefone em inglês, mas ler o conteúdo do aplicativo em francês, holandês ou qualquer outro idioma ...)

Por que isso está funcionando perfeitamente no 1.5 / 1.6, mas NÃO no 2.0?

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch(item.getItemId()) {
    case 201:
        Locale locale2 = new Locale("fr"); 
        Locale.setDefault(locale2);
        Configuration config2 = new Configuration();
        config2.locale = locale2;
        getBaseContext().getResources().updateConfiguration(
            config2, getBaseContext().getResources().getDisplayMetrics());
        // loading data ...
        refresh();
        // refresh the tabs and their content
        refresh_Tab ();   
     break;
     case 201: etc...

O problema é que o MENU "encolhe" cada vez mais sempre que o usuário passa pelas linhas de código acima ...

Este é o menu que é reduzido:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    menu.add(0, 100, 1, "REFRESH").setIcon(android.R.drawable.ic_menu_compass);
    SubMenu langMenu = menu.addSubMenu(0, 200, 2, "NL-FR").setIcon(android.R.drawable.ic_menu_rotate);
        langMenu.add(1, 201, 0, "Nederlands");
        langMenu.add(1, 202, 0, "Français");
    menu.add(0, 250, 4, R.string.OptionMenu2).setIcon(android.R.drawable.ic_menu_send);
    menu.add(0, 300, 5, R.string.OptionMenu3).setIcon(android.R.drawable.ic_menu_preferences);
    menu.add(0, 350, 3, R.string.OptionMenu4).setIcon(android.R.drawable.ic_menu_more);
    menu.add(0, 400, 6, "Exit").setIcon(android.R.drawable.ic_menu_delete);

    return super.onCreateOptionsMenu(menu);
}

O que devo fazer no nível 5 da API para que isso funcione novamente?

Eis o código completo, se você deseja testar isso:

import java.util.Locale;

import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.widget.Toast;

public class Main extends Activity {
    /** Called when the activity is first created. */


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        SubMenu langMenu = menu.addSubMenu(0, 200, 2, "NL-FR").setIcon(android.R.drawable.ic_menu_rotate);
            langMenu.add(1, 201, 0, "Nederlands");
            langMenu.add(1, 202, 0, "Français");

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch(item.getItemId()){

        case 201:

            Locale locale = new Locale("nl"); 
            Locale.setDefault(locale);
            Configuration config = new Configuration();
            config.locale = locale;
            getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
            Toast.makeText(this, "Locale in Nederlands !", Toast.LENGTH_LONG).show();
            break;

        case 202:

            Locale locale2 = new Locale("fr"); 
            Locale.setDefault(locale2);
            Configuration config2 = new Configuration();
            config2.locale = locale2;
            getBaseContext().getResources().updateConfiguration(config2, getBaseContext().getResources().getDisplayMetrics());

            Toast.makeText(this, "Locale en Français !", Toast.LENGTH_LONG).show();
            break;  

        }
        return super.onOptionsItemSelected(item);
    }
}

E AQUI ESTÁ O MANIFESTO:

<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.cousinHub.ChangeLocale"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".Main"
                      android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
        <uses-sdk android:minSdkVersion="3" /> 
    </manifest>

Foi o que eu achei:

<uses-sdk android:minSdkVersion="5" />

=> Funciona muito bem ...

<uses-sdk android:minSdkVersion="3" />

=> Menu diminui toda vez que você altera a localidade !!!

como desejo manter meu aplicativo acessível para usuários na versão 1.5, o que devo fazer?

Hubert
fonte
O que você quer dizer com "encolhe"?
CommonsWare 15/02/10
fica cada vez menor a cada vez. talvez eu devesse usar algo além de getBaseContext?
Hubert
Talvez não sejam essas linhas que estão criando o problema, pois quando eu faço um aplicativo muito básico fazendo exatamente a alteração no Locale, não tenho o mesmo "encolhimento do menu". Eu pensei que talvez fosse porque minha Atividade era de fato uma TabActivity, mas mesmo com isso, não consigo recriar o problema. Vou ter que investigar melhor qual é a causa exata desse bug ... não procure mais. Vou postar a resposta aqui quando a encontrar. Abraços, H.
Hubert
Editei meu primeiro post, dando um exemplo de como criar o problema. E notei que, quando altero a linha <uses-sdk android: minSdkVersion = "5" /> para "3" ... de fato, o problema aparece!
Hubert
1
Você pode usar a seguinte biblioteca, que fornece a lista de idiomas, a preferência pela tela de configurações e substitui o idioma no seu aplicativo: github.com/delight-im/Android-Languages
caw

Respostas:

151

Por meio da pergunta original, não é exatamente sobre o código do idioma todas as outras questões relacionadas ao código do idioma se referem a este. É por isso que eu queria esclarecer a questão aqui. Usei essa pergunta como ponto de partida para meu próprio código de mudança de localidade e descobri que o método não está exatamente correto. Funciona, mas apenas até qualquer alteração na configuração (por exemplo, rotação da tela) e somente nessa Atividade em particular. Jogando com um código por um tempo, acabei com a seguinte abordagem:

Estendi android.app.Application e adicionei o seguinte código:

public class MyApplication extends Application
{
    private Locale locale = null;

    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
        super.onConfigurationChanged(newConfig);
        if (locale != null)
        {
            newConfig.locale = locale;
            Locale.setDefault(locale);
            getBaseContext().getResources().updateConfiguration(newConfig, getBaseContext().getResources().getDisplayMetrics());
        }
    }

    @Override
    public void onCreate()
    {
        super.onCreate();

        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);

        Configuration config = getBaseContext().getResources().getConfiguration();

        String lang = settings.getString(getString(R.string.pref_locale), "");
        if (! "".equals(lang) && ! config.locale.getLanguage().equals(lang))
        {
            locale = new Locale(lang);
            Locale.setDefault(locale);
            config.locale = locale;
            getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
        }
    }
}

Esse código garante que todas as atividades tenham um conjunto de códigos de idioma personalizado e não serão redefinidas na rotação e em outros eventos.

Também passei muito tempo tentando fazer com que a alteração de preferência fosse aplicada imediatamente, mas não obteve êxito: o idioma foi alterado corretamente na Reinicialização de atividade, mas os formatos de número e outras propriedades de localidade não foram aplicados até a reinicialização completa do aplicativo.

Muda para AndroidManifest.xml

Não se esqueça de adicionar android:configChanges="layoutDirection|locale"a todas as atividades no AndroidManifest, bem como android:name=".MyApplication"ao <application>elemento.

Andrey Novikov
fonte
1
Sua atividade preferencial está sendo chamada de sua atividade principal? você pode colocar isso no resumo ... @Override void protected onResume () {if (! (PreferenceManager.getDefaultSharedPreferences (getApplicationContext ()). getString ("listLanguage", "en") .equals (langPreference))) { refresh (); super.onResume (); } atualização de void privada () {finish (); Intenção myIntent = new Intent (Main.this, Main.class); startActivity (myIntent); }
trgraglia
1
Na verdade, é isso que eu preciso, costumava chamar updateConfiguration () em MainActivity, mas os menus não são atualizados quando o aplicativo é finalizado e, em seguida, iniciado. Sua solução está certa. Se você deseja atualizar as strings imediatamente, acho que ainda deve redefinir as strings manualmente. (por exemplo, etiqueta do botão reset ou menu recriar).
Emeraldhieu 11/11
Isso interfere no código do idioma do sistema Android?
22612 Kostadin
17
Observe que você nunca deve modificar um objeto de configuração fornecido pelo Android ( confige newConfig). Você deve fazer uma cópia config = new Configuration(config);primeiro. Passei apenas uma hora depurando esse código antes de descobrir o problema (estava fazendo com que a atividade piscasse sem parar).
Imgx64>
1
Essa técnica apresenta falhas no Android 7 ou superior ao iniciar o aplicativo a partir de uma partida a frio, com o tamanho da fonte do sistema definido como grande. O Google mudou a maneira como updateConfiguration()funciona. Às vezes, sua atividade é exibida em dois idiomas diferentes (especialmente nos Diálogos). Mais informações: stackoverflow.com/questions/39705739/…
Mr-IDE
61

Depois de uma boa noite de sono, encontrei a resposta na Web (uma simples pesquisa no Google na seguinte linha " getBaseContext().getResources().updateConfiguration(mConfig, getBaseContext().getResources().getDisplayMetrics());"), aqui está:

link text => este link também mostra o screenshotsque está acontecendo!

Densidade foi o problema aqui , eu precisava ter isso no AndroidManifest.xml

<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true"
/>

O mais importante é o andróide: anyDensity = "true" .

Não se esqueça de adicionar o seguinte em AndroidManifest.xmltodas as atividades (para Android 4.1 e abaixo):

android:configChanges="locale"

Esta versão é necessária quando você cria a explicação para o Android 4.2 (API nível 17) aqui :

android:configChanges="locale|layoutDirection"
Hubert
fonte
3
Eu tinha o android: anyDensity = "false" devido aos conselhos do Google (Estratégias para aplicativos herdados - ponto 9: developer.android.com/intl/fr/guide/practices/… ). Tenha cuidado, então!
Hubert
2
Os textos nos botões não mudam depois que eu mudo a localidade. Gostaria de saber se existe alguma solução para atualizar todos os widgets que contêm recursos de string. Agora estou redesenhando o texto usando setText (getString (R.string.button_label)) em OnRestart (). (Claro que muda após a reinicialização do aplicativo.)
emeraldhieu
você pode chamar atividade recriaractivity.recreate()
Para Kra
59

No Android M, a melhor solução não funciona. Eu escrevi uma classe auxiliar para corrigir o que você deve chamar da classe Application e de todas as atividades (sugiro criar uma BaseActivity e depois fazer com que todas as atividades sejam herdadas.

Nota : Isso também suporta corretamente a direção do layout RTL.

Classe auxiliar:

public class LocaleUtils {

    private static Locale sLocale;

    public static void setLocale(Locale locale) {
        sLocale = locale;
        if(sLocale != null) {
            Locale.setDefault(sLocale);
        }
    }

    public static void updateConfig(ContextThemeWrapper wrapper) {
        if(sLocale != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            Configuration configuration = new Configuration();
            configuration.setLocale(sLocale);
            wrapper.applyOverrideConfiguration(configuration);
        }
    }

    public static void updateConfig(Application app, Configuration configuration) {
        if (sLocale != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            //Wrapping the configuration to avoid Activity endless loop
            Configuration config = new Configuration(configuration);
            // We must use the now-deprecated config.locale and res.updateConfiguration here,
            // because the replacements aren't available till API level 24 and 17 respectively.
            config.locale = sLocale;
            Resources res = app.getBaseContext().getResources();
            res.updateConfiguration(config, res.getDisplayMetrics());
        }
    }
}

Inscrição:

public class App extends Application {
    public void onCreate(){
        super.onCreate();

        LocaleUtils.setLocale(new Locale("iw"));
        LocaleUtils.updateConfig(this, getBaseContext().getResources().getConfiguration());
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        LocaleUtils.updateConfig(this, newConfig);
    }
}

BaseActivity:

public class BaseActivity extends Activity {
    public BaseActivity() {
        LocaleUtils.updateConfig(this);
    }
}
Roberto B.
fonte
11
Essa resposta deve ser aceita. Certifique-se de que updateConfig deve ser chamado do construtor e não do onCreate da classe de atividade, eu estava cometendo esse erro.
Hitesh Sahu
1
depende de como você implementou isso em seu programa. Se você substituiu onConfigurationChanged (), não precisa chamar .updateConfig () e é por isso que você está recebendo o erro, porque ele está sendo chamado duas vezes. se você está dizendo que apenas chamar .setLocale () não está funcionando, isso ocorre porque você precisa atualizar sua interface do usuário, chamando recreate () na atividade ou seu próprio método em atividades / fragmentos (eu prefiro o primeiro, mesmo que não é suave, mas você nunca vai se preocupar com problemas)
RJFares
3
// import android.support.v7.view.ContextThemeWrapper; import android.view.ContextThemeWrapper; // espaço para nome correto
Mehdi Rostami
1
O BaseActivity()construtor não deveria ligar super()?
precisa saber é
1
Essa solução inclui implicitamente a colocação <application android:name=".App">do manifesto? Que tal adicionar android:configChanges="layoutDirection|locale"a todas as atividades no manifesto?
precisa saber é
10

Isto é para o meu comentário sobre a resposta de Andrey, mas não posso incluir código nos comentários.

Sua atividade preferencial está sendo chamada de sua atividade principal? você pode colocar isso no resumo ...

@Override
protected void onResume() {
    if (!(PreferenceManager.getDefaultSharedPreferences(
            getApplicationContext()).getString("listLanguage", "en")
            .equals(langPreference))) {
        refresh();
    }
    super.onResume();
}

private void refresh() {
    finish();
    Intent myIntent = new Intent(Main.this, Main.class);
    startActivity(myIntent);
}
trgraglia
fonte
2
Quer dizer, se eu quiser alterar o idioma em tempo de execução, tenho que terminar essa atividade e iniciar essa atividade novamente para obter alterações? existe alguma outra maneira de fazer a alteração, pois não é bom sempre que a atividade é concluída e iniciada novamente.
Shreyash Mahajan
1
melhor fazeractivity.recreate()
To Kra
@ Trgradlia, eu quero usar o código acima. Por favor, diga-me, o que é langPreference? Acho que estamos comparando os idiomas recentemente alterados e anteriores.
Mahen 23/05
1
Activity tem recreate()método, então use recreate()no lugar de refresh()no seu código. Isto irá salvar sua linha 3 do código no refresh()método
Inzimam Tariq TI
1
@InzimamTariqIT, Obrigado pela dica ... A resposta tem quase 7 anos, então deixarei como está e as pessoas podem encontrar sua melhoria aqui nos comentários.
trgraglia
6

Eu não poderia usar o android: anyDensity = "true" porque os objetos no meu jogo seriam posicionados completamente diferentes ... parece que isso também funciona:

// criando localidade
Localidade local2: novo localidade (local); 
Locale.setDefault (locale2);
Configuração config2 = new Configuration ();
config2.locale = locale2;

// atualizando localidade
mContext.getResources (). updateConfiguration (config2, nulo);
nikib3ro
fonte
Esta solução é melhor para mim apenas usou essacode Locale.setDefault(mLocale); Configuration config = getBaseContext().getResources().getConfiguration(); if (!config.locale.equals(mLocale)) { config.locale = mLocale; getBaseContext().getResources().updateConfiguration(config, null); }
Huds0nHawk
1

Se você deseja efetuar as opções de menu para alterar a localidade imediatamente, faça o seguinte.

//onCreate method calls only once when menu is called first time.
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    //1.Here you can add your  locale settings . 
    //2.Your menu declaration.
}
//This method is called when your menu is opend to again....
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
    menu.clear();
    onCreateOptionsMenu(menu);
    return super.onMenuOpened(featureId, menu);
}
Ramesh Akula
fonte
Mas essa solução não limpa o menu em todos os menus abertos? Suponho que onMenuOpeneddeve ser chamado apenas uma vez, após a alteração da localidade.
trante