Traducción Over-the-air de apps en iOS con Phrase Strings

Si quieres asegurarte de que todos los usuarios de tu aplicación obtengan el texto correcto, la función de traducción Over-the-Air de Phrase puede ayudarte a publicar tus actualizaciones de traducción al instante.

A la hora de implementar nuevas traducciones en nuestra aplicación, nuestro equipo no necesita depender de nosotros, los desarrolladores, ni ocupar nuestro valioso tiempo. También podemos ahorrarnos el proceso de aprobación de la App Store al actualizar el contenido localizado de nuestra aplicación. Veamos cómo podemos aprovechar estos beneficios en nuestra app de iOS con la función de traducción Over-the-air en iOS de Phrase. Agregaremos traducción OTA en IOS con Phrase a una aplicación en funcionamiento, conectando todo en la consola de Phrase y nuestro proyecto de XCode.

Nuestra aplicación

Continuamos donde lo dejamos en nuestro artículo anterior, iOS App Localization with Phrase. En ese tutorial, tomamos una aplicación y la conectamos a Phrase para optimizar nuestro flujo de trabajo de localización. Asumimos que has leído ese artículo o que ya tienes Phrase conectado a tu app. Si no, te recomendamos mucho que revises ese artículo antes de continuar.

Aplicación de demostración | PhraseShort Circuit, nuestra aplicación de demostración, una lista seleccionada de música electrónica

Nuestra pequeña aplicación de demostración es una lista de canciones de música electrónica junto con sus artistas y fechas de lanzamiento. Es una aplicación sencilla de dos pantallas con un UITableViewController que muestra todas nuestras pistas y tiene una pantalla de detalles para mostrar la información de cada una.

🔗 Recurso » Si quieres seguir el proceso, puedes descargar el proyecto de inicio de Github. Tienes un enlace al proyecto completo en la parte inferior del artículo.

Preparación

Vale, vamos a preparar nuestra app para la traducción Over-the-air en iOS. En nuestra aplicación iOS, moveremos nuestro texto localizado de los storyboards a nuestras conocidas y fiables Localizable.strings. Luego agregaremos soporte para traducción OTA en iOS a nuestro proyecto de Phrase. Para completar nuestra preparación, instalaremos el SDK de Phrase para iOS, que sincroniza las traducciones de nuestra aplicación con nuestro proyecto de traducción Over-the-air en iOS con Phrase.

Cómo mover las traducciones de nuestra aplicación de Storyboards a Localizable.strings

La traducción Over-the-air en iOS con Phrase funcionará perfectamente con los archivos .strings de storyboard. Sin embargo, para no complicar demasiado esta explicación, pongamos todas nuestras cadenas que se pueden traducir en un solo archivo Localizable.strings. Es una refactorización simple de Main.strings a Localizable.strings, así que manos a la obra.

📖 Profundiza más » Si quieres saber más sobre la localización de storyboard en iOS, consulta nuestra guía de internacionalización de iOS: Internacionalización de Storyboards en Xcode

Comencemos con los títulos de los elementos de navegación de nuestra pantalla. Podemos simplemente sobrescribir los títulos establecidos en nuestros storyboards configurando el campo title en nuestros controladores de vista. Nuestra pantalla de inicio, con un listado de nuestros artistas, está conectada a TrackListViewController. Actualicemos ese controlador ahora.

class TrackListViewController: UIViewController {
   // ...
    override func viewDidLoad() {
        super.viewDidLoad()
        title = NSLocalizedString("trackListTitle", comment: "")
        // ...
    }
    // ...
}

Usamos el habitual NSLocalizedString para obtener nuestra traducción de Localizable.strings. Vamos a asegurarnos de que la cadena de traducción exista en la versión en inglés de Localizable.strings.

"copyright" = "Copyright © %@ %@. All rights reserved";
"trackListTitle" = "Short Circuit";

