Search code examples
iosswiftxcode

How to implement plurals in a String Catalog for labels that don't show numbers?


I am localizing my app and I need to handle text views that display pluralized nouns without showing a specific number. Eg:

VStack(alignment: .center, spacing: 0) {
      Text("\(stepperValue)")
      Text("\(item.actionCount, specifier: specifier) \(literal: actionNameVariable)")
 }

I am using a solution from this SO post for combining literals with formatted values to form a localization key:

I attempted to remove the number directly in the translation. However, when I have two languages, with English as the base language, removing the count from the English translation results in:

string catalogue screenshot

which I guess make sense, cause iOS doesn't know how to handle pluralization without an actual number.

If I leave it with a number in English, in all other languages I can remove the number in translation. However, this is not a complete solution because the base language still displays the number.

What would be a way to handle this correctly?


Solution

  • You can open up the xcstrings file as JSON using "Open As" -> "Source Code".

    enter image description here

    The JSON structure should be edited to be something like this:

    // I've put comments in this JSON to explain things, 
    // but you should remove them in your actual code or it will not compile
    {
      "sourceLanguage" : "en",
      "strings" : {
        // "%lld Foo" is the key, "Foo" is the thing you want to pluralise
        "%lld Foo" : {
          "extractionState" : "manual",
          "localizations" : {
            // insert other languages in this JSON object...
            "en" : {
              "stringUnit" : {
                "state" : "translated",
                "value" : "%#@arg1@"
              },
              "substitutions" : {
                "arg1" : {
                  "argNum" : 1,
                  "formatSpecifier" : "lld", // repeat the format specifier in the key here
                  "variations" : {
                    "plural" : {
                      "one" : {
                        "stringUnit" : {
                          "state" : "translated",
                          "value" : "Foo" // singular form of the word
                        }
                      },
                      "other" : {
                        "stringUnit" : {
                          "state" : "translated",
                          "value" : "Foos" // plural form of the word
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "version" : "1.0"
    }
    

    (Notice the similarity between this structure and the one in this question about doing the same thing but with stringdict)

    Then, Text("\(1) Foo") would output "Foo" and Text("\(2) Foo") would output "Foos".


    It is also possible to do this almost entirely in the string catalog editor.

    First, add a key that uses two format specifiers, such as %lld %lld Foo, where "Foo" is the thing you want to pluralise.

    enter image description here

    Next, use the "Vary by Plural" option. This gives you choice of which format specifier you want to vary by. Choose the first one.

    enter image description here

    It will now look like this:

    enter image description here

    The purple "@arg1" thing on the second row is why we inserted two format specifiers at first. I could not find a way to get Xcode to do that any other way, for some reason.

    Now we can delete the other fluff that we don't need. Instead of including "%arg" (which represents the number) in the translation, we just write the singular and plural forms of the word. The end result looks like this:

    enter image description here

    Notice that I change removed the second "%lld" from %lld %lld Foo. Doing this through Xcode will actually cause Xcode to crash (on version 15.3 at least), so you have to edit the JSON in the xcstrings file directly. But after that Xcode should work as normal.