EDIT
I was working with the Indonesian language, it seems this is an Java "Issue" which actually requires you to use the values-in folder.
/EDIT
I ran into a "bug" today while working on multiple languages.
It seems that you cannot force the use of a locale if it's not installed on a phone (Using this mostly for translations).
In our app we want to force the use of a certain language sometimes people who use this are moderators who work in multiple languages, so when adding the indonesian language I noticed the locale can't be set unless you have it installed so my values-id is not being used.
I'm currently using this code to fetch the translation (and have a fallback to english if the translation would be empty however this doesn't occur anymore).
Is there any way I could force the use of the values-id file (or other languages in that case).
try {//try to fetch it, if we're good boys we added it and it'll be no problem
Resources res = context.getResources();
String str = res.getString(res.getIdentifier(text, "string", context.getApplicationInfo().packageName));
if (str.equals("") && GlobalVars.globalCurrentLanguage != "en"){
Configuration conf = new Configuration();
conf.setToDefaults(); // That will set conf.locale to null (current locale)
// We don't want current locale, so modify it
conf.locale = new Locale(""); // Or conf.locale = Locale.ROOT for API 9+
// or conf.setLocale(Locale.ROOT) for API 14+
// Since we need only strings, we can safely set metrics to null
Resources rsrcDflt = new Resources(context.getAssets(), null, conf);
str = rsrcDflt.getString(res.getIdentifier(text, "string", context.getApplicationInfo().packageName)); // Got it at last!
conf.locale = new Locale(GlobalVars.globalCurrentLanguage);
rsrcDflt = new Resources(context.getAssets(), null, conf);
rsrcDflt.getString(res.getIdentifier(text, "string", context.getApplicationInfo().packageName)); // Got it at last!
}
return str;
} catch (Exception e) {
//oh dearie we forgot the translation lets return the given string instead
return text;
}
As seen in the question above, I found out it was due to Java requiring the values-in file instead of values-id for indonesian.
The same goes for yiddish and hebrew, if you ever seem to have the same issue that I had.
Related
I've a problem with AAB when I need to change the app locale from within the app itself(i.e. have the language change setting inside the app), the issue is that the AAB gives me only my device languages resources, for example:
my device has English and French languages installed in it, so AAb gives me only the resources for English and French,
but from within the app itself there is a choice to switch the language between English, French, and Indonesian,
in that case, when changing the language to English or French everything is working perfectly, but when changing it to Indonesian, the app simply enters a crash loop as it keep looking for Indonesian language but it can't find.
The problem here is that even if I restarted the app, it enters the crash loop again as the app is still looking for the missing language resources, and here the only solution is to clear cash or reinstall which are the solutions that the normal user won't go through.
Just to mention it, this is how I change the locale through the app:
// get resources
Resources res = context.getResources();
// create the corresponding locale
Locale locale = new Locale(language); // for example "en"
// Change locale settings in the app.
android.content.res.Configuration conf = res.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
conf.setLocale(locale);
conf.setLayoutDirection(locale);
} else {
conf.locale = locale;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context.getApplicationContext().createConfigurationContext(conf);
}
res.updateConfiguration(conf, null);
P.S. The app is working perfectly when build it as APK.
Edit:
The PlayCore API now supports downloading the strings for another language on-demand:
https://developer.android.com/guide/playcore/feature-delivery/on-demand#lang_resources
Alternative solution (discouraged):
You can disable the splitting by language by adding the following configuration in your build.gradle
android {
bundle {
language {
// Specifies that the app bundle should not support
// configuration APKs for language resources. These
// resources are instead packaged with each base and
// dynamic feature APK.
enableSplit = false
}
}
}
This latter solution will increase the size of the app.
This is not possible with app bundles: Google Play only downloads resources when the device's selected languages change.
You'll have to use APKs if you want to have an in app language picker.
Details of downloading the language on demand can be found here
https://android-developers.googleblog.com/2019/03/the-latest-android-app-bundle-updates.html
In your app’s build.gradle file:
dependencies {
// This dependency is downloaded from the Google’s Maven repository.
// So, make sure you also include that repository in your project's build.gradle file.
implementation 'com.google.android.play:core:1.10.0'
// For Kotlin users also add the Kotlin extensions library for Play Core:
implementation 'com.google.android.play:core-ktx:1.8.1'
...
}
Get a list of installed languages
val splitInstallManager = SplitInstallManagerFactory.create(context)
val langs: Set<String> = splitInstallManager.installedLanguages
Requesting additional languages
val installRequestBuilder = SplitInstallRequest.newBuilder()
installRequestBuilder.addLanguage(Locale.forLanguageTag("pl"))
splitInstallManager.startInstall(installRequestBuilder.build())
Check above link for full details
After many hours I was finally able to use the on-demand language with the new PlayCore API.
Step 1.) As the user changes the language, you need to first check whether the language is already available, if not then download the language
private void changeLocale(final String languageSelected){
SplitInstallManager splitInstallManager = SplitInstallManagerFactory.create(PlayAgainstComputer.this);
final Set<String> installedLangs = splitInstallManager.getInstalledLanguages();
if(installedLangs.contains(languageSelected)){ // checking if lang already available
Toast.makeText(PlayAgainstComputer.this,"Done! The language settings will take effect, once you restart the app!").show();
}
else{
SplitInstallRequest request =
SplitInstallRequest.newBuilder()
.addLanguage(Locale.forLanguageTag(languageSelected))
.build();
splitInstallManager.startInstall(request);
splitInstallManager.registerListener(new SplitInstallStateUpdatedListener() {
#Override
public void onStateUpdate(#NonNull SplitInstallSessionState splitInstallSessionState) {
if(splitInstallSessionState.status() == SplitInstallSessionStatus.INSTALLED){
Toast.makeText(PlayAgainstComputer.this,"Download complete! The language settings will take effect, once you restart the app!").show();
}
}
});
}}
Step2.) The downloaded languages must be installed when the user starts the app. which is done in the attchBaseContext() method
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
SplitCompat.install(this); // It will install all the downloaded langauges into the app
}
Step 3.) You need to tell the Activity to use the chosen language. Following code should be placed before setContentView(R.layout.layout); of that activity
String selectedLanguage = getFromPrefernceOrWhereEverYouSavedIt(); // should be 2 letters. like "de", "es"
Locale locale = new Locale(selectedLanguage);
Locale.setDefault(locale);
Resources resources = getResources();
Configuration config = new Configuration(resources.getConfiguration());
config.locale = locale;
resources.updateConfiguration(config,
getBaseContext().getResources().getDisplayMetrics());
Done!
Please Note
When a user (who chose a non-default/downloaded language) updates the app, that language needs to be downloaded again into the app, so make sure you handle that in your code.
when I used activity.recreate(); after the download finished (to automatically refresh the app for new language) I faced some problems, that is why I used Toast to ask the user to manually restart the app. but you can try other methods
I also noticed some other inconsistencies (even sometimes faced memory leak because of SplitCompat.install(this);) with this method, so make sure you test and optimize it according to your code.
I am trying to change my device's language in my app. I have this code:
Locale locale = new Locale("en_US");
Locale.setDefault(locale);
Configuration config = new Configuration();
config.locale = locale;
getApplicationContext().getResources().updateConfiguration(config, null);
Log.i("some","Well, I tried!");
But this code does not change state of my device, and in LogCat I can see "Well, I tried" message. What are the possible reasons of such strange behaviour?
Edit this line
getApplicationContext().getResources().updateConfiguration(config, null);
like this:
context.getApplicationContext().getResources().updateConfiguration(config, null);
If you came here for language problems for builds after Summer 2021, it may have nothing to do with your code. We had the same issue and the problem was the new bundle (.aab) requirement (required since Summer 2021).
With app bundles, devices download only the code and resources they require to run your app. So, for language resources, a user’s device downloads only your app’s language resources that match the one or more languages currently selected in the device’s settings.
Read further
Basically, the language file is not downloaded if the device does not support that language. There are 2 ways to solve it:
Option 1 (Easiest): Just disable the bundle optimization
Add this in your build.gradle file:
android {
bundle {
language {
enableSplit = false
}
}
// ...
// Other configuration
}
Option 2: Download the language file on demand
Use the method described here.
How to debug
Before applying these methods,
Delete the app
Add the new language in the device settings
Download the app again
If the app supports the new language then the problem is definitely the bundle optimization and the above mentioned solutions will work.
To change the applications locale manually.
You will need to set the locale using the code below.
Locale locale = new Locale("AR"); // AR here is for arabic
Locale.setDefault(locale);
Configuration config = new Configuration();
config.locale = locale;
getBaseContext().getResources().updateConfiguration(config,
getBaseContext().getResources().getDisplayMetrics());
Make sure it is set in the activity "onCreate" method before calling the "setContentView" method.
Also see that the resource files are specified for the language required.
I have some code I want to test. I want to check if a String is properly composed out of various strings that I have in resources. The challenge here is to deal with multiple translations in my resources. I know that locale can be an issue when testing a desktop application and that it is recommended that you create locale-independent tests.
I've found that you can set the locale programatically, but it was not recommended (see Change language programmatically in Android). While this question is aimed at changing locale at runtime when running an app normally, I was wondering if there was a better solution to my problem.
If it's just for testing, then you can change the locale programmatically without any issues. It will change the configuration of your app and you will be able to test your code with the new locale. It has the same effect as if a user has changed it. If you want to automate your tests, you can write a script that changes locale using adb shell as described here, and launch your tests afterwards.
Here is an example of testing translations of word "Cancel" for English, German and Spanish locales:
public class ResourcesTestCase extends AndroidTestCase {
private void setLocale(String language, String country) {
Locale locale = new Locale(language, country);
// here we update locale for date formatters
Locale.setDefault(locale);
// here we update locale for app resources
Resources res = getContext().getResources();
Configuration config = res.getConfiguration();
config.locale = locale;
res.updateConfiguration(config, res.getDisplayMetrics());
}
public void testEnglishLocale() {
setLocale("en", "EN");
String cancelString = getContext().getString(R.string.cancel);
assertEquals("Cancel", cancelString);
}
public void testGermanLocale() {
setLocale("de", "DE");
String cancelString = getContext().getString(R.string.cancel);
assertEquals("Abbrechen", cancelString);
}
public void testSpanishLocale() {
setLocale("es", "ES");
String cancelString = getContext().getString(R.string.cancel);
assertEquals("Cancelar", cancelString);
}
}
Here are the execution results in Eclipse:
Android O update.
When running in Android O method Locale.setDefault(Category.DISPLAY, locale) shall be used (see behaviour changes for more detail).
The current accepted answer didn't help me.
But #Dennis's comment helped to solve the Problem for me.
Use Robolectric and override the locale by specifying a resource qualifier.
Add for example #Config(qualifiers="de-port") for the German language.
#Test
#Config(qualifiers = "de-port")
fun testGetLocaleGerman(){ ... }
Robolectrics Documentation
I have several language files in Android application: value/strings.xml, value-en/strings.xml, ...etc
It is possible to load the content of this files in some arrays or something. For example, I would like to load default text strings and english strings in 2 different arrays at run time.
Thanks
Alin
Create a method like this:
Resources getResourcesByLocale( Context context, String localeName ) {
Resource res = context.getResources();
Configuration conf = new Configuration(res.getConfiguration());
conf.locale = new Locale(localeName);
return new Resources(res.getAssets(), res.getDisplayMetrics(), conf);
}
Than you can get resources for any locale you've defined, for example:
Resources res_en = getResourcesByLocale(context, "en");
Resources res_de = getResourcesByLocale(context, "de");
Resources res_fr = getResourcesByLocale(context, "fr");
String some_name_en = res_en.getString(R.string.some_name);
String some_name_fr = res_fr.getString(R.string.some_name);
// etc...
Moreover, you do not need to take care about exceptions if you did not define string for some locale, because anyway default (from res/values/*) will be loaded instead.
Actually the situation is like that. Imagine I have this scenario: some chinese open the application. He has the mobile phone set with ch locale. The application default is xx as language, meaning I have 2 language files values/strings.xml (spanish for eg as default) and another language values-en/strings.xml for english. The default will make no sense for him, so english will be the most appropiate for his understanding, even if he does not understand it very good. So at the app start I open language settings (android language settings), where any selection will set the app in spanish unless he select english. I am forcing him to change the phone locale in english basically, just to use my app. Overall the concept of android is wrong, because i need to be able to see an application in any language I want without changing device language.
What I have done: - I created in values folder one more string_xx.xml file. Now, for a translation string name = "txtTranslation" I have in string_xx file "en_txtTranslation" key. R.java loads them all and in my app, based on a global var selectedLanguage = xx, I attach the write string using this code:
public String translate(String text)
{
String appLanguage = UtilsCentral.getUserLanguage(getApplicationContext());
if (appLanguage != "")
{
return getString(getResources().getIdentifier(appLanguage + "_" + text, "string", this.getPackageName()));
}
else
{
return getString(getResources().getIdentifier(text, "string", this.getPackageName()));
}
}
Indeed, at activity on create i need to set all views with .text = tarnslate("txtTranslation")
Note: UtilsCentral.getUserLanguage(getApplicationContext()) returns app language (user selection)
Conclusion, there is more unuseful work, but lets me do what i need, and what i believe is normal.
I've found using the android.text.format.DateUtils relative APIs that return values like "yesterday" or "2 hours ago" very nice - but my app does not support every language Android does. So, I default to English, but for every language I don't support, the relative string shows in the device's setting.
For example, like:
Last attempt: hace 11 minutos.
I'd like to make the API call default to English for any languages I don't support. However, I don't see anywhere to set the Locale for the API call - I'm hoping I'm just missing it somewhere.
Is there a way to set the Locale just for the API call, ignoring the device setting?
This is working for me up to Android 7
void forceLocale(Locale locale) {
Configuration conf = getBaseContext().getResources().getConfiguration();
updateConfiguration(conf, locale);
getBaseContext().getResources().updateConfiguration(conf, getResources().getDisplayMetrics());
Configuration systemConf = Resources.getSystem().getConfiguration();
updateConfiguration(systemConf, locale);
Resources.getSystem().updateConfiguration(conf, getResources().getDisplayMetrics());
Locale.setDefault(locale);
}
void updateConfiguration(Configuration conf, Locale locale) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
conf.setLocale(locale);
}else {
//noinspection deprecation
conf.locale = locale;
}
}
According to the source code of the DateUtils class it uses both Resource.getSystem() and Locale.getDefault() method for formatting date and time. You can change the default Locale using Locale.setDefault() method but I don't think it's possible to change the return value of the Resource.getSystem() method. You can try to change the default locale to Locale.US but it seems to me that results will be even worse in this case.