Con esto, nuestra aplicación se verá exactamente igual cuando la ejecutes en inglés. Agregaremos nuestros idiomas que no son en inglés un poco más tarde. Primero, terminemos de mover nuestras traducciones fuera de nuestros storyboards.
Para nuestras etiquetas, tenemos que asegurarnos de que tengamos conexiones desde nuestros storyboards a sus respectivos controladores.

Después de conectar nuestras etiquetas, simplemente repetimos el mismo proceso de llamar a NSLocalizedString para proporcionarles cadenas traducidas. Y, por supuesto, agregamos nuestras nuevas cadenas a nuestro archivo Localizable.strings en inglés.
Nuestro TrackDetailsViewController, que ayuda a mostrar la información de una sola pista, se vería así después de mover nuestras traducciones de storyboard:

import UIKit
class TrackDetailsViewController: UIViewController {
    @IBOutlet weak var trackNameHeaderLabel: UILabel!
    @IBOutlet weak var artistNameHeaderLabel: UILabel!
    // ...
    var track: Track?
    override func viewDidLoad() {
        super.viewDidLoad()
        title = NSLocalizedString("trackDetailsTitle", comment: "")
        if let track = track {
            trackNameHeaderLabel.text =
                getLocalizedHeaderText(key: "trackNameHeader")
            artistNameHeaderLabel.text =
                getLocalizedHeaderText(key: "artistNameHeader")
            releaseDateHeaderLabel.text =
                getLocalizedHeaderText(key: "releaseDateHeader")
            // ...
        }
    }
    // ...
    fileprivate func getLocalizedHeaderText(key: String) -> String {
        return NSLocalizedString(key, comment: "")
            .localizedUppercase
    }
}

La función getLocalizedHeaderText es solo una pequeña función auxiliar que usamos para proporcionar un formato en mayúsculas adaptado al idioma a nuestro texto de etiqueta traducido.
Después de agregar nuestras cadenas de traducción a nuestras Localizable.strings, nuestra aplicación tiene exactamente el mismo aspecto que antes.

Aplicación con refactorización exitosa | PhraseParece que la refactorización ha funcionado

Ahora podemos eliminar completamente nuestros archivos de traducción de los storyboards de nuestro proyecto de Xcode. Lo hacemos seleccionando el archivo de storyboard en el navegador de proyectos y desmarcando cada localización bajo el encabezado de localizaciones en el inspector de archivos.

📖 Profundiza más » Si trabajas mucho con storyboards de iOS, puede que te interese nuestro artículo sobre automatizar la localización de storyboards de iOS.

Actualizando nuestras traducciones con Phrase

Ahora necesitamos actualizar las traducciones de Localizable.strings para los idiomas que no son el idioma fuente (que no sean el inglés, en mi caso). Hacemos esto para que los usuarios que no hablan inglés puedan ver nuestras etiquetas de actualización en su idioma. Podemos, por supuesto, usar nuestro flujo de trabajo habitual de Phrase para traducir nuestras etiquetas.
Primero, subiremos nuestro nuevo archivo fuente haciendo un $ phrase push desde la línea de comandos.
Nuestras nuevas claves de traducción ya deberían aparecer en la consola web de Phrase. Una vez que nuestros traductores hayan traducido las claves a todos los idiomas que admite nuestra aplicación, podemos hacer $ phrase pull desde la línea de comandos para obtener nuestros archivos Localizable.strings actualizados. Ahora, cuando ejecutamos nuestra aplicación en un idioma que no es el original (árabe en mi caso), vemos que nuestras vistas están traducidas como se esperaba.

Aplicación de demostración traducida | PhraseTodo bien en todos los idiomas

📖 Profundiza más » Puedes aprender más sobre el flujo de trabajo de traducción de Phrase con iOS en nuestro artículo sobre localización de aplicaciones iOS con Phrase.

