复数 NSLocalizedString 中的字符串参数

String parameter in pluralized NSLocalizedString

我想用适当的复数形式翻译字符串 %@ there are up to %i sun hours

%@ 包含一天,%i 太阳时数。

这是我的 Localizable.stringsdict 文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>%@ there are up to %i sun hours</key>
        <dict>
            <key>NSStringLocalizedFormatKey</key>
            <string>%#@hours@</string>
            <key>hours</key>
            <dict>
                <key>NSStringFormatSpecTypeKey</key>
                <string>NSStringPluralRuleType</string>
                <key>NSStringFormatValueTypeKey</key>
                <string>i</string>
                <key>zero</key>
                <string>%@ there are no sun hours</string>
                <key>one</key>
                <string>There is one sun hour %@ </string>
                <key>other</key>
                <string>%@ there are up to %i sun hours</string>
            </dict>
        </dict>
    </dict>
</plist>

我是这样称呼它的:

[NSString stringWithFormat:NSLocalizedString(@"%@ there are up to %i sun hours", nil), dayString, sunHours];

无论我将什么 NSInteger 作为第二个参数传入,翻译将始终使用 "other" 模板。我将它缩小到 NSStringLocalizedFormatKey 是错误的,当我使用像 %@ %#@hours@ 这样的键时它正在工作。

不过,我希望字符串参数成为本地化的一部分。有办法吗?

根据我所做的实验,您需要更改参数的顺序。好像只有第一个可以作为替换规则的控制值。

这本词典

<key>%i hours %@</key>
<dict>
    <key>NSStringLocalizedFormatKey</key>
    <string>%#@hours@</string>
    <key>hours</key>
    <dict>
        <key>NSStringFormatSpecTypeKey</key>
        <string>NSStringPluralRuleType</string>
        <key>NSStringFormatValueTypeKey</key>
        <string>i</string>
        <key>zero</key>
        <string>%2$@ there are no sun hours</string>
        <key>one</key>
        <string>There is one sun hour %2$@ </string>
        <key>other</key>
        <string>%2$@ there are up to %1$d sun hours</string>
    </dict>
</dict>

结合

[NSString stringWithFormat:NSLocalizedString(@"%i hours %@", nil), sunHours, dayString];

产生了我预期的结果。请注意,我已将参数索引添加到替换字符串中,以便正确放置值。

看起来文档对此功能的描述过于雄心勃勃。例如,给出的示例 in the document titled "OS X 10.9 Release Notes"(尽管 link 用于 iOS)意味着您应该能够打开第二个参数:

We're allowing the recursive formatting by applying the entire argument list to each substituted format specifier.

@"%d in %d files are selected" = @"%2$#@d_in_d_files_are_selected@"

The configuration dictionary can contain

"d_in_d_files_are_selected" = {

    "NSStringFormatSpecTypeKey" = "NSStringPluralRuleType"; // plural type
    "NSStringFormatValueTypeKey" = "d"; // int argument

    "zero" = "There is no file";
    "one" = "There is a file, and %1$#@it_is_selected@";
    "other" = "%1$d in %2$d files are selected"; 
};

但是根据那里的指南构建字典并没有给出规定的结果。 (请注意紧接着给出的示例 XML 与该词典不匹配。)

可能是我误读了某些内容(或者可能是错误),但我无法弄清楚到底发生了什么。现在,我将其保留为 "changing the argument order will fix your problem".

从 macOS SDK 10.12/Xcode 8.3 开始,这仍然是一个问题,NSStringLocalizedFormatKey 忽略参数编号并使用第一个参数来确定 d_in_d_files_are_selected 的复数。但。复数规则格式中的嵌套编号引用确实有效,例如"one" = "There is one file, and %2$#@it_is_selected@"; 将正确使用 select 复数规则的第二个参数。这意味着如果您使用单个 other 规则创建格式代理,则无需重新排列格式字符串即可实现您想要的行为:

<key>%@ there are up to %i sun hours</key>
<dict>
    <key>NSStringLocalizedFormatKey</key>
    <string>%#@proxy@</string>
    <key>proxy</key>
    <dict>
        <key>NSStringFormatSpecTypeKey</key>
        <string>NSStringPluralRuleType</string>
        <key>NSStringFormatValueTypeKey</key>
        <string>i</string>
        <key>other</key>
        <string>%2$#@hours@</string>
    </dict>
    <key>hours</key>
    <dict>
        <key>NSStringFormatSpecTypeKey</key>
        <string>NSStringPluralRuleType</string>
        <key>NSStringFormatValueTypeKey</key>
        <string>i</string>
        <key>zero</key>
        <string>%1$@ there are no sun hours</string>
        <key>one</key>
        <string>There is one sun hour %1$@ </string>
        <key>other</key>
        <string>%1$@ there are up to %2$i sun hours</string>
    </dict>
</dict>

