Traducción de la app de iOS de forma inalámbrica con Phrase Strings

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

Nuestro equipo de la aplicación no necesita depender de nosotros, los desarrolladores (y ocupar nuestro precioso tiempo) al implementar nuevas traducciones en nuestra aplicación. También podemos evitar la 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 over-the-air de Phrase. Agregaremos Phrase Over the Air 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 y/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 curada de música electrónica

Nuestra pequeña aplicación de demostración lista pistas 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 una pista.

🔗 Recurso » Si quieres seguir el proceso, puedes descargar el proyecto de inicio de Github. El proyecto completado está vinculado cerca de la parte inferior del artículo.

Preparación

Vale, vamos a preparar nuestra app para traducciones inalámbricas. En nuestra aplicación iOS, moveremos nuestro texto localizado de los storyboards a Localizable.strings. Luego agregaremos soporte para traducción por aire 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 Phrase de forma inalámbrica.

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

Las traducciones de Phrase Over the Air funcionarán perfectamente con los archivos de Storyboard .strings. Sin embargo, para mantenerlo simple aquí, 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.

📖 Descubre más » Si quieres saber más sobre la localización de storyboard en iOS, consulta nuestra guía, iOS i18n: 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, que lista a 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 © %@ %@." Todos los derechos reservados.
"trackListTitle" = "Circuito Corto";

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 nuestro Localizable.strings, nuestra aplicación se ve exactamente igual que antes.

Aplicación con refactorización exitosa | PhraseParece una refactorización exitosa

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 Localizaciones en el Inspector de archivos.

📖 Descubre más » Si trabajas mucho con storyboards de iOS, puede que quieras consultar nuestro artículo, 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 (no 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 locales que admite nuestra aplicación, podemos $ phrase pull desde la línea de comandos para obtener nuestros archivos actualizados Localizable.strings. 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 locales

📖 Saber más » Puedes aprender más sobre el flujo de trabajo de traducción de Phrase con iOS en 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 nuestro proyecto de Phrase.

Añadir una distribución por aire en Phrase

Hagamos que nuestro proyecto de Phrase esté listo para traducciones inalámbricas. En nuestra consola web de Phrase, abramos el menú desplegable de nuestra organización en la barra de navegación superior y seleccionemos 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 encontrar la fila Over-the-Air (OTA) y hacer clic en su botón Configurar.

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 apunta a una o más plataformas y tiene 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 Create distribution para abrir el diálogo Add distribution.

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 Guardar para crear la distribución.

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

Agrega tu primer lanzamiento OTA

Para trabajar con el SDK de iOS sin ver un error cuando intentamos actualizar nuestras traducciones en la aplicación, necesitamos tener un lanzamiento OTA. Un lanzamiento es básicamente una instantánea de nuestras traducciones de Phrase que podemos probar y desplegar en nuestra aplicación de iOS mediante OTA. Más adelante, hablaremos de los lanzamientos. Por ahora, crea un primer lanzamiento 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 Añadir lanzamiento. Podemos añadir una descripción para ayudarnos a identificar el lanzamiento más tarde, dejar 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 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 una salida que indique que Phrase ha sido instalado. 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()
        si 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("Traducciones actualizadas correctamente")
                        onUpdateComplete?()
                    } else {
                        printIfDebug("No se han encontrado traducciones nuevas")
                    }
                case .failure:
                    printIfDebug("Error al actualizar las traducciones")
                }
            }
        } catch {
            printIfDebug("Error updating translations: \(error)")
        }
    }
    fileprivate func loadConfig() -> PList? {
        do {
            return try PList(pListResource: "PhraseApp")
        } catch {
            printIfDebug(
                "Error al cargar la configuración de Phrase desde plist, \(error)"
        }
        devolver nil
    }
}

💡FYI » 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 un 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 verse un poco como lo 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 Resumen del tablero 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 Distribution ID, Development secret y Production secret para copiarlos y pegarlos en nuestro archivo .plist. Por supuesto, estamos usando «token» en lugar de «secreto» en nuestro .plist, pero probablemente ya te habrás dado cuenta.

Copiando 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 las traducciones por aire de 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 una nueva versión para nuestra distribución OTA (igual que hicimos antes), y
  3. Publica la versión.

Publicando lanzamiento | Phrase

No olvides publicar tu lanzamiento después de revisarlo

Es realmente 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 PhraseApp SDK descargarán las nuevas traducciones por aire. 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!

Conectar nuestra aplicación a las traducciones por aire de Phrase lleva uno o dos días. 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

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 usuarios internacionales.

Software localization blog category featured image | Phrase

Blog post

La Guía Definitiva para la Localización de Flutter

Descodifiquemos los secretos de la localización de Flutter para que puedas hablar el idioma de tus usuarios y seguir codificando tu camino hacia la dominación global.

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

Detectando el idioma de un usuario en una aplicación web

Uno de los problemas más comunes en el desarrollo de aplicaciones web es detectar la configuración regional de un usuario. Así se hace de la manera correcta.