646

Is it possible to have placeholders in string values in string.xml that can be assigned values at run time?

Example:

some string PLACEHOLDER1 some more string

Ellen Spertus
  • 6,576
  • 9
  • 50
  • 101
SoftReference
  • 6,969
  • 4
  • 19
  • 25
  • Possible duplicate of [Reference one string from another string in strings.xml?](http://stackoverflow.com/questions/4746058/reference-one-string-from-another-string-in-strings-xml) – Harish Gyanani Dec 12 '16 at 09:38
  • @HarishGyanani No, this one is older, that one should be merge to this one instead – Danh Dec 12 '16 at 09:58

14 Answers14

1254

Formatting and Styling

Yes, see the following from String Resources: Formatting and Styling

If you need to format your strings using String.format(String, Object...), then you can do so by putting your format arguments in the string resource. For example, with the following resource:

<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>

In this example, the format string has two arguments: %1$s is a string and %2$d is a decimal number. You can format the string with arguments from your application like this:

Resources res = getResources();
String text = String.format(res.getString(R.string.welcome_messages), username, mailCount);

Basic Usage

Note that getString has an overload that uses the string as a format string:

String text = res.getString(R.string.welcome_messages, username, mailCount);

Plurals

If you need to handle plurals, use this:

<plurals name="welcome_messages">
    <item quantity="one">Hello, %1$s! You have a new message.</item>
    <item quantity="other">Hello, %1$s! You have %2$d new messages.</item>
</plurals>

The first mailCount param is used to decide which format to use (single or plural), the other params are your substitutions:

Resources res = getResources();
String text = res.getQuantityString(R.plurals.welcome_messages, mailCount, username, mailCount);

See String Resources: Plurals for more details.

wbk727
  • 8,017
  • 12
  • 61
  • 125
  • 72
    The String.format call in the first code sample is actually not necessary, Resources.getString() supports formatting, see: http://developer.android.com/reference/android/content/res/Resources.html#getString(int, java.lang.Object...) – Arnaud Oct 24 '12 at 11:01
  • 17
    for Plurals in String.xml you need to give id as R.plurals.welcome_messages instead of R.string.welcome_messages – om252345 Feb 09 '13 at 16:06
  • plural thing returning only last item string...any idea how can get append string. – CoDe Mar 05 '14 at 06:14
  • 1
    When using this method to format your string, it removed any meta tags you may have used such as hyperlinks etc that could have been contained in the xml string text – Jono Mar 30 '15 at 09:31
  • Can you like define a string having placeholders in the database and then use String.format to replace the placeholders with actual text? – John Ernest Guadalupe Sep 18 '15 at 14:59
  • 4
    String stands for %1$s, decimal for %2$d and integer stands for what? what is the meaning %1,%2.Is this counting of parameter?.if I want third parameter Is mention %3? – reegan29 Oct 23 '15 at 10:57
  • how can these be used in layout files or any other xml file – dmSherazi Sep 20 '16 at 10:58
  • How can we handle this in RTL languages like Arabic? I am facing issues while using %1$s in Arabic – venkat Oct 10 '16 at 07:26
  • I think there's a typo in the last line `String text = res.getQuantityString(R.plurals.welcome_messages, mailCount, username, mailCount);` should be `String text = res.getQuantityString(R.plurals.welcome_messages, username, mailCount);`. Notice the `mailCount` was passed twice. – Gabriel Bourgault Jul 14 '17 at 13:34
  • @GabrielBourgault I believe the initial version was correct. The first `mailCount` was the `quantity` argument, used to decide whether or not to use the plural form, and the second `mailCount` was to fill in a wildcard for the format string. – Tim M. Dec 18 '19 at 17:08
  • Just want to point this out, calling getString() with placeholder argument will cause an error on unit test. It's best to use String.format(). I am using mockito – CodeAndWave Jan 22 '20 at 20:40
414

Supplemental Answer

When I first saw %1$s and %2$d in the accepted answer, it made no sense. Here is a little more explanation.

They are called format specifiers. In the xml string they are in the form of

%[parameter_index$][format_type] 
  • %: The percent sign marks the beginning of the format specifier.

  • parameter index: This is a number followed by a dollar sign. If you had three parameters that you wanted to insert into the string, then they would be called 1$, 2$, and 3$. The order you place them in the resource string doesn't matter, only the order that you supply the parameters.

  • format type: There are a lot of ways that you can format things (see the documentation). Here are some common ones:

  • s string

  • d decimal integer

  • f floating point number

Example

We will create the following formatted string where the gray parts are inserted programmatically.

My sister Mary is 12 years old.

string.xml

<string name="my_xml_string">My sister %1$s is %2$d years old.</string>

MyActivity.java

String myString = "Mary";
int myInt = 12;
String formatted = getString(R.string.my_xml_string, myString, myInt);

Notes

  • I could use getString because I was in an Activity. You can use context.getResources().getString(...) if it is not available.
  • String.format() will also format a String.
  • The 1$ and 2$ terms don't need to be used in that order. That is, 2$ can come before 1$. This is useful when internationalizing an app for languages that use a different word order.
  • You can use a format specifier like %1$s multiple times in the xml if you want to repeat it.
  • Use %% to get the actual % character.
  • For more details read the following helpful tutorial: Android SDK Quick Tip: Formatting Resource Strings
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • Do you know what's the purpose of the dollar sign there? I noticed that sometimes, even without it my app is working fine. Example: in strings.xml: %1s Symptoms in my activity: setToolbarTitle(getString(R.string.symptoms_append, it)) – programmer dreamer Dec 15 '21 at 01:50
  • 1
    @programmerdreamer, Sorry, it's been a while since I've worked on this and so if I ever knew what the `$` was there for I've forgotten now. – Suragch Dec 15 '21 at 06:06
139

When you want to use a parameter from the actual strings.xml file without using any Java code:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
  <!ENTITY appname "WhereDat">
  <!ENTITY author "Oded">
]>