Ahora que hemos movido nuestras cadenas de traducción de storyboard a archivos Localizable.strings, podemos pasar a configurar traducciones OTA en iOS a nuestro proyecto de Phrase.

Cómo añadir una distribución de traducción Over-the-air en iOS en Phrase

Vamos a dejar nuestro proyecto de Phrase listo para la traducción Over-the-air en iOS. En nuestra consola web de Phrase, vamos a abrir el menú desplegable de nuestra organización en la barra de navegación superior y seleccionar Integraciones.

Abriendo el menú de Integraciones en Phrase | Phrase OTA se encuentra en Integraciones

Esto abrirá nuestra página de Integraciones de Phrase. Aquí podemos buscar la fila Over-the-Air (OTA) y hacer clic en su botón de configuración.

Configurando OTA | PhraseEl creciente número de integraciones de Phrase

Ahora deberíamos ver la página Over-the-air, que nos invita a crear una distribución. Una distribución es una configuración OTA de un proyecto que tiene como objetivo una o más plataformas y cuenta con opciones de respaldo.

Creando una distribución para la aplicación de demostración | PhrasePodemos crear una distribución OTA específica para nuestra aplicación iOS

Haz clic en el botón Crear distribución para abrir el diálogo Agregar distribución.

Opciones de distribución de la aplicación | PhraseLas opciones para añadir distribución son bastante autoexplicativas

Para nuestro proyecto actual, podemos darle a la distribución un nombre que elijamos, seleccionar el proyecto de Phrase asociado con nuestra app de iOS y marcar la casilla de la plataforma iOS. Podemos dejar las opciones de respaldo predeterminadas tal como están, ya que se ajustan a nuestras necesidades. Haz clic en el botón Save para crear la distribución.

💡Nota » Puedes cambiar la configuración de tu distribución en cualquier momento volviendo a la página Over-the-air.

Agrega tu primera versión OTA

Para trabajar con el SDK de iOS sin ver un error cuando intentamos actualizar nuestras traducciones en la aplicación, necesitamos un lanzamiento de OTA. Una versión es básicamente una instantánea de nuestras traducciones de Phrase que podemos probar y desplegar en nuestra aplicación de traducción Over-the-air en iOS. Más adelante, hablaremos de los lanzamientos. Por ahora, crea una primera versión para empezar a trabajar con el SDK.
Empezaremos haciendo clic en el botón Crear lanzamiento en la parte inferior de nuestra página de distribución.

Botón de crear lanzamiento | Phrase
Lánzame

Esto abre el diálogo para añadir el lanzamiento. Podemos añadir una descripción para ayudarnos a identificar el lanzamiento más tarde; por ahora deja todo lo demás como está y haz clic en Guardar.

Menú de añadir lanzamiento | PhraseUn lanzamiento es una instantánea de traducción que podemos desplegar

Ya deberías ver una entrada en la sección de lanzamientos cerca de la parte inferior de nuestra página de distribución.
Ahora que tenemos una distribución OTA y un primer lanzamiento, podemos configurar OTA en nuestra aplicación de iOS.

Instalando el SDK de Phrase para iOS

Necesitarás tener el SDK de Phrase instalado para usar OTA. Podemos hacerlo a través de CocoaPods, Carthage, o manualmente.
Para instalar el SDK a través de CocoaPods, necesitamos tener CocoaPods instalado en nuestro Mac. Puedes ver las instrucciones de instalación en el sitio web de CocoaPods. Suponiendo que tenemos CocoaPods instalado, podemos navegar a nuestro directorio raíz del proyecto (el que contiene el archivo .xcodeproj) y ejecutar el siguiente comando en la terminal.

$ pod init

Esto creará un Podfile en nuestro directorio raíz. Abramos este archivo en nuestro editor de código favorito y agreguemos una línea para que se vea así:

target 'phraseapp-demo' do
  use_frameworks!
  # Pods for phraseapp-demo
  pod 'PhraseSDK'
end

