user316117
user316117

Reputation: 8271

Using Locale to force Android to use a specific strings.xml file for a non-supported language

...and if so, how?

We make a dedicated Android device for use in an industrial environment. It's basically a tablet, but with only one app running. The user is not expected to access any other features of the device and even the system settings, like WiFi and Time settings are performed through our app instead of through the Android Settings widget. So basically every button and message they see uses our strings.xml file.

Currently all of our customers are satisfied to use the default US-English settings but we will soon have some customers who want local languages and have supplied us with translation files. Currently one of them is Romanian, which is not a language with any native support on this device (a Samsung Galaxy tab 4); another is Czech.

So we want to add strings.xml files in appropriate res folders, for the non-English languages and a dropdown in our app to select which language we're using. Programmatically we think we can use Locale to set which strings.xml file it uses, so for example, if Romanian has been selected from the dropdown we would use Locale to set the tablet into Romanian so all of our app's UI will use the Romanian strings.xml file.

Our settings, including the new dropdown, are inaccessible to customers - they're set at the customer site by a field-service engineer.

Questions:

  1. Will this work? I.e., can we control which strings.xml file it uses via Locale, even if the device has no native support for that language?
  2. Since Romanian is not a natively-supported language with this device we assume that system messages will still come up in English. Is this true? (it's not a problem if it does - system messages are rare with our app and the users of our products are trained to contact support if that happens. I just want to make sure that if we set the Locale to Romanian, or Czech or some other language without native support it won't crash the tablet if it does try to issue a system message).

Upvotes: 15

Views: 6948

Answers (4)

Onik
Onik

Reputation: 19949

Will this work? I.e., can we control which strings.xml file it uses via Locale, even if the device has no native support for that language?

Yes, you can, by updating Locale within Configuration (see an example below). If you try to use the locale for which there are no corresponding resources (either within your app or system), the default string resources (res/values/strings.xml) of your app will be utilized.

Since Romanian is not a natively-supported language with this device we assume that system messages will still come up in English. Is this true?

It is true, if English is the current system locale.

I just want to make sure that if we set the Locale to Romanian, or Czech or some other language without native support it won't crash the tablet if it does try to issue a system message.

Your app won't crash. Locale changes made within an app effect locale resources of the app, not system one's.

An example to answer "if so, how?" The method can be used to test locale changes while an Activity is running*.

public static void changeLocale(Context context, String locale) {
    Resources res = context.getResources();
    Configuration conf = res.getConfiguration();
    conf.locale = new Locale(locale);
    res.updateConfiguration(conf, res.getDisplayMetrics());
}

* You might want to call recreate() to see string resource changes "on the fly".

Upvotes: 10

Gabe Sechan
Gabe Sechan

Reputation: 93559

The best way to do this is to actually set the device locale. The code to do that is

Class<?> activityManagerNative = Class.forName("android.app.ActivityManagerNative");
Object am = activityManagerNative.getMethod("getDefault").invoke(activityManagerNative);
Object config = am.getClass().getMethod("getConfiguration").invoke(am);
config.getClass().getDeclaredField("locale").set(config, item.getLocale());
config.getClass().getDeclaredField("userSetLocale").setBoolean(config, true);
am.getClass().getMethod("updateConfiguration", android.content.res.Configuration.class).invoke(am, config);

ActivityManagerNative.java

package android.app;

public abstract class ActivityManagerNative implements IActivityManager {

    public static IActivityManager getDefault(){
        return null;
    }

}

IActivityManager

package android.app;

import android.content.res.Configuration;
import android.os.RemoteException;

public interface IActivityManager {
    public abstract Configuration getConfiguration () throws RemoteException;
    public abstract void updateConfiguration (Configuration configuration) throws RemoteException;
}

This way you'll set the device locale, and let everything change through the normal pathways. You'll need the android:name="android.permission.CHANGE_CONFIGURATION" permission in your manifest. This is a secure permission, but installing yourself as a system app shouldn't be a problem for you.

Upvotes: 1

s.d
s.d

Reputation: 29436

Yes you can definitely switch locale of single app. For that you have to extend every activity from a base class like following:

public abstract class MyLangActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        Locale locale = // get the locale to use...
        Configuration conf = getResources().getConfiguration();
        if (Build.VERSION.SDK_INT >= 17) {
            conf.setLocale(locale);
        } else {
            conf.locale = locale;
        }

        DisplayMetrics metrics = getResources().getDisplayMetrics();
        getResources().updateConfiguration(conf, metrics);
        super.onCreate(savedInstanceState);
    }
}

Now about the issue of device not supporting the language applied. The device font being used might not have support for characters in the custom language file. You might use your own fonts files to support that.

Upvotes: 5

Qamar
Qamar

Reputation: 5135

Override getResources in Application and BaseActivity

@Override
    public Resources getResources() {
        if(mRes == null)
        mRes = new PowerfulResources(getAssets(),new DisplayMetrics(), null);
        return mRes;
    }

    public static class PowerfulResources extends Resources{

        /**
         * Create a new Resources object on top of an existing set of assets in an
         * AssetManager.
         *
         * @param assets  Previously created AssetManager.
         * @param metrics Current display metrics to consider when
         *                selecting/computing resource values.
         * @param config  Desired device configuration to consider when
         */
        public PowerfulResources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
            super(assets, metrics, config);
        }

        @NonNull
        @Override
        public String getString(int id) throws NotFoundException {
            //do your stuff here
            return super.getString(id);
        }
    }

also read this

Upvotes: -1

Related Questions