<resources>
    <string name="app_name">&appname;</string>
    <string name="description">The &appname; app was created by &author;</string>
</resources>

This does not work across resource files, i.e. variables must be copied into each XML file that needs them.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
Oded Breiner
  • 28,523
  • 10
  • 105
  • 71
21

Was looking for the same and finally found the following very simple solution. Best: it works out of the box.
1. alter your string ressource:

<string name="welcome_messages">Hello, <xliff:g name="name">%s</xliff:g>! You have 
<xliff:g name="count">%d</xliff:g> new messages.</string>

2. use string substitution:

c.getString(R.string.welcome_messages,name,count);

where c is the Context, name is a string variable and count your int variable

You'll need to include

<resources xmlns:xliff="http://schemas.android.com/apk/res-auto">

in your res/strings.xml. Works for me. :)

user3016503
  • 211
  • 2
  • 3
  • 5
    What is the point of the `xliff` tags around the format specifiers? What extra value do they add as opposed to just using the `%s` and `%d` specifiers on their own? – Richard Le Mesurier May 08 '18 at 11:48
  • The "name" attribute in the xliff tag is just a way to give a hint to the translator about what will be substituted in there. Sometimes it isn't easy to guess what the "%s" means. – hugomg Nov 06 '19 at 00:24
  • The "xliff:g" is used to inform translators of sections that should not be replaced. Think names, coupon codes, and of course dynamic placeholders like above. More info here https://developer.android.com/guide/topics/resources/localization#mark-message-parts – Jeremy Fox Oct 06 '20 at 14:55
  • We use the same approach here and it works great, except for one thing: All of the translators we've used through Google Play (maybe it's something about Google Play itself) add extra lines before and after the xliff:g tags, which create extra spaces in our strings and makes for extremely annoying translation integrations. – mtrewartha Nov 15 '21 at 16:07
9

If you want to write percent (%), duplicate it:

<string name="percent">%1$d%%</string>

label.text = getString(R.string.percent, 75) // Output: 75%.

If you write simply %1$d%, you will get the error: Format string 'percent' is not a valid format string so it should not be passed to String.format.

Or use formatted=false" instead.

CoolMind
  • 26,736
  • 15
  • 188
  • 224
5

In Kotlin you just need to set your string value like this:

<string name="song_number_and_title">"%1$d ~ %2$s"</string>

Create a text view on your layout:

<TextView android:text="@string/song_number_and_title"/>

Then do this in your code if you using Anko:

