I've faced a problem with quantity strings (plurals).
The manual says, I may provide quantity strings that are specific for each localization, and there are several common cases: "zero", "one", "two", "few", "many", and "other". I don't know if all possible cases for all languages in the world were covered; anyway, it is more than enough for Russian that I'm trying to make a localization for.
In Russian, numbers from 2 to 4 should be treated like "few" (the rule is actually more complex but I only need numbers below ten).
However, when I request a quantity string for 2, the system takes the "other" string. It doesn't take neither "two" string nor "few" one (I have provided them in my resources). If I removed the "other" string, I get the exception:
android.content.res.Resources$NotFoundException:
Plural resource ID #0x7f080000 quantity=2 item=other
I tried this both on emulator (Android 2.1) and on a real device (Android 2.3), the behaviour is the same wrong in both cases. Obviously, there is a mistake somewhere—the system does not recognize locale-specific quantities for my language. Could it be that Android has some bug here?
I believe this is currently broken in Android.
http://code.google.com/p/android/issues/detail?id=8287
Specifically, the following code in PluralRules.java shows that most languages only use the one or other strings, but Czech will use the few string:
static final PluralRules ruleForLocale(Locale locale) {
String lang = locale.getLanguage();
if ("cs".equals(lang)) {
if (cs == null) cs = new cs();
return cs;
}
else {
if (en == null) en = new en();
return en;
}
}
private static PluralRules cs;
private static class cs extends PluralRules {
int quantityForNumber(int n) {
if (n == 1) {
return QUANTITY_ONE;
}
else if (n >= 2 && n <= 4) {
return QUANTITY_FEW;
}
else {
return QUANTITY_OTHER;
}
}
}
private static PluralRules en;
private static class en extends PluralRules {
int quantityForNumber(int n) {
if (n == 1) {
return QUANTITY_ONE;
}
else {
return QUANTITY_OTHER;
}
}
}
Thanks to Brigham who has pointed to the issue where the problem is explained; that explanation first rised more questions but now they seem to be solved. Indeed, the quantity strings don't work properly (at least prior to API 11, which is Android 3.x), so you have to use an alternative.
The solution for APIs prior to version 11 is mentioned in the comment 15 that contains a link to the project for alternative handling of quantity strings. That project is a program that simply does what the system was supposed to do. It can be converted to a library easily, so you just add it to your project, import the class and go.
Define your plurals strings in values-ru/strings.xml not in values/strings.xml
set your mobile device language as Russian (from setting->Language&input->Language->select Russian)
If your language is not Russian it will take plurals strings from values/strings.xml
Related
I tried to convert int to curency format and everything worked as I expected
example 1000 --> Rp1.000
but on certain devices the results are not what I expected
i.e. 1000 --> Rp1.000,00
So the question is how to remove the last 2 extra nolls if the device displays them.
my current code:
public static String toUang(String DigitUang) {
return NumberFormat.getCurrencyInstance(new Locale("in", "ID")).format(Double.parseDouble(DigitUang));
}
The best solution here would probably be to fix your locale settings and handle the presentation there. That being said, you also could probably handle this using a regex replacement:
public static String toUang(String DigitUang) {
return NumberFormat.getCurrencyInstance(new Locale("in", "ID"))
.format(Double.parseDouble(DigitUang))
.replaceAll("[,.]00$", "");
}
I need to check my fragment when I change the app language.
Here is my Android Espresso test:
#Test
public void changeLanguages() {
Resources resources = context.getResources();
String[] appLanguages = resources.getStringArray(R.array.app_lang_codes);
for (int index = 0; index < appLanguages.length; index++) {
String currentLang = appLanguages[index];
Locale currentLocale = new Locale(currentLang);
if (currentLocale.equals(AppLanguageService.getLocaleRO(context))) {
// click Romanian
onView(withId(R.id.containerLanguageRO)).perform(click());
onView(withId(R.id.textViewSelectLanguage)).check(matches(withText("Selecți limba")));
} else if (currentLocale.equals(AppLanguageService.getLocaleEN(context))) {
// click English
onView(withId(R.id.containerLanguageEN)).perform(click());
onView(withId(R.id.textViewSelectLanguage)).check(matches(withText("Select language")));
}
}
}
Ant it's working fine. OK!
But as you can see I need to hard code the string for a specific language for the test.
"Selecți limba" and "Select language". And I think it's not good.
Is it possible to not use hard code strings to check that text is shown in a specific language?
You can use
mActivityRule.getActivity()
to get the activity you are testing. With this you could fetch a string from your resources like this:
mActivityRule.getActivity().getResources().getString(R.string.your_string)
You could rewrite your check like this:
onView(withId(R.id.textViewSelectLanguage)).check(matches(withText(mActivityRule.getActivity().getResources().getString(R.string.your_string))));
where your_string is the name of your string resource in your strings.xml files.
I have a large Android codebase and I am writing a custom lint rule that checks whether the values of certain attributes fall within a given range.
For example, I have this component:
<MyCustomComponent
my:animation_factor="0.7"
...>
</MyCustomComponent>
and I want to write a lint rule that alerts developers that values of my:animation_factor >= 1 should be used with caution.
I followed the instructions at http://tools.android.com/tips/lint-custom-rules and managed to retrieve the value of my:animation_factor using this code:
import com.android.tools.lint.detector.api.*;
public class XmlInterpolatorFactorTooHighDetector {
....
#Override
public Collection<String> getApplicableElements() {
return ImmutableList.of("MyCustomComponent");
}
#Override
public void visitElement(XmlContext context, Element element) {
String factor = element.getAttribute("my:animation_factor");
...
if (value.startsWith("#dimen/")) {
// How do I resolve #dimen/xyz to 1.85?
} else {
String value = Float.parseFloat(factor);
}
}
}
This code works fine when attributes such as my:animation_factor have literal values (e.g. 0.7).
However, when the attribute value is a resources (e.g. #dimen/standard_anim_factor) then element.getAttribute(...) returns the string value of the attribute instead of the actual resolved value.
For example, when I have a MyCustomComponent that looks like this:
<MyCustomComponent
my:animation_factor="#dimen/standard_anim_factory"
...>
</MyCustomComponent>
and #dimen/standard_anim_factor is defined elsewhere:
<dimen name="standard_anim_factor">1.85</dimen>
then the string factor becomes "#dimen/standard_anim_factor" instead of "1.85".
Is there a way to resolve "#dimen/standard_anim_factor" to the actual value of resource (i.e. "1.85") while processing the MyCustomComponent element?
The general problem with the resolution of values is, that they depend on the Android runtime context you are in. There might be several values folders with different concrete values for your key #dimen/standard_anim_factory, so just that you are aware of.
Nevertheless, AFAIK there exist two options:
Perform a two phase detection:
Phase 1: Scan your resources
Scan for your attribute and put it in a list (instead of evaluating it immediately)
Scan your dimension values and put them in a list as well
Phase 2:
override Detector.afterProjectCheck and resolve your attributes by iterating over the two lists filled within phase 1
usually the LintUtils class [1] is a perfect spot for that stuff but unfortunately there is no method which resolves dimensions values. However, there is a method called getStyleAttributes which demonstrates how to resolve resource values. So you could write your own convenient method to resolve dimension values:
private int resolveDimensionValue(String name, Context context){
LintClient client = context.getDriver().getClient();
LintProject project = context.getDriver().getProject();
AbstractResourceRepository resources = client.getProjectResources(project, true);
return Integer.valueOf(resources.getResourceItem(ResourceType.DIMEN, name).get(0).getResourceValue(false).getValue());
}
Note: I haven't tested the above code yet. So please see it as theoretical advice :-)
https://android.googlesource.com/platform/tools/base/+/master/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
Just one more slight advice for your custom Lint rule code, since you are only interested in the attribute:
Instead of doing something like this in visitElement:
String factor = element.getAttribute("my:animation_factor");
...you may want to do something like this:
#Override
public Collection<String> getApplicableAttributes() {
return ImmutableList.of("my:animation_factor");
}
#Override
void visitAttribute(#NonNull XmlContext context, #NonNull Attr attribute){
...
}
But it's just a matter of preference :-)
I believe you're looking looking for getResources().getDimension().
Source: http://developer.android.com/reference/android/content/res/Resources.html#getDimension%28int%29
Assuming xml node after parsing your data, try the following
Element element = null; //It is your root node.
NamedNodeMap attrib = (NamedNodeMap) element;
int numAttrs = attrib.getLength ();
for (int i = 0; i < numAttrs; i++) {
Attr attr = (Attr) attrib.item (i);
String attrName = attr.getNodeName ();
String attrValue = attr.getNodeValue ();
System.out.println ("Found attribute: " + attrName + " with value: " + attrValue);
}
I'm writing an app in which I want to display a string, describing an object in my model, to the user. I've started thinking about localization and I'm a bit stumped as to the best approach to deal with the difference in grammar between languages.
Imagine if in my model I have a verb an object and a quantity (pseudocode):
int _quantity = 6;
String _object = "#string/object";
String _verb = "#string/verb";
This should render in English as _verb + _quantity + _object (i.e. "eat 6 eggs", whereas in German it should render as _quantity + _object + _verb (i.e. "6 eggs eat" (but, obviously, using German word tokens rather than English :) ))
Is there a standard approach to this type of problem in Android?
Thanks.
OK, I've come up with a solution which, based on what I know, is the best I can find. If anyone has a better solution, please post!
1) In strings.xml, define any bits of text needed (for me, these equated to the localised names for enumeration members and for the string used to build natural language strings (more on this in a bit))
Example:
<resources>
...
<string name="verb_ate">ate</string>
<string name="verb_threw">threw</string>
...
<string name="modelobject_naturallanguagedescription">I {0} {1} {2}</string>
...
</resources>
2) In any enumerations, decorate them with the resource ids of the strings they are to use for display
Example:
public enum VerbType
Ate(R.string.verb_ate),
Threw(R.string.verb_threw);
private final int _stringID;
private VerbType(int stringID) {
_stringID = stringID;
}
public int getStringID() {
return _stringID;
}
}
3) Make a helper function which will take the model object and the context and will piece together the required natural language string (using java.text.MessageFormat), plus a helper function for each enumeration:
Example (assume that ModelObject has _verb, _quantity, _object and relevant getters):
public static String getVerb(VerbType v, Context c) {
return c.getResources().getString(v.getStringID());
}
public static String getNaturalLanguageString(ModelObject o, Context c) {
MessageFormat mf = new MessageFormat(c.getResources().getString(R.string.modelobject_naturallanguagedescription);
return mf.format(new Object[] {getVerb(o.getVerb(),c), o.getQuantity(), o.getObject()});
}
So, having done all of this, it's easy enough to define another strings file for a different language (German for example (I don't know German, so the language won't be correct)):
<resources>
...
<string name="verb_ate">gegessen</string>
<string name="verb_threw">gethrown</string>
...
<string name="modelobject_naturallanguagedescription">Ich haben {1} {2} {0}</string>
...
</resources>
So, in an english locale getNaturalLanguageString might return:
I ate 6 eggs
whereas in a German locale it might return:
Ich haben 6 eggs gegessen
I'm using the speech recognizer to get a voice input from the user, it returns an array of 5 strings which I pass to this method
public int analyzeTag(ArrayList<String> voiceResults,Editor editor, Context context){
for (String match : voiceResults) {
Log.d(TAG, match);
if (match.equalsIgnoreCase(context.getResources().getString(R.string.first_tag))){
editor.append(context.getResources().getString(R.string.first_tag));
return 1;
}
else if (match.equalsIgnoreCase(context.getResources().getString(R.string.second_tag))){
editor.append(context.getResources().getString(R.string.second_tag));
return 1;
}
//etc....(huge list of tags)
//Some tags might also have acceptable variations, example:
else if (match.equalsIgnoreCase("img") || match.equalsIgnoreCase("image")
{
editor.append("img"); //the string to append is always taken from the first variation
}
}
return 0;
}
This method compares the results with a list of tags, the tag list will be pretty big with hundreds of tags so I would like to find the most efficient way to do this operation.
I need help with:
1.Is my way of comparing results the most efficient? Is there a better way? (from the user experience perspective, I don't want users waiting a long time to get a result).
The voice input will be a big part of my app so this method will be called quite often
2.I have a long list of tags, obviously the if(), elseIf() route is gonna be quite repetitive, is there a way to iterate this? Considering the fact that some tags might have variations (even more than 1)and that the variation 1 ("img") will be the same for everyone, but other variations will be locale/language sensitive example: "image" for english users "immagini" for italian users etc.
Text appended to the editor will be always taken from the first variation
How about puting tags in a StringArray and then iterate though the array ?
String[] tags = context.getResources().getStringArray(R.array.tags);
for (String match : voiceResults) {
for (int index = 0; index < tags.length; index++ ) {
if (match.equalsIgnoreCase(tags[index]) {
editor.append(tags[index]);
}
}
}
Here's the doc on StringArray