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.
Related
I have some packages with a status.
in strings xml those status would be (translated to norwegian)
<string name="collectable">Kan hentes</string>
<string name="underway">Underveis</string>
<string name="transport_to_recipient">Leveres i dag</string>
<string name="returned">Sendt i retur</string>
<string name="archived">Utlevert</string>
<string name="sent">Sendt av deg</string>
<string name="unknown">Foreløpig ukjent</string>
so in my xml layout i want to do something like this:
android:text="#{`#strings/`+ parcelListItem.status.toString()}"
so the string names would match with the text. i am also using databinding for this to get parcelitem.status.
My problem is that it just prints out "#strings/transport_to_receipient", not the actual translation. Is there a way to do this in a smart way, or should i just probably make a method on it?
You need to get the by using something like this:
public String getStringByString(String key) {
String retString = key;
int id = getResource().getIdentifier(key, "string", getPackageName());
if (id != 0) {
retString = getString(id);
}
return retString;
}
So basically this will try to get the resource name from you strings.xml file. If it fails will return the key.
To access this string <string name="transport_to_recipient">Leveres i dag</string> you need to update your resource configuration just LIKE THIS.
Resources resources= context.getResources();
Configuration configuration = resources.getConfiguration();
DisplayMetrics metrics= resources.getDisplayMetrics();
configuration.setLocale(new Locale("nb")); //country code depend on your XML
resources.updateConfiguration(configuration,metrics);
nb is country code might be in your case it will be different.
You will country code on the side of string.xml file in Resource folder.
Ended up using this method based on the answer above:
public static int getStringResourceIdByName(Context context, String stringId) {
if (Utils.isNullOrBlank(stringId))
stringId = ParcelListItem.Status.unknown.toString();
return context.getResources().getIdentifier(stringId, "string", context.getPackageName());
}
and referred like this:
Utils.getStringResourceIdByName(context, parcelListItem.status.toString())
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
Why do I get this Exception?
05-18 20:29:38.044: ERROR/AndroidRuntime(5453): java.lang.IllegalArgumentException: The key must be an application-specific resource id.
05-18 20:29:38.044: ERROR/AndroidRuntime(5453): at android.view.View.setTag(View.java:7704)
05-18 20:29:38.044: ERROR/AndroidRuntime(5453): at com.mypkg.viewP.inflateRow(viewP.java:518)
the line in question is:
((Button) row.findViewById(R.id.btnPickContact)).setTag(TAG_ONLINE_ID,objContact.onlineid);
and I have it defined as:
private static final int TAG_ONLINE_ID = 1;
The reason you're not able to use setTag(int, Object) is because android require a pre-compiled unique id in the 'int' argument.
Try creating two unique entry in String.xml xml say, "firstname" & "secondname" & use them as below
imageView.setTag(R.string.firstname, "Abhishek");
imageView.setTag(R.string.lastname, "Gondalia");
I'm a little late to the party but I stumbled on this problem myself today and thought I'd give an answer as well. This answer will be a bit of a compilation of the other answers, but with a twist. First of all, the id, as has been pointed out by others, can NOT be a constant defined in your code (such as private static final int MYID = 123) or any other int that you define as a field somewhere.
The id has to be a precompiled unique id, just like the ones you get for strings that you put in values/strings.xml (ie R.string.mystring). Refer to http://developer.android.com/guide/topics/resources/available-resources.html and http://developer.android.com/guide/topics/resources/more-resources.html for more information.
My suggestion is that you create a new file called values/tags.xml and write:
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<item name="TAG_ONLINE_ID" type="id"/>
</resources>
I think it's better to create a separate file instead of putting it in strings.xml as EtienneSky suggested.
THIS WILL DO THE JOB...
If you just have 1 setTag in your class, you could use any int, maybe static final declared in the top.
The problem comes when you had 2 or more setTag's with different keys.
I mean:
public static final int KEY_1 = 1;
public static final int KEY_2 = 2;
...
setTag(KEY_1, VALUE_1)
setTag(KEY_2, VALUE_2)
...
That scenario is wrong. You then need to add a value file called maybe ids.xml with the following:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="resourceDrawable" />
<item type="id" name="imageURI" />
</resources>
Then, in your class, call:
...
setTag(R.id.resourceDrawable, VALUE_1)
setTag(R.id.imageURI, VALUE_2)
...
The tag id must be unique so it wants it to be an id created in a resources file to guarantee uniqueness.
If the view will only contain one tag though you can just do
setTag(objContact.onlineid);
private static final int TAG_ONLINE_ID = 1 + 2 << 24;
should work. More info from ceph3us:
The specified key should be an id declared in the resources of the
application to ensure it is unique Keys identified as belonging to the
Android framework or not associated with any package will cause an
IllegalArgumentException to be thrown.
from source:
public void setTag(int key, final Object tag) {
// If the package id is 0x00 or 0x01, it's either an undefined package
// or a framework id
if ((key >>> 24) < 2) {
throw new IllegalArgumentException("The key must be an application-specific "
+ "resource id.");
}
setKeyedTag(key, tag);
}
I've used viewHolder.itemTitleTextView.getId(). But you can also declare in your resources:
<item type="id" name="conversation_thread_id"/>
you can use this :
private static final int TAG_ONLINE_ID = View.generateViewId() + 2 << 24;
for uniqness application-specific resource id
This works for me:
setTag(0xffffffff,objContact.onlineid);
The reason why you want to save the value by an id is, that you want to cover more than one value in this tag, right?
Here a more simple solution:
Let's say you want to save two values (Strings) into this tag: "firstname" and "lastname". You can save them both in one string, separated by semicolon:
v.setTag(firstname + ";" + lastname);
... and access them by splitting them into an string array:
String[] data = v.getTag().toString().split(";");
System.out.println(data[0]) //firstname
System.out.println(data[1]) //lastname
Here is a simple workaround that works for me:
int tagKey = "YourSimpleKey".hashCode();
myView.setTag(tagKey, "MyTagObject");
the important clue here is to call .hashCode(); on the String