val song = database.use { // get your song from the database }
song_number_and_title.setText(resources.getString(R.string.song_number_and_title, song.number, song.title))  

You might need to get your resources from the application context.

NimaAzhd
  • 162
  • 3
  • 15
msbodw001
  • 310
  • 3
  • 4
3

In your string file use this

<string name="redeem_point"> You currently have %s points(%s points = 1 %s)</string>

And in your code use as accordingly

coinsTextTV.setText(String.format(getContext().getString(R.string.redeem_point), rewardPoints.getReward_points()
                        , rewardPoints.getConversion_rate(), getString(R.string.rs)));
Soni Kumar
  • 283
  • 1
  • 4
  • 16
2

However, you should also read Elias Mårtenson's answer on Android plurals treatment of “zero”. There is a problem with the interpretation of certain values such as "zero".

Community
  • 1
  • 1
JJD
  • 50,076
  • 60
  • 203
  • 339
2

For Multilingual Projects

As someone who has worked on a major white label solution with many and varying languages and configurations per variant, I can say that there's a lot to consider. Text direction aside, grammar alone can give you some headaches. For instance can the order of items change thus

<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>

is to be preferred over

<string name="welcome_messages">Hello, %s! You have %d new messages.</string>

but once you work with translators who often don't know what a string or an integer is, let alone what formatting character to use for each type, or people in general who have no idea in what order the parameters are applied in your code, or even you yourself forget that, or things change which then must be updated at multiple places at once, so using MessageFormat like

<string name="welcome_message">Hello, {0}! You have {1} new messages.</string>

MessageFormat(R.string.welcome_message).format("Name", numMessages)

isn't viable either and the idea of having non tech people try to figure out xlift can't even be entertained then the best solution I know so far is to use explicit, named placeholders as such

<string name="placeholder_data" translatable="false">DATA</string>
<string name="placeholder_data" translatable="false">$DATA</string>
<string name="placeholder_data" translatable="false">%DATA%</string>

..or whatever else doesn't conflict with your texts.

And while you could use DOCTYPE like

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
  <!ENTITY placeholder_data "$DATA">
]>
<string name="text_with_data">Your data is &placeholder_data;.</string>

this will not work with separate files for each language.

Thus in your main/res/values/strings.xml provide the placeholders and default strings like this

<resources>

    <string name="placeholder_data" translatable="false">$DATA</string>
    <string name="placeholder_error" translatable="false">$ERROR</string>

    <string name="app_name">The App</string>

    <string name="content_loading">loading..</string>
    <string name="content_success">success: $DATA</string>
    <string name="content_error">error: $ERROR</string>

</resources>

and then in your variant variant/res/values-de/strings.xml

<resources>

    <string name="app_name">Die Applikation</string>

    <string name="content_loading">Ladevorgang..</string>
    <string name="content_success">Erfolg: $DATA</string>
    <string name="content_error">Netzwerkkommunikationsfehler: $ERROR</string>

</resources>

and to use it, write something like

    textView.text = when (response) {
        is Data -> getText(content_success).resolveData(response.data)
        is Error -> getText(content_error).resolveError(response.error)
        is Loading -> getText(content_loading)
    }

with the use of some helper functions like

    fun CharSequence.resolveData(data: JsonObject) =
        toString().replace(getString(placeholder_data), data.toString())

    fun CharSequence.resolveError(error: Throwable) =
        toString().replace(getString(placeholder_error), error.toString())

for simply the reason of having a reference for the translation files and development. Hence there should not be a default file per build flavor. Only the single default file and then a file per language x variant.

Now there's also the issue of numeric grammar. This can be solved with plurals but here again the complexity of the xml file increases. And, as pointed out, zero does not work as one would expect. But also you may want to have a limit to which your app counts due to display size limitations or number of pre-rendered images for your UI and need to display 99+ instead of 100. A solution would be to use a helper function like

    fun Context.getText(
        quantity: Int,
        @PluralsRes resIdQuantity: Int,
        @StringRes resIdNone: Int? = null,
        @StringRes resIdMoreThan: Int? = null,
        maxQuantity: Int? = null,
    ): CharSequence {
        if (resIdMoreThan != null && maxQuantity != null && quantity > maxQuantity)
            return getText(resIdMoreThan)
        return if (resIdNone != null && quantity == 0) return getText(resIdNone)
        else resources.getQuantityText(resIdQuantity, quantity)
    }

to override and extend the behavior of the plurals resolver.

And if you have optional features per variant then add a res/values/strings-beans.xml like:

