liamnichols / xcstrings-tool

A plugin to generate Swift constants for your Strings Catalogs.

Home Page:https://swiftpackageindex.com/liamnichols/xcstrings-tool/documentation/documentation

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Decoding error with substitutions

HarshilShah opened this issue · comments

In my app I have some scenarios where I need to pluralise a string that needs to display not a number directly but a formatted value for it. I'm using substitutions for this, which seems to work just fine in Xcode by itself, but these don't seem to be parsed correctly by XCStrings-tool, and I get an error reading:

Decoding error at ‘strings → Days %lf %@ → localizations → en → substitutions → count‘ - The data couldn’t be read because it is missing.

Here's the raw source for one of the keys that's causing this issue:

"Steps %lf %@" : {
   "localizations" : {
      "en" : {
         "stringUnit" : {
            "state" : "translated",
            "value" : "%#@count@"
         },
         "substitutions" : {
            "count" : {
               "formatSpecifier" : "lf",
               "variations" : {
                  "plural" : {
                     "one" : {
                        "stringUnit" : {
                           "state" : "translated",
                           "value" : "%2$@ step"
                        }
                     },
                     "other" : {
                        "stringUnit" : {
                           "state" : "translated",
                           "value" : "%2$@ steps"
                        }
                     }
                  }
               }
            }
         }
      }
   }
}

Thanks for raising! This seems like the same as #26 where I was expecting argNum to always be present in the substitution object.

I was using this value to help repurpose the substation names (count in your example) as argument labels, but I then came across a few other bugs related to using positional specifiers that sidetracked me a bit. It turns out that it’s a bit more difficult to figure out the correct argument order as it’s not clear why argNum exists vs the traditional %2$@ specifier format.

Anyway… A workaround for now is to manually add argNum to the count object where the value is an integer matching the argument position in the overall string (a 1-based index).

Hopefully I’ll have an actual fix this month though once I’m fully back into Xcode 😃

I’m also curious about how this JSON was formed.. was it from a migration?

There are a few other strange pieces that I’ve not seen before.. the formatSpecifier value is lf despite the underlying values being %2$@… I have a vague memory that the tool is looking for %arg instead.

You can compare to the snapshot of what I made Xcode produce for the test suite:

{
"sourceLanguage" : "en",
"strings" : {
"substitutions_example.string" : {
"comment" : "A string that uses substitutions as well as arguments",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@! There %#@total.strings@ and you have %#@remaining.strings@ remaining"
},
"substitutions" : {
"remaining.strings" : {
"argNum" : 3,
"formatSpecifier" : "lld",
"variations" : {
"plural" : {
"other" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "%arg"
}
}
}
}
},
"total.strings" : {
"argNum" : 2,
"formatSpecifier" : "lld",
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "is %arg string"
}
},
"other" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "are %arg strings"
}
}
}
}
}
}
}
}
}
},
"version" : "1.0"
}

Depending on how your JSON was formed though, I might need to figure out how to support it

The JSON here was manually written out, based on this answer to a similar question I came across on the dev forums: https://developer.apple.com/forums/thread/737329?answerId=764796022#764796022

It's a tricky problem to solve, it seems, where I want to pluralise the string based on the numeric argument, but I then don't want to use it at all in my localisation value, and instead want to use a second string argument (in this case, a formatted, number; I wish to render say 10k steps, and not 10,000 steps).

If I add an explicit argNum, Xcode's fails to compile that at all, with the error message:

String ‘Steps %lf %@‘ was corrupt: The argument at position 1 was not included in the source localization and it's type cannot be inferred.

Okay so I rearranged things a bit to be able to add the argNum there, but I'm still seeing the same error. I've rearranged things so that the first argument is only referenced in the substitution rules, nowhere else. Here's the full JSON now:

"Steps %lf %@" : {
  "extractionState" : "manual",
  "localizations" : {
    "en" : {
      "stringUnit" : {
        "state" : "translated",
        "value" : "%2$@ %#@unit@"
      },
      "substitutions" : {
        "unit" : {
          "argNum" : 1,
          "formatSpecifier" : "lf",
          "variations" : {
            "plural" : {
              "one" : {
                "stringUnit" : {
                  "state" : "translated",
                  "value" : "step"
                }
              },
              "other" : {
                "stringUnit" : {
                  "state" : "translated",
                  "value" : "steps"
                }
              }
            }
          }
        }
      }
    }
  }
}

The argument at position 1 was not included in the source localization and it's type cannot be inferred.

Hmm yeah, this is an edge case that doesn't have a solution with my current approach... Currently, the implementation parses the placeholders from a flattened format string such as the following:

"%@: There are %lld items remaining"

In the above example, we see %@ as a String argument and %lld as an Int argument.

In order to be able to figure out the argument types like ☝️ when it comes to substitutions, I flatten them myself... For example, if we had "%@: There are %#@items@ remaining" and then %lld items for the other substitution of items, i'd flatten this back into %@: There are %lld items remaining before parsing it.

In your case, one and other don't include the format specifier, which results in the error that you saw.

This is in theory fixable though as we have the information that we need in the formatSpecifier property.. It's just that it requires a bit of a change in how strings are parsed. I think that I need to make these kind of changes anyway to solve some of the other issues so hopefully there will be a way to account for this use case as well