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

例子:

PLACEHOLDER1一些字符串


当前回答

Kotlin版本的公认答案…

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

其他回答

是的!你可以不写任何Java/Kotlin代码,只使用XML,使用我创建的这个小库,它在构建时这样做,所以你的应用程序不会受到它的影响:https://github.com/LikeTheSalad/android-stem

使用

你的字符串:

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

构建后生成的字符串:

<!-- Auto generated during compilation -->
<resources>
    <string name="welcome_message">Welcome to My App Name</string>
</resources>

多语言项目

作为一个曾经致力于大型白标解决方案的人,我可以说有很多需要考虑的地方。 除了文本方向,语法本身也会让你头疼。 例如,项目的顺序可以改变这样

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

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

然后在你的变体中,变体/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变量一个文件。

还有一个数字语法的问题。这可以用复数来解决,但这里xml文件的复杂性又增加了。而且,正如所指出的,零并不像人们所期望的那样工作。但你也可能想要限制你的应用的计数,因为显示大小限制或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端获得相同的功能,根据我的经验,需要python脚本来修改.xcodeproj文件并生成swift代码。

当你想使用实际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文件中。

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

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

但是,你也应该阅读Elias Mårtenson关于Android复数处理“零”的回答。对某些值(如“零”)的解释存在问题。