Android plurals not working - android

I start to meet with android plurals and i'm stack in usability of this feature. I declare plurals.xml and try to read them from my code, but i getting incorrect results.
Plurals :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="numbers">
<item quantity="zero">No comments</item>
<item quantity="one">%1$d comment.</item>
<item quantity="two">%1$d comments.</item>
<item quantity="few">%1$d comments.</item>
<item quantity="many">%1$d comments.</item>
<item quantity="other">%1$d comments.</item>
</plurals>
<plurals name="numberOfSongsAvailable">
<item quantity="zero">No song found.</item>
<item quantity="one">One song found.</item>
<item quantity="two">Two song found.</item>
<item quantity="other">Other songs found.</item>
</plurals>
</resources>
Activity :
public class AndroidPlurals extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.plurals);
((ListView) findViewById(R.id.listView)).setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, getPluralsList(10)));
Toast.makeText(this, getClass().getSimpleName(), Toast.LENGTH_LONG).show();
}
private String[] getPluralsList(int k) {
String[] array = new String[k];
for (int i = 0; i < array.length; i++) {
String quantityString = (String) this.getResources().getQuantityText(R.plurals.numberOfSongsAvailable, i);
array[i] = quantityString;
android.util.Log.i("Plurals", "i = " + i + ", quantityString = " + quantityString);
}
return array;
}
}
OutPut :
I/Plurals (17689): i = 0, quantityString = Other songs found.
I/Plurals (17689): i = 1, quantityString = One song found.
I/Plurals (17689): i = 2, quantityString = Other songs found.
I/Plurals (17689): i = 3, quantityString = Other songs found.
I/Plurals (17689): i = 4, quantityString = Other songs found.
I/Plurals (17689): i = 5, quantityString = Other songs found.
I/Plurals (17689): i = 6, quantityString = Other songs found.
I/Plurals (17689): i = 7, quantityString = Other songs found.
I/Plurals (17689): i = 8, quantityString = Other songs found.
I/Plurals (17689): i = 9, quantityString = Other songs found.
Why i have not correct result for 0 quantity like a No song found. text???
Note : tested on Android 5.0 Lolipop

The short answer is that on Android, plurals are meant for grammatical distinctions.
Quote from the docs (my highlight):
The selection of which string to use is made solely based on grammatical necessity. In English, a string for zero will be ignored even if the quantity is 0, because 0 isn't grammatically different from 2, or any other number except 1 ("zero books", "one book", "two books", and so on). Conversely, in Korean only the other string will ever be used.
And:
Don't be misled either by the fact that, say, two sounds like it could only apply to the quantity 2: a language may require that 2, 12, 102 (and so on) are all treated like one another but differently to other quantities. Rely on your translator to know what distinctions their language actually insists upon.
More details and alternatives here: Android plurals treatment of "zero"

On the android device where input language is English the case with 0 or 2 will be handled the same. So in russian language (and very possible in others) if you app use russian language and you use it on the device with English input, you always will get the value for zero case from many case. For example
2 Билетов а не 2 Билета

Why i have not correct result for 0 quantity like a No song found. text?
Because English has only a special case for quantity one. The quantity other is picked up for all other cases.
Please read the documentation.

From developer.android.com:
Don't be misled either by the fact that, say, two sounds like it could only apply to the quantity 2: a language may require that 2, 12, 102 (and so on) are all treated like one another but differently to other quantities. Rely on your translator to know what distinctions their language actually insists upon.
You'd only need to specify one and other here ("song" vs. "songs").

Related

Multiple values String format Android

I would to display text like this sample : "0/2 documents"
I'm trying to do this with :
<plurals name="documents_get">
<item quantity="one">%1d/%2d doucment</item>
<item quantity="other">%1d/%2d documents</item>
</plurals>
resources.getQuantityString(
R.plurals.documents_get,
docCount,
documents.filter {
it.retrieved_at != null
}.count(),
docCount)
My problem is the result is : 0/ 2 documents instead "0/2 documents". Space after the '/' is the problem.
Do you know a solution for this ?
Thanks in Advance
The problem is the format you are using (%2d). So change your plurals to this:
<plurals name="documents_get">
<item quantity="one">%d/%d doucment</item>
<item quantity="other">%d/%d documents</item>
</plurals>
Example with using String.format
String s1 = String.format("%d/%d document", 0, 1); // s1: "0/1 document"
String s2 = String.format("%d/%2d document", 0, 1); // s2: "0/ 1 document"
Edit
As #Kingfisher Phuoc noted, it should be %1$d/%2$d instead of %d. The $ sign allows you to specify the index of the string (the position where the string should be printed, called "explicit argument indices" or "positional arguments". You can read more here).
For example:
String s1 = String.format("%1$d/%2$d", 1, 2); // s1: "1/2"
String s2 = String.format("%2$d/%1$d", 1, 2); // s2: "2/1"
Remove (%2d) and replace it with (%d) or you can use string format
String resource = String.format("%d/%d document", 0, 1); // s1: "0/1 document"
You can define your string inside string.xml like this
<resources>
<string name="string">0"/"2 documents</string>
</resources>
and fetch it via getResources.getString method.
Note : Special Characters are enclosed inside double quotes.