请注意,要使其正常工作,您必须在最终格式字符串 ("%1$@ there are up to %2$i sun hours") 中明确指定参数编号。

在解决了我需要基于第二个 argument/variable 进行复数化的问题之后,我还找到了解决您问题的方法,而无需更改参数的顺序或使用代理规则链。感谢其他发帖者的回答和解决方案,让我进行了实验并更好地理解了 .stringsdict

我认为我的情况与您的情况接近,但更容易一些,并且可以帮助您更好地理解如何应用字符串格式和规则,所以让我从我的示例开始展示。

我的案例

我需要构造一个字符串,如 1 of 1 item selected1 of 5 items selected:

let countWithSelectionFormat = NSLocalizedString("%ld of %ld item(s) selected", comment: "Number of items with selection")
let countString = String.localizedStringWithFormat(countWithSelectionFormat, selectedCount, totalCount)

字符串格式%ld of %ld item(s) selected在这里只是一个占位符,一个别名。它在 Localizable.strings 中被忽略,因为它首先在 Localizable.stringsdict 中找到。它仅用作Localizable.stringsdict中规则的键,但实际上并不用于构造字符串:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>%ld of %ld item(s) selected</key>
    <dict>
        <key>NSStringLocalizedFormatKey</key>
        <string>%ld of %#@totalItems@ selected</string>
        <key>totalItems</key>
        <dict>
            <key>NSStringFormatSpecTypeKey</key>
            <string>NSStringPluralRuleType</string>
            <key>NSStringFormatValueTypeKey</key>
            <string>ld</string>
            <key>one</key>
            <string>%ld item</string>
            <key>other</key>
            <string>%ld items</string>
        </dict>
    </dict>
</dict>
</plist>

实际的字符串格式在NSStringLocalizedFormatKey中列出,是%ld of %#@totalItems@ selected。这里第一个参数 selectedCount 是通过使用 %ld 传递的(没有使其成为变量并为其列出规则)。第二个参数 totalCount%#@totalItems@ 变量及其规则解析 return 1 item 或例如 5 items,从而构造正确的字符串。

如果您需要更改某些语言输出中参数的顺序,您可以使用 %2$#@totalItems@, %1$ld selected 作为 NSStringLocalizedFormatKey

如果需要,您还可以引入第二个变量(和规则):%2$#@totalItems@, %1$#@selectedItems@.


你的情况

我的 Swift 代码与 Objective-C 中的基本相同:

let stringFormat = NSLocalizedString("On %@ there are up to %ld sun hours", comment: "")
let string = String.localizedStringWithFormat(stringFormat, dayString, sunHours)

Localizable.stringsdict:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>On %@ there are up to %ld sun hours</key>
    <dict>
        <key>NSStringLocalizedFormatKey</key>
        <string>%#@day@%#@hours@</string>
        <key>day</key>
        <dict>
            <key>NSStringFormatSpecTypeKey</key>
            <string>NSStringPluralRuleType</string>
            <key>NSStringFormatValueTypeKey</key>
            <string>d</string>
            <key>other</key>
            <string></string>
        </dict>
        <key>hours</key>
        <dict>
            <key>NSStringFormatSpecTypeKey</key>
            <string>NSStringPluralRuleType</string>
            <key>NSStringFormatValueTypeKey</key>
            <string>ld</string>
            <key>zero</key>
            <string>On %1$@ there are no sun hours</string>
            <key>one</key>
            <string>There is one sun hour on %1$@</string>
            <key>other</key>
            <string>On %1$@ there are up to %2$ld sun hours</string>
        </dict>
    </dict>
</dict>
</plist>

NSStringLocalizedFormatKey 中我使用了 2 个变量:%#@day@%#@hours@.

day变量和规则用于'consume'第一个参数dayString,我们这里不需要任何输出(固定在句首)。我在这里使用了格式值类型 d(如 %d 中的整数),因为我们不需要并且我们无法解析字符串来选择适用的复数规则。我们只使用必需的 other 规则,其中 return 是一个空字符串。出于同样的原因,字符串格式中的 dayhours 变量之间没有 space。

hours 变量捕获第二个参数 sunHours 并且它的规则负责构造实际的输出字符串(在这种情况下是整个句子)。因为我必须从第二个变量的规则中引用两个参数,所以我使用 %1$@%2$ld 分别引用 dayStringsunHours 参数。因此,您还可以按任意组合和顺序使用变量。

这给出了预期的结果:

String.localizedStringWithFormat(stringFormat, dayString, 0) // On Monday there are no sun hours
String.localizedStringWithFormat(stringFormat, dayString, 1) // There is one sun hour on Monday
String.localizedStringWithFormat(stringFormat, dayString, 5) // On Monday there are up to 5 sun hours

参考文献:

两个示例均使用 Swift 5、Xcode 10.2.1、macOS 10.14.5、目标 macOS 10.12 进行了测试。