El error que se reportó dos veces, y la interpolación de cadenas que cambia en silencio el formato de un número
Este ya se había encontrado una vez, se había registrado, y quedó medio olvidado. Una revisión de capturas de pantalla hace unos días detectó un año de lanzamiento que se mostraba como “2.026” en lugar de “2026” en un dispositivo configurado con la región de Costa Rica, y lo registré como prioridad baja porque parecía cosmético y fácil de postergar. Una revisión de diseño esta semana encontró exactamente el mismo error de forma independiente y lo marcó como prioridad alta. Dos tickets, mismo archivo, misma línea, nadie los había conectado hasta que me senté a arreglar uno y reconocí la descripción.
El error en sí es una trampa pequeña y específica de Swift. El código construía un subtítulo como “Album · 2024” con String(localized: "\(type) · \(year)"), donde year es un Int simple. Eso parece completamente inofensivo. Pero String(localized:) con interpolación de cadenas no solo pega el número como texto. Pasa por String.LocalizationValue, que trata cualquier valor numérico interpolado como algo que debería recibir formato numérico según la configuración regional, el mismo mecanismo que convierte 1234567 en “1,234,567” para un precio o un conteo de población. Un año simple de 4 dígitos no es una cantidad, es un identificador, y en cualquier región que use un punto como separador de miles, ese formato “útil” convierte 2024 en 2.024.
La solución es de una línea: envolver el año en String(year) antes de que entre en la cadena interpolada, para que llegue como texto en lugar de número y se salte por completo ese paso de formato.
La parte que en verdad llevó un minuto entender: el String Catalog de Xcode indexa las cadenas interpoladas por la forma de su formato, no por el texto literal de origen. Antes de la corrección, la clave de este punto de llamada se veía como %@ · %lld, porque un argumento era un String y el otro un Int. Después de cambiar el año a String, el mismo punto de llamada produce una clave genuinamente distinta, %@ · %@, porque ahora los dos argumentos son Strings. Así que un cambio de tipo de una sola línea no fue solo una corrección en tiempo de ejecución, también significó que el catálogo de localización necesitaba una entrada nueva y una traducción nueva, y la clave vieja necesitaba limpieza en cuanto nada volviera a referenciarla. Antes de tocar nada, revisé si algún otro punto de llamada compartía cualquiera de las dos claves, porque estas claves con forma de formato pueden ser reutilizadas en silencio por código sin relación que interpola los mismos tipos de argumento.
La lección menor: cuando el mismo error se detecta dos veces, de forma independiente, desde ángulos distintos, suele ser una señal de que es más visible o más molesto de lo que sugería su etiqueta de prioridad original. Vale la pena una revisión rápida de duplicados antes de empezar a trabajar, no solo después.
Lecturas relacionadas
Cómo corregí la pluralización, y por qué casi elijo la API equivocada de Apple
La etiqueta visible estaba bien; la etiqueta de accesibilidad decía '1 tracks.' Y la ingeniosa API de inflexión habría publicado un error silencioso en portugués en el propio sistema operativo mínimo que soporta la app.
Traducir Deep Cut Atlas al español, y el bug de binario desactualizado que no era un bug de traducción
153 strings a es-419 a mano, veinte minutos depurando un bug que no existía, y tres botones que un grep mecánico encontró y un repaso manual se había pasado por alto.
Cómo localizar el aviso de permiso de Apple Music (y el desvío de pruebas en dispositivo que eso implicó)
Una sola cadena de texto, dos hilos de foro que se contradicen, un iPhone equivocado, y el descubrimiento de que desinstalar una app no reinicia su estado de permisos, solo un reinicio completo de privacidad lo hace.