Con esa edición guardada, podemos ejecutar lo siguiente desde nuestra terminal:

$ pod install

Si todo salió bien, deberíamos haber recibido el mensaje de que se ha instalado Phrase. Después de eso, podemos cerrar cualquier ventana de XCode que tengamos abierta y abrir el archivo .xcworkspace que CocoaPods agregó a la raíz de nuestro proyecto después de instalar Phrase.

Configurando Phrase en nuestra aplicación iOS

Con el SDK instalado, vamos a conectarlo a nuestra app. Primero, echemos un vistazo rápido a dos métodos principales que expone el SDK de Phrase.
Phrase.shared.setup(distributionID:environmentSecret:timeout:) inicializa el objeto singleton compartido de Phrase, utilizando nuestras credenciales y un parámetro de tiempo de espera opcional.
Phrase.shared.updateTranslations(translationResult:) actualiza manualmente las traducciones en la aplicación, obteniendo nuevas desde los servidores de Phrase si existe un nuevo lanzamiento disponible. updateTranslations toma un cierre de escape opcional, translationResult, que podemos proporcionar si queremos actuar cuando se complete la solicitud de actualización.

Ok, agreguemos una clase OTATranslations que ofrece una capa ligera alrededor de PhraseApp.

import PhraseSDK
class OTATranslations {
    static let shared = OTATranslations()
    private init() {
        #if DEBUG
        Phrase.shared.debugMode = true
        #endif
        let config: PList? = loadConfig()
        if let config = config {
            #if DEBUG
            let environmentTokenKey: String = "devToken"
            #else
            let environmentTokenKey: String = "prodToken"
            #endif
            Phrase.shared.setup(
                distributionID: config.getValue(withKey: "distributionID"),
                environmentSecret: config.getValue(withKey: environmentTokenKey),
                timeout: config.getValue(withKey: "timeout")
            )
        }
    }
    func updateTranslations(onUpdateComplete: (() -> Void)? = nil) {
        do {
            try Phrase.shared.updateTranslations { result in
                switch result {
                case .success(let updated):
                    if updated {
                        printIfDebug("Translations updated successfully")
                        onUpdateComplete?()
                    } else {
                        printIfDebug("No new translations found")
                    }
                case .failure:
                    printIfDebug("Failure updating translations")
                }
            }
        } catch {
            printIfDebug("Error updating translations: \(error)")
        }
    }
    fileprivate func loadConfig() -> PList? {
        do {
            return try PList(pListResource: "PhraseApp")
        } catch {
            printIfDebug(
                "Error loading PhraseApp configuration from plist, \(error)")
        }
        return nil
    }
}

💡Dato » Phrase.shared.debugMode es un indicador que hace que el SDK de Phrase sea más explícito, mostrando más detalles en la consola cuando está activado. Lo activamos cuando estamos ejecutando una compilación de depuración de nuestra aplicación.
💡 FYI » La función printIfDebug(_:) es un ayudante personalizado que simplemente muestra en la consola la cadena que se le pasa si el indicador #DEBUG está activado.

Proporcionamos un objeto singleton simple, OTATranslations.shared, para usar en nuestra aplicación. Nuestro objeto se inicializa extrayendo valores de configuración de Phrase de una PhraseApp.plist y proporcionándolos a PhraseApp.setup. Para que esto funcione, necesitamos crear la clase PhraseApp.plist en nuestro proyecto de XCode. Hagámoslo ahora. Debería tener un aspecto similar al siguiente.

Claves | PhraseAsegúrate de que tus claves coincidan con las de aquí

Aquí hay una versión de texto si quieres ⌘-C.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>distributionID</key>
    <string>your_distribution_id</string>
    <key>devToken</key>
    <string>your_distribution_development_secret</string>
    <key>prodToken</key>
    <string>your_distribution_production_secret</string>
    <key>timeout</key>
    <integer>10</integer>
</dict>
</plist>

✋🏽 Atención » Quizá quieras agregar el archivo PhraseApp.plist a tu .gitignore para proteger tus claves secretas.
💡Para tu información » La clase PList que usamos para cargar los valores de nuestro archivo .plist es una clase auxiliar personalizada.

Los valores de nuestro distributionID, devToken y prodToken se pueden encontrar en la página de distribución OTA en la consola web de Phrase. Podemos navegar a la pestaña de Resumen del tablero de control de nuestro proyecto y hacer clic en el botón Ver distribuciones en la sección Over-the-air. Esto abre la página Over-the-air, y desde allí podemos encontrar y hacer clic en la distribución OTA de nuestro proyecto en la lista Distribuciones. Una vez en la página de la distribución específica, podemos encontrar los valores de ID de distribución, Secreto de desarrollo y Secreto de producción para copiarlos y pegarlos en nuestro archivo .plist. Por supuesto, estamos usando «token» en lugar de «secret» en nuestra .plist, pero probablemente ya te habrás dado cuenta.

Copiar ID y tokens secretos | PhraseCopiar y pegar tu ID y tus tokens secretos

Ok, ahora que tenemos el SDK de Phrase conectado a nuestra clase OTATranslations, hagamos uso de él en nuestro AppDelegate.swift.

import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        OTATranslations.shared.updateTranslations()
        return true
    }
}

En el método AppDelegate de application(_:didFinishLaunchingWithOptions:), que se ejecuta cuando nuestra aplicación se inicia por primera vez, actualizamos manualmente nuestras traducciones de forma remota. Esto incorpora nuevas traducciones en segundo plano. Sin embargo, nuestra configuración actual no hará que la interfaz de usuario se actualice de inmediato. Las actualizaciones de traducción que se obtienen durante este lanzamiento de la aplicación aparecerán para el usuario durante su próximo lanzamiento de la aplicación. Este es a menudo el comportamiento que queremos: que nuevas cadenas de traducción aparezcan de repente mientras el usuario está en medio de hacer algo en la aplicación podría ser una experiencia de usuario extraña y potencialmente inquietante.

Actualizar manualmente la interfaz de usuario después de importar nuevas traducciones

Sin embargo, dependiendo de nuestras necesidades, puede que necesitemos actualizar la interfaz de usuario de la app inmediatamente después de recibir una actualización de traducción. Podemos hacer eso utilizando el parámetro de cierre de callback que proporcionamos en updateTranslations(onUpdateComplete:).