<resources>

    <string name="placeholder_name" translatable="false">$NAME</string>
    <string name="placeholder_count" translatable="false">$COUNT</string>

    <string name="beans_content_bean_count_zero">Hello $NAME! You have no beans.</string>
    <string name="beans_content_bean_count_one">Hello $NAME! You have one bean.</string>
    <string name="beans_content_bean_count_many">Hello $NAME! You have $COUNT beans.</string>
    <string name="beans_content_bean_count_more_than_9000">Hello $NAME! You have over $COUNT beans!</string>
    <string name="beans_content_bean_count_two">@string/beans_content_bean_count_many</string>
    <string name="beans_content_bean_count_few">@string/beans_content_bean_count_many</string>
    <string name="beans_content_bean_count_other">@string/beans_content_bean_count_many</string>
    <plurals name="beans_content_bean_count">
        <item quantity="zero">@string/beans_content_bean_count_zero</item>
        <item quantity="one">@string/beans_content_bean_count_one</item>
        <item quantity="two">@string/beans_content_bean_count_two</item>
        <item quantity="few">@string/beans_content_bean_count_few</item>
        <item quantity="many">@string/beans_content_bean_count_many</item>
        <item quantity="other">@string/beans_content_bean_count_other</item>
    </plurals>

</resources>

while the variant in variant-with-beans/res/value-en/strings-beans.xml only needs to contain

<resources>

    <string name="beans_content_bean_count_zero">Hello $NAME! You have no beans.</string>
    <string name="beans_content_bean_count_one">Hello $NAME! You have one bean.</string>
    <string name="beans_content_bean_count_many">Hello $NAME! You have $COUNT beans.</string>
    <string name="beans_content_bean_count_more_than_9000">Hello $NAME! You have over 9000 beans!</string>

</resources>

and language specific overrides can be provided on a per file basis. To use this, the code can look like this

    val name = "Bob"
    val beanCount = 3
    val limit = 9000
    text = getText(
        beanCount,
        beans_content_bean_count,
        beans_content_bean_count_zero,
        beans_content_bean_count_more_than_9000,
        limit,
    )
        .resolveCount(beanCount)
        .resolveName(name)

which resolves to the outputs

    beanCount = 0 -> "Hello Bob! You have no beans."
    beanCount = 1 -> "Hello Bob! You have one bean."
    beanCount = 3 -> "Hello Bob! You have 3 beans."
    beanCount = 9001 -> "Hello Bob! You have over 9000 beans!"

and due to the resulting simplicity of the language specific resource files, they then can be generated with deployment tools from spread sheets or your company's own server endpoints, etc.

I hope you enjoyed my maddening ride into the world of dynamic string resources for Android and hopefully appreciate that you're not the poor fools having to get the same functionality to work on the iOS side of the product which from my experience required python scripts to modify .xcodeproj files and generate swift code.

krysov
  • 116
  • 1
  • 4
1

You can use MessageFormat:

<string name="customer_address">Wellcome: {0} {1}</string>

In Java code :

String text = MessageFormat(R.string.customer_address).format("Name","Family");

API level 1:

https://developer.android.com/reference/java/text/MessageFormat.html

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Ahmad Aghazadeh
  • 16,571
  • 12
  • 101
  • 98
1

in res/values/string.xml

<resources>
    <string name="app_name">Hello World</string>
    <string name="my_application">Application name: %s, package name: %s</string>
</resources>

in java code

String[] args = new String[2];
args[0] = context.getString(R.string.app_name);
args[1] = context.getPackageName();
String textMessage = context.getString(R.string.my_application,(Object[]) args);
Chanh
  • 433
  • 3
  • 12
1

Yes! you can do so without writing any Java/Kotlin code, only XML by using this small library I created, which does so at buildtime, so your app won't be affected by it: https://github.com/LikeTheSalad/android-stem

Usage

Your strings:

<resources>
    <string name="app_name">My App Name</string>
    <string name="welcome_message">Welcome to ${app_name}</string>
</resources>

The generated string after building:

<!-- Auto generated during compilation -->
<resources>
    <string name="welcome_message">Welcome to My App Name</string>
</resources>
César Muñoz
  • 535
  • 1
  • 6
  • 10
0

A Direct Kotlin Solution to the problem:

strings.xml

<string name="customer_message">Hello, %1$s!\nYou have %2$d Products in your cart.</string>

kotlinActivityORFragmentFile.kt:

val username = "Andrew"
val products = 1000
val text: String = String.format(
      resources.getString(R.string.customer_message), username, products )
nyxee
  • 2,773
  • 26
  • 22
-1

Kotlin version of the accepted answer...

val res = resources
val text = String.format(res.getString(R.string.welcome_messages), username, mailCount)
serv-inc
  • 35,772
  • 9
  • 166
  • 188
jesses.co.tt
  • 2,689
  • 1
  • 30
  • 49
  • 4
    More correct way is to use `resources.getString(int, ... args)` method with arguments instead of `String.format`. There is no kotlin specifics in this code either, other then `val` keyword. See answer of msbodw001 – dant3 Dec 12 '17 at 11:27