Localización en Swift: String Catalog, LocalizedStringResource, plurales y formateo

Localizar una app significa adaptarla a los idiomas y convenciones de cada mercado. Xcode 15 introdujo el String Catalog (.xcstrings), un formato de fichero que unifica los antiguos .strings y .stringsdict en una interfaz visual y una representación JSON editable. Junto a LocalizedStringResource y los estilos de formateo de la librería estándar, Swift tiene hoy el mejor soporte de localización de su historia.

String Catalog: el nuevo formato de Xcode 15

Al crear un fichero Localizable.xcstrings en el proyecto, Xcode extrae automáticamente las cadenas al construir y permite gestionar traducciones con estado (new, needs review, translated) en una interfaz visual. El fichero es JSON editable por herramientas externas:

// En el código Swift, sin cambios respecto a antes:
Text("welcome.title")
// o
Text("Bienvenido a la app")

// Xcode 15 los detecta y los añade al catálogo automáticamente
// en el siguiente build.

String(localized:) y LocalizedStringResource

Para cadenas fuera de vistas SwiftUI (en ViewModels, modelos o capas de servicio), usa String(localized:):

// String localizada en runtime
let mensaje = String(localized: "error.red.titulo",
                     defaultValue: "Sin conexión",
                     bundle: .main,
                     comment: "Título cuando no hay red")

// Con tabla específica (fichero .xcstrings diferente de Localizable)
let boton = String(localized: "btn.confirmar",
                   table: "Botones",
                   defaultValue: "Confirmar")

LocalizedStringResource es un tipo de valor que representa una cadena localizable pero que no se resuelve hasta que se necesita. Es ideal para APIs que aceptan cadenas de distintas fuentes (incluyendo AppIntents):

struct AlertaError {
    let titulo: LocalizedStringResource
    let mensaje: LocalizedStringResource

    static let sinRed = AlertaError(
        titulo: "error.red.titulo",
        mensaje: "error.red.mensaje"
    )
}

// En la vista:
Alert(
    title: Text(alerta.titulo),
    message: Text(alerta.mensaje)
)

Plurales automáticos por idioma

El String Catalog gestiona plurales por idioma directamente en la interfaz de Xcode, sin necesidad de .stringsdict. Para cada cadena, se pueden definir variantes plural con las categorías CLDR del idioma (zero, one, two, few, many, other):

// La clave con placeholder contable
Text("(cuenta) tareas pendientes")
// Xcode genera automáticamente las variantes para español:
// one: "1 tarea pendiente"
// other: "(cuenta) tareas pendientes"

// Para idiomas con más categorías (árabe, ruso...),
// el catálogo añade tantas variantes como necesita el idioma.

El String Catalog también maneja variaciones por dispositivo (iPhone, iPad, Apple Watch) con la misma clave:

Text("tap.to.continue")
// iPhone: "Toca para continuar"
// iPad: "Haz clic para continuar"
// Apple Watch: "Continuar"

Formateo localizado: Date.FormatStyle y Number.FormatStyle

El sistema de formateo de Swift adapta fechas, números, monedas y listas al locale del dispositivo sin configuración adicional:

// Fechas
let fecha = Date()
Text(fecha, format: .dateTime.day().month(.wide).year())
// "27 de junio de 2026" en español
// "June 27, 2026" en inglés

Text(fecha, style: .relative)
// "hace 3 minutos" / "3 minutes ago"

// Números y monedas
let precio = 1234.56
Text(precio, format: .currency(code: "EUR"))
// "1.234,56 €" en español
// "€1,234.56" en inglés

// Porcentajes
Text(0.73, format: .percent)
// "73 %" en español

// Listas
let items = ["manzanas", "peras", "uvas"]
Text(ListFormatStyle().format(items))
// "manzanas, peras y uvas" en español
// "apples, pears, and grapes" en inglés

// Medidas
let distancia = Measurement(value: 5.2, unit: UnitLength.kilometers)
Text(distancia, format: .measurement(width: .wide))
// "5,2 kilómetros" en español

Interpolación en cadenas localizadas

Las cadenas localizadas en SwiftUI admiten interpolación directamente en los literales:

let usuario = "Ana"
let puntos = 1250

// SwiftUI localiza la cadena completa con el placeholder
Text("puntos.usuario (usuario) (puntos)")
// Clave: "puntos.usuario %@ %lld"
// Traducción ES: "%1$@ tiene %2$lld puntos"
// Traducción EN: "%1$@ has %2$lld points"
// Resultado: "Ana tiene 1.250 puntos"

Comprobar traducciones en el simulador

Durante el desarrollo, es útil activar la visualización de pseudo-localización para detectar cadenas no localizadas y comprobar que la interfaz aguanta textos largos:

// En Scheme ? Run ? Options:
// App Language ? Accented Pseudolanguage (detecta cadenas hardcodeadas)
// App Language ? Right-to-Left Pseudolanguage (prueba RTL)

// En código, forzar un locale específico para tests:
let locale = Locale(identifier: "ar")  // Árabe
let fecha = Date()
print(fecha.formatted(.dateTime.day().month().year().locale(locale)))
// "27 ????? 2026"

Resumen

El stack de localización moderno de Swift se apoya en tres pilares: el String Catalog de Xcode 15 que centraliza cadenas, plurales y variantes de dispositivo en un único fichero; String(localized:) y LocalizedStringResource para cadenas fuera de SwiftUI; y los FormatStyle de la librería estándar que adaptan fechas, números, monedas y listas al locale del usuario sin una sola línea de configuración. Una app bien localizada desde el principio es mucho más barata de mantener que una que intenta adaptarse a posteriori.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP