是否可以在 strings.xml 中为运行时值添加占位符?

Posted

技术标签:

【中文标题】是否可以在 strings.xml 中为运行时值添加占位符?【英文标题】:Is it possible to have placeholders in strings.xml for runtime values? 【发布时间】:2011-04-09 01:18:20 【问题描述】:

是否可以在string.xml 的字符串值中包含可以在运行时分配值的占位符?

例子:

一些字符串 PLACEHOLDER1 还有一些字符串

【问题讨论】:

Reference one string from another string in strings.xml?的可能重复 @HarishGyanani 不,这个比较旧,应该合并到这个 【参考方案1】:

格式和样式

是的,请参阅String Resources: Formatting and Styling的以下内容

如果您需要使用String.format(String, Object...) 格式化字符串,则可以通过将格式参数放入字符串资源中来实现。例如,使用以下资源:

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

在本例中,格式字符串有两个参数:%1$s 是字符串,%2$d 是十进制数。您可以使用应用程序中的参数格式化字符串,如下所示:

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

基本用法

注意getString 有一个使用字符串作为格式字符串的重载:

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

复数

如果你需要处理复数,使用这个:

<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>

第一个mailCount 参数用于决定使用哪种格式(单数或复数),其他参数是您的替换:

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

更多详情请见String Resources: Plurals。

【讨论】:

第一个代码示例中的String.format调用其实不是必须的,Resources.getString()支持格式化,见:developer.android.com/reference/android/content/res/…,java.lang.Object...) 对于 String.xml 中的复数,您需要将 id 指定为 R.plurals.welcome_messages 而不是 R.string.welcome_messages 复数只返回最后一项字符串...知道如何获取附加字符串。 当使用此方法格式化您的字符串时,它会删除您可能使用过的任何元标记,例如可能包含在 xml 字符串文本中的超链接等 字符串代表%1$s,十进制代表%2$d,整数代表什么? %1,%2 是什么意思。这是参数的计数吗?如果我想要第三个参数是否提到 %3?【参考方案2】:

补充答案

当我第一次在接受的答案中看到%1$s%2$d 时,这毫无意义。这里还有一点解释。

它们被称为格式说明符。在 xml 字符串中,它们的形式为

%[parameter_index$][format_type] 

%:百分号标记格式说明符的开始。

参数索引:这是一个数字,后跟一个美元符号。如果您想将三个参数插入到字符串中,那么它们将被称为1$2$3$。您将它们放在资源字符串中的顺序无关紧要,重要的是您提供参数的顺序。

格式化类型:有很多种方法可以格式化事物 (see the documentation)。以下是一些常见的:

s字符串

d十进制整数

f浮点数

示例

我们将创建以下格式化字符串,其中灰色部分以编程方式插入。

我姐姐Mary12 岁。

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);

注意事项

我可以使用getString,因为我在一个活动中。如果不可用,您可以使用context.getResources().getString(...) String.format() 也会格式化一个字符串。 1$2$ 术语不需要按此顺序使用。也就是说,2$ 可以在 1$ 之前。这在为使用不同词序的语言国际化应用程序时很有用。 如果要重复使用,可以在 xml 中多次使用 %1$s 等格式说明符。 Use %% to get the actual % character. 有关更多详细信息,请阅读以下有用的教程:Android SDK Quick Tip: Formatting Resource Strings

【讨论】:

你知道美元符号的用途吗?我注意到有时,即使没有它,我的应用程序也能正常工作。示例:在 strings.xml 中:%1s 症状 在我的活动中:setToolbarTitle(getString(R.string.symptoms_append, it)) @programmerdreamer,抱歉,我已经有一段时间没有做这个了,所以如果我知道 $ 是干什么用的,我现在已经忘记了。【参考方案3】:

当您想使用实际 strings.xml 文件中的参数而不使用任何 Java 代码时:

<?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>

这不适用于资源文件,即必须将变量复制到需要它们的每个 XML 文件中。

【讨论】:

这是一个很好的答案,除非您在 有没有办法在单独的文件中定义&lt;!DOCTYPE... ]&gt; 部分并将其包含在多个资源文件中?有什么技巧可以做到这一点?【参考方案4】:

一直在寻找相同的,最后找到了以下非常简单的解决方案。最佳:开箱即用。 1. 改变你的字符串资源:

<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。使用字符串替换:

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

其中 c 是 Context,name 是字符串变量,count 你的 int 变量

你需要包括

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

在您的 res/strings.xml 中。 为我工作。 :)

【讨论】:

格式说明符周围的xliff 标签有什么意义?与单独使用 %s%d 说明符相比,它们增加了哪些额外价值? xliff 标记中的“name”属性只是向翻译人员提示其中将替换什么的一种方式。有时很难猜出“%s”的含义。 “xliff:g”用于通知译者不应替换的部分。想想名字、优惠券代码,当然还有像上面这样的动态占位符。更多信息在这里developer.android.com/guide/topics/resources/… 我们在这里使用相同的方法,效果很好,除了一件事:我们通过 Google Play 使用过的所有翻译器(可能与 Google Play 本身有关)在前后添加了额外的行xliff:g 标签,它会在我们的字符串中创建额外的空格,并导致非常烦人的翻译集成。【参考方案5】:

