Skip to content
Development

The bug that was filed twice, and the string interpolation that quietly changes a number's formatting

By Victor Da Luz
swiftiosi18ndev-logdeep-cut-atlas

This one had already been found once, filed, and half-forgotten. A screenshot sweep a few days ago caught a release year rendering as “2.026” instead of “2026” on a device set to Costa Rica’s region, and I filed it as low priority since it looked cosmetic and easy to defer. A design review this week found the exact same bug independently and flagged it as high priority. Two tickets, same file, same line, nobody had connected them until I sat down to fix one and recognized the description.

The bug itself is a small, specific Swift gotcha. The code was building a subtitle like “Album · 2024” with String(localized: "\(type) · \(year)"), where year is a plain Int. That looks completely harmless. But String(localized:) with string interpolation doesn’t just paste the number in as text. It routes through String.LocalizationValue, which treats any interpolated numeric value as something that should get locale-appropriate number formatting, the same mechanism that turns 1234567 into “1,234,567” for a price or a population count. A bare 4-digit year isn’t a quantity, it’s an identifier, and in any region that uses a period as its thousands separator, that “helpful” formatting turns 2024 into 2.024.

The fix is one line: wrap the year in String(year) before it goes into the interpolated string, so it arrives as text instead of a number and skips that formatting pass entirely.

The part that actually took a minute to understand: Xcode’s String Catalog keys interpolated strings by their format shape, not by their literal source text. Before the fix, this call site’s key looked like %@ · %lld, since one argument was a String and the other an Int. After changing the year to a String, the same call site produces a genuinely different key, %@ · %@, since now both arguments are Strings. So a one-line type change wasn’t just a runtime fix, it also meant the localization catalog needed a new entry and a new translation, and the old key needed cleanup once nothing referenced it anymore. I checked first whether any other call site shared either key before touching anything, since these format-shaped keys can be silently reused by unrelated code that happens to interpolate the same argument types.

The smaller lesson: when the same bug gets noticed twice, independently, from different angles, that’s usually a signal it’s more visible or more annoying than its original priority label suggested. Worth a quick duplicate check before starting work, not just after.

Related reading