Android's XMLPullParser getAttributeValue is removing leading zeros from string

I have an XML document that contains strings with leading zeros. When I'm iterating the XML file with XmlResourceParser I have noticed that strings with leading zeros are modified removing the leading zeros when calling getAttributeValue. This functionality worked in the past and I have only noticed after upgrading to Android Studio 3.x. Is there something special I need to do in order for "getAttributeValue" to preserve the leading zeros?
Here is a test XML file that I'm using:
<?xml version="1.0" encoding="UTF-8"?>
<FictionalSpies>
<Property Country="Great Britain" Agency="MI6">
<Item FullName="James Bond" AgentCode="007" />
<Item FullName="John Wolfgramm" AgentCode="0010" />
<Item FullName="Sam Johnston" AgentCode="0012" />
</Property>
<Property Country="United States" Agency="CONTROL">
<Item FullName="Maxwell Smart" AgentCode="86" />
<Item FullName="Unknown" AgentCode="99" />
<Item FullName="The Chief" AgentCode="Q" />
</Property>
<Property Country="United States" Agency="MiB">
<Item FullName="James Darrell Edwards III" AgentCode="J" />
<Item FullName="Kevin Brown" AgentCode="K" />
<Item FullName="Derrick Cunningham" AgentCode="D" />
</Property>
</FictionalSpies>
Here is the log print out for each 'spy' in the list. As you can see the first three have lost the "00" in their AgentCode. For example, James Bond should have agent code "007" and not "7".
D/XMLTest: Great Britain MI6 James Bond 7
D/XMLTest: Great Britain MI6 John Wolfgramm 10
D/XMLTest: Great Britain MI6 Sam Johnston 12
D/XMLTest: United States CONTROL Maxwell Smart 86
D/XMLTest: United States CONTROL Unknown 99
D/XMLTest: United States CONTROL The Chief Q
D/XMLTest: United States MiB James Darrell Edwards III J
D/XMLTest: United States MiB Kevin Brown K
D/XMLTest: United States MiB Derrick Cunningham D
Here is the code that is hooked up to a button press on a form and iterates the XML producing the previous log messages:
public void buttonOnClick(View v)
{
int eventType = -1;
String name;
String country = null;
String agency = null;
String fullName = null;
String agentCode = null;
try
{
XmlResourceParser xmlRP = getResources().getXml(R.xml.test);
while (eventType != XmlResourceParser.END_DOCUMENT)
{
if (eventType == XmlResourceParser.START_TAG)
{
name = xmlRP.getName();
if (name.contentEquals("Property"))
{
country = xmlRP.getAttributeValue(null, "Country");
agency = xmlRP.getAttributeValue(null, "Agency");
} else if (name.contentEquals("Item"))
{
fullName = xmlRP.getAttributeValue(null, "FullName");
agentCode = xmlRP.getAttributeValue(null, "AgentCode");
Log.d("XMLTest", country + " " + agency + " " + fullName + " " + agentCode );
}
}
eventType = xmlRP.next();
}
} catch (XmlPullParserException e)
{
} catch (IOException e)
{
}
}
This is a bug in AAPT2. It's been reported and already fixed in Android Studio 3.4 and 3.5 (see https://androidstudio.googleblog.com/2019/01/android-studio-35-canary-2-available.html).
You need to upgrade the Android Gradle Plugin (AGP), too (as seen here: https://stackoverflow.com/a/35272475)
With the following build.gradle entry the AAPT2 behavior can be altered to keep leading zeros:
android {
aaptOptions {
additionalParameters "--keep-raw-values"
}
}
Please note that this might increases your APK size.
XmlResourceParser provide all data types return value, you are getting int Agencycode from parser value. so instead of getAttributeValue() use getAttributeIntValue("namespace", "attribute",0)
just replace this
agentCode = xmlRP.getAttributeValue(null, "AgentCode");
To
agentCode =xmlRP.getAttributeIntValue(null, "AgentCode",0);
Happy coding!!

Get specific JSON feature based on a variable string comparsion

I work with two Bluetooth beacons and two Philips Hue lights. I have a JSON file which contains the beacon UUID for each region and the light IDs:
{
"features":
[
{
"iot_identifier" : "1",
"iot_beacon_uuid" : "E2C56DB5-DFFB-48D2-B060-D0F5A71096E1"
},
{
"iot_identifier" : "2",
"iot_beacon_uuid" : "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0"
}
]
}
I also have a string in my code which displays the current region UUID. It changes when I enter the other region.
This is my code to control the two lights after reading in the JSON:
List<Feature> listFeatures; //Features from JSON file
int count = listFeatures.size();
for(int i=0; i<count; i++){
// Here I have the UUID (not elegant, but does its work)
String regionString = beaconManager.getMonitoredRegions().toString();
String regionUUID = regionString.substring(6, 43).toUpperCase();
// Here I get the JSON properties for each feature
String iotIdentifierString = listFeatures.get(i).getIotIdentifier();
String iotBeaconUuid = listFeatures.get(i).getIotBeaconUuid();
// need this value as double to associate the JSON-ID with the Hue light ID
double iotIdentifierDouble = Double.parseDouble(iotIdentifierString);
if( /* anything */ ){
// get the light with the ID of the current JSON feature
String lightId = hueLightObjects.getFeatures().get(i).getIotIdentifier();
final PHLight light = bridge.getResourceCache().getLights().get(lightId);
// control the lights, turn on/off
}
}
So the "regionUUID" string changes whenever I switch the region. Currently I am always able to control both lights, no matter in which region I am.
What I am looking for is a way to only control the light that has the current beacon UUID in the JSON file.
Something like:
For regionUUID.equals(iot_beacon_uuid), only control the light with the ID in the feature where regionUUID equals the iot_beacon_uuid.
If I change the region, then I only want to control the other light in the other feature where this statement is true again.
Could someone help me with this?
First of all I believe that your question can be more shorter something like ( A Minimal, Complete, and Verifiable example)
So when I am in the region where the minor ID = 1, I only want to select the lamps, where the property "Minor_ID" is also 1
Your code can achieve that in your case it's depend on how you using it after retrieving process.
But you can do that with current way too:
for (int i=0; i<count; i++) {
if(selectedLamp == iotIdentifierDouble ){
//do what you wish
}
}

Getting Locale variant for simplified chinese

How can I get simplified chinese description (简体)? From the available locale Locale.SIMPLIFIED_CHINESE, no method seems to return this description:
getDisplayLanguage() returns the correct language name, but without the variant.
getDisplayName() returns the correct language name and country, but also without the variant.
getDisplayVariant() returns an empty string.
I've also tried to build a new Locale using the different constructors, also to no avail.
new Locale("zh", "CN");
new Locale("zh", "CN", "Hans");
I've checked the Android source code for LocalePicker and I've concluded that it is loaded from the resources (special_locale_codes and special_locale_names).
Any solutions besides having to hardcode/include this string in my resources?
Let me explain my process on how I tackled this. First, I found this block of code in LocalePicker.java
private static String getDisplayName(Locale l, String[] specialLocaleCodes, String[] specialLocaleNames) {
String code = l.toString();
for (int i = 0; i < specialLocaleCodes.length; i++) {
if (specialLocaleCodes[i].equals(code)) {
return specialLocaleNames[i];
}
}
return l.getDisplayName(l);
}
which takes in a Locale as you already know. Then it tries to find the locale code in the specialLocaleCodes string array. The specialLocaleNames you are seeking are obtained from arrays.xml as you've helpfully stated:
<string-array translatable="false" name="special_locale_codes">
<item>ar_EG</item>
<item>zh_CN</item>
<item>zh_TW</item>
</string-array>
and the corresponding languages
<string-array translatable="false" name="special_locale_names">
<item>العربية</item>
<item>中文 (简体)</item>
<item>中文 (繁體)</item>
</string-array>
Notice the code with the simplified Chinese is zh_CN and the last two characters are capitalized.
However,
Locale locale = new Locale("zh_CN");
System.out.println("Locale: " + locale);
prints
Locale: zh_cn
Notice the lower case. So there is no way specialLocaleCodes[i].equals(code) will return true. So then I poked around Locale.java and, long story short, we can bypass that case-changing jumble by doing this (and you MUST keep the 3rd parameter as an empty string for this to work):
Locale locale = new Locale("zh", "CN", "");
System.out.println("Locale: " + locale);
Prints
Locale: zh_CN
With this you should be able to do this:
Locale locale = new Locale("zh", "CN", "");
System.out.println("Name:" + locale.getDisplayName(locale));
Upon further inspection on Kitkat using this (thank you Andrew!)
int specialLocaleNamesId = Resources.getSystem().getIdentifier("special_locale_names", "array", "android");
String[] specialLocaleNames = Resources.getSystem().getStringArray(specialLocaleNamesId);
it was possible to print out
العربية,中文 (简体),中文 (繁體)
as expected. However, something in Kitkat is still preventing the correct string to display. Frustrating.
However, in Lollipop 5.0+ and Java 1.7 this works using forLanguageTag() in Locale.
Locale locale = Locale.forLanguageTag("zh-Hans");
System.out.println("getDisplayName:" + locale.getDisplayName(locale));
System.out.println("getDisplayLanguage:" + locale.getDisplayLanguage(locale));
which prints
getDisplayName:中文 (简体中文)
getDisplayLanguage:中文
You could probably access the android internal resource: com.android.internal.R.array.special_locale_names the same way it's done in LocalePicker:
final Resources resources = context.getResources();
final String[] specialLocaleNames = resources.getStringArray(com.android.internal.R.array.special_locale_names);
But it's probably safer to use your own resource here (avoiding
the use of internals)

android: referring to ressources in custom xml

i have a multilingual android app, where i have put the different translations in the strings.xml in the respective directory.
now i also have a custom xml file, where i would like to reference texts like this:
<?xml version="1.0" encoding="UTF-8"?>
<rooms>
<room title="#+string/localizedtext" />
</rooms>
now when i read the title attribute in my code, i obviously get the unresolved string "#+string/localizedtext" like it is.
is it possible to somehow resolve this link to the localized text automatically?
thanks!
Almost a year later:
public static String getStringResource(Context context, String thingie) {
try {
String[] split = thingie.split("/");
String pack = split[0].replace("#", "");
String name = split[1];
int id = context.getResources().getIdentifier(name, pack, context.getPackageName());
return context.getResources().getString(id);
} catch (Exception e) {
return thingie;
}
}
That'll do it.
This might seem like a broad answer but I believe it'll clarify a lot of things for people who spent hours looking for it (I'm one of them).
The short answer is yes, you can use references in custom XML, not just for strings, but that's the example I use, for ease of understanding.
Considering the context:
res/values/strings.xml
(Default strings, usually en-US for convenience but that's up to the developer)
<resources>
<string name="sample_string">This is a sample string.</string>
</resources>
res/values-fr/strings.xml
(Localized french strings)
<resources>
<string name="sample_string">Ceci est un exemple de chaîne</string>
</resources>
res/xml/test.xml
(Custom XML file)
<!-- #string/sample_string identifies both
the default and french localized strings,
the system settings determine which is used at runtime.
-->
<test>
<sample name="sampleName" text="#string/sample_string"/>
</test>
src/com/example/app/TestXmlParser.java
//Omitted imports for clarity.
public class testXmlParser {
public static final String ns = null;
public int parse(XmlResourceParser parser) throws XmlPullParserException,
IOException{
while(parser.next() != XmlPullParser.END_DOCUMENT){
if(parser.getEventType() == XmlPullParser.START_TAG){
if(parser.getName().equalsIgnoreCase("sample")){
// This is what matters, we're getting a
// resource identifier and returning it.
return parser.getAttributeResourceValue(ns, "text", -1);
}
}
}
return -1;
}
Use String getText(int id) to obtain the string corresponding to id (localized, if available).
Using the example above it would amount to replace :
//Return the resource id
return parser.getAttributeResourceValue(ns, "text", -1);
with :
//Return the localized string corresponding to the id.
int id = parser.getAttributeResourceValue(ns, "text", -1);
return getString(id);
The way you tried is not possible.
You might get similar functionality with <string-array> resource:
<resources>
<string-array name="room">
<item>#string/localizedText</item>
<item>#string/otherLocalizedText</item>
</string-array>
</resources>
then you would use it like this :
String[] room = getResources().getStringArray(R.array.room);
String localizedText = room[0];
String otherLocalizedText = room[1];
Localization in Android is done with resource identifiers. Check out this Android tutorial.
http://developer.android.com/resources/tutorials/localization/index.html
See discussion below.
Great answer kyis, shame I still don't have enough brownie points to rate it. To answer Nick's question, just change the last bit of code to:
int id = parser.getAttributeResourceValue(ns, "text", 0);
return (id != 0) ? getString(id) : parser.getAttributeValue(ns, "text");
Note that I used 0 for the default value of the resource as this is guaranteed never to be a real resource value. -1 would have done also.

Categories

Resources