如果你想写百分比(%),复制它:

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

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

如果你只写%1$d%,你会得到错误:Format string 'percent' is not a valid format string so it should not be passed to String.format

或者use formatted=false" 代替。

【讨论】:

【参考方案6】:

在 Kotlin 中,您只需像这样设置字符串值:

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

在你的布局上创建一个文本视图:

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

如果您使用 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))  

您可能需要从应用程序上下文中获取资源。

【讨论】:

这与 java 示例有何不同?我看不出有什么区别【参考方案7】:

在你的字符串文件中使用这个

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

并在您的代码中相应地使用

coinsTextTV.setText(String.format(getContext().getString(R.string.redeem_point), rewardPoints.getReward_points()
                        , rewardPoints.getConversion_rate(), getString(R.string.rs)));

【讨论】:

【参考方案8】:

但是,您还应该阅读Elias Mårtenson 在Android plurals treatment of “zero” 上的回答。对某些值(例如“零”)的解释存在问题。

【讨论】:

【参考方案9】:

在 res/values/string.xml

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

在java代码中

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);

【讨论】:

【参考方案10】:

你可以使用MessageFormat:

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

在 Java 代码中:

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

API 级别 1:

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

【讨论】:

【参考方案11】:

是的!您可以在不编写任何 Java/Kotlin 代码的情况下执行此操作,只需使用我创建的这个小型库(在构建时执行此操作),只使用 XML,因此您的应用程序不会受到它的影响:https://github.com/LikeTheSalad/android-string-reference

用法

你的字符串:

<resources>
    <string name="app_name">My App Name</string>
    <string name="template_welcome_message">Welcome to $app_name</string>
</resources>

构建后生成的字符串:

<!--resolved.xml-->
<resources>
    <string name="welcome_message">Welcome to My App Name</string>
</resources>

【讨论】:

【参考方案12】:

多语言项目

作为一个曾使用多种不同语言和每个变体配置的主要白标解决方案的人,我可以说有很多需要考虑的地方。 抛开文字方向不谈,单单语法就可以让你有些头疼。 例如项目的顺序可以改变吗

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

优先于

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

但是一旦您与通常不知道字符串或整数是什么的翻译人员一起工作,更不用说每种类型使用什么格式字符,或者一般不知道参数应用顺序的人你的代码,甚至你自己都忘记了,或者事情发生了变化,必须同时在多个地方更新,所以使用MessageFormat like

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

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

也不可行,让非技术人员尝试找出xlift 的想法甚至无法接受,那么我所知道的最佳解决方案是使用明确的命名占位符 就这样

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

..或其他与您的文本不冲突的内容。

虽然你可以像使用 DOCTYPE 一样

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

这不适用于每种语言的单独文件。

因此在您的main/res/values/strings.xml 中提供像这样的占位符和默认字符串

<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>

然后在你的变体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>

要使用它,写类似

    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)
    

使用一些辅助函数,例如

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

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

仅出于对翻译文件和开发有参考的原因。因此,每个构建风格不应该有一个默认文件。只有一个默认文件,然后是每个语言 x 变体的一个文件。

现在还有数字语法的问题。这可以通过plurals 解决,但这里 xml 文件的复杂性再次增加。而且,正如所指出的,zero 并没有像人们期望的那样工作。但是,由于显示大小限制或 UI 的预渲染图像数量,您可能希望限制应用计数,并且需要显示 99+ 而不是 100。一个解决方案是使用一个辅助函数,如

    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)
    

覆盖和扩展复数解析器的行为。

如果每个变体都有可选功能,请添加res/values/strings-beans.xml,例如:

<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>

variant-with-beans/res/value-en/strings-beans.xml中的变体只需要包含

<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>

可以基于每个文件提供特定于语言的覆盖。 要使用它,代码可以如下所示

    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)

解析为输出

    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!"

并且由于语言特定资源文件的简单性,然后可以使用电子表格或您公司自己的服务器端点等的部署工具生成它们。

我希望你喜欢我在 Android 的动态字符串资源世界中的疯狂旅程,并希望你不是可怜的傻瓜,必须获得相同的功能才能在产品的 ios 端工作,这是我的经验所要求的用于修改 .xcodeproj 文件并生成 swift 代码的 python 脚本。

【讨论】:

【参考方案13】:

该问题的直接 Kotlin 解决方案:

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 )

【讨论】:

【参考方案14】:

已接受答案的 Kotlin 版本...

val res = resources
val text = String.format(res.getString(R.string.welcome_messages), username, mailCount)

【讨论】:

更正确的方法是使用带参数的resources.getString(int, ... args) 方法而不是String.format。此代码中也没有 kotlin 细节,除了 val 关键字。查看 msbodw001 的答案

以上是关于是否可以在 strings.xml 中为运行时值添加占位符?的主要内容,如果未能解决你的问题,请参考以下文章

在 Visual Studio 中跟踪运行时值

Android strings.xml 最佳实践? [关闭]

strings.xml 中的参数是不是可能? [复制]

如何在 Angular 4 中为表格添加分页?

在 Web Api 2 中为链接标题添加分页

Spring实战bean装配的运行时值注入——属性占位符和SpEL