import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        OTATranslations.shared.updateTranslations() {
            AppDelegate.reloadRootViewController()
        }
        return true
    }
    static func reloadRootViewController() {
        DispatchQueue.main.async {
            let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
            let appDelegate = UIApplication.shared.delegate as! AppDelegate
            appDelegate.window?.rootViewController =
                storyboard.instantiateInitialViewController()
        }

Aquí proporcionamos el argumento onUpdateComplete a updateTranslations como un cierre de escape. Este closure se llamará cuando haya nuevas traducciones que se hayan obtenido con éxito. Cuando esto sucede, llamamos a nuestro método personalizado AppDelegate.reloadViewController(). Esto recargará nuestra aplicación y mostrará al usuario nuestras traducciones más recientes.

✋🏽 Aviso » Ten cuidado al recargar el controlador de vista raíz de la aplicación. Si el usuario ya ha comenzado a hacer algo en tu aplicación, puede que ese flujo se interrumpa – y sus datos no guardados se pierdan – cuando ocurra la recarga. Para reducir las posibilidades de que esto suceda, es posible que desees usar una configuración de timeout relativamente baja al configurar el SDK de PhraseApp. De esa manera, si la solicitud de traducción tarda un tiempo, simplemente se cancelará y no activará la recarga. Alternativamente, y de forma más robusta, puedes hacer que aparezca un indicador de carga bloqueante durante la solicitud. Esto impediría que el usuario interactúe por completo con tu aplicación durante la carga de traducción, evitando la pérdida de datos mencionada anteriormente.

La libertad de la traducción Over-the-air en iOS con Phrase

Ya casi hemos terminado de integrar las traducciones OTA en nuestra aplicación. Después de publicar esta versión de nuestra aplicación en la App Store, podemos enviar traducciones a nuestros usuarios sin pasar por la aprobación de la App Store de nuevo. Todo lo que tenemos que hacer es ir a nuestra consola web de Phrase y

  1. Actualiza nuestras traducciones de localización y luego
  2. crea un nuevo lanzamiento para nuestra distribución de OTA (igual que hicimos antes), y
  3. publica el lanzamiento.

Publicando el lanzamiento | Phrase

No olvides publicar tu lanzamiento después de revisarlo

De verdad que es así de simple. Una vez que publiquemos nuestra versión en la consola web de Phrase, nuestros usuarios comenzarán a recibir las traducciones actualizadas. Todos los usuarios que tengan la versión de la aplicación conectada al SDK de PhraseApp descargarán las nuevas traducciones Over-the-air. No necesitas publicar una nueva versión de tu aplicación, ni esperar la aprobación del App Store. Dulce libertad.

🔗 Recurso » Puedes ver y descargar el código completo del proyecto de iOS que construimos aquí desde nuestro repositorio de GitHub.

Más información

Si quieres profundizar en la internacionalización y localización de iOS, echa un vistazo a algunos de nuestros otros artículos sobre este tema.

¡Eso es todo!

Solo tardarás un par de días en conectar nuestra aplicación a las traducciones Over-the-air de Phrase. Esto puede ahorrarnos decenas de horas mientras publicamos nuevas traducciones directamente a nuestros usuarios de la aplicación. Con OTA también tenemos la ventaja añadida de saltarnos la aprobación de la App Store (solo para una actualización de texto). Y Phrase te ofrece mucho más que OTA: Phrase puede realmente agilizar el proceso de localización de tu aplicación. Echa un vistazo a todos los productos de Phrase y regístrate para una prueba gratuita de 14 días.
Espero que estés comenzando a ver el potencial de las traducciones OTA, y que hayas disfrutado de esta pequeña guía sobre cómo hacer que OTA funcione en tu aplicación iOS. Estaremos añadiendo más contenido OTA en los próximos días, así que mantente atento, ¡y feliz programación! :)

Publicaciones relacionadas

Blog post

Localizar juegos de Unity con el plugin oficial de Phrase

¿Quieres localizar tu juego de Unity sin el caos de CSV? Descubre cómo el plugin oficial de Phrase Strings para Unity simplifica el flujo de trabajo de localización de tu juego, desde la configuración de la tabla de cadenas hasta la obtención de traducciones directamente en tu proyecto. Ya estés desarrollando para alemán, serbio o cualquier otro idioma, esta guía muestra cómo empezar rápido y localizar como un experto.

Software localization blog category featured image | Phrase

Blog post

La guía definitiva para la localización de JavaScript

Pon en marcha la localización de JavaScript para tu navegador con esta guía completa y prepara tu aplicación para los usuarios internacionales.

Software localization blog category featured image | Phrase

Blog post

Guía definitiva para la localización Flutter

Vamos a descifrar los secretos de la localización Flutter para que puedas hablar el idioma de tus usuarios y seguir tu camino hacia la dominación global, línea de código tras línea de código.

Software localization blog category featured image | Phrase

Blog post

Usa el mejor plugin traductor de Phrase Strings para WordPress

Aprende a traducir páginas, publicaciones y más de WordPress a múltiples idiomas con la integración de Phrase Strings para WordPress.

Software localization blog category featured image | Phrase

Blog post

Detectar la localidad del usuario en una aplicación web

Uno de los problemas más comunes en el desarrollo de aplicaciones web es detectar la localidad del usuario. Esto es la manera correcta de hacerlo.