Gestão Dev & TIARTIGO

Um papo sobre Localizable e constants em Swift

veja uma forma de trabalhar com Localizable, que basicamente é a utilização de diversos idiomas no app.

Antes de tudo, importante dizer que este artigo foi criado a quatro mãos, ou duas cabeças (ocas… rsrsrs). Já que não dá para atribuir o texto para duas pessoas lá embaixo, queria deixar anotada a participação do Thiago Holanda (ou Trollanda, como é conhecido por alguns).

Este artigo foi oriundo de um momento comum que temos aqui na Concrete. Após criar uma feature interessante, corremos na mesa de amigos, mostramos e discutimos melhorias ou alterações. Vou tentar transcrever como foi a conversa sobre essa feature em especial.

Um belo dia, às 9:40, Holanda chegou à CS e… ok, ok, sem tanto detalhe. Estávamos mostrando alguns conceitos que vimos, e então o Holanda mostrou uma forma de trabalharmos com Localizable, que basicamente é a utilização de diversos idiomas no app. Trata-se de utilizar enums para abstrair a utilização (e repetição) de NSLocalizedString e organizar a estrutura da chamada das strings necessárias. A estrutura é bem simples, são apenas enums com chaves para cada string no documento (ex: pt-BR.lproj/Localizable.strings, en.proj/Localizable.strings ou qualquer outro idioma que se queira dar suporte).

Esses documentos utilizam o seguinte padrão:

// Common - Portuguese
 "common.change" = "Mudar";
 "common.error" = "Erro";
 "common.delete" = "Excluir";
 "common.wait" = "Aguarde";

// Common - English
 "common.change" = "Change";
 "common.error" = "Error";
 "common.delete" = "Delete";
 "common.wait" = "Wait";

A partir da localização configurada no iPhone, ele utilizará um desses idiomas (veja aqui mais informações sobre internacionalização de app).

Mas vocês falaram sobre usar enums, como é isso?

A ideia nasceu no Android e foi estendida a alguns frameworks em Swift, como SwiftGen, R.swift e outros. Aplicamos o mesmo conceito, como no exemplo abaixo:

enum Project {

    // Localizable Strings
    enum Localizable {
        
        // Common
        enum Common: String {
            case change     = "common.change"
            case error      = "common.error"
            case delete     = "common.delete"
            case wait       = "common.wait"
        }
    }
}

Está ficando mais claro. Mas como utilizamos?

É realmente bem simples usar essa estrutura no seu projeto. Em primeiro lugar, após configurar todos os valores nos enums, você ganha o poder do code completion, o que impede que você se engane na hora de usar a string desejada.

image01

Digamos que você vá configurar o valor em algum UILabel:

self.youLabel.text = NSLocalizedString(Project.Localizable.Common.wait.rawValue, comment: nil)

Essa estrutura é bem legal! Por que não usá-la para estruturar nossas constantes ou até mesmo para nossas strings de imagens? Boa ideia, vamos lá:

// MARK: - Project Constants
enum Project {
    
    // Image Names
    enum Images {
        enum User: String {
            case first  = "icon-first-image"
            case second = "icon-second-image"
        }
    }

    // Localizable Strings
    enum Localizable {
        
        // Common
        enum Common: String {
            case change     = "common.change"
            case error      = "common.error"
            case delete     = "common.delete"
            case wait       = "common.wait"
        }
    }
}

Muito bom! Agora, para usar essa string, basta:

self.photo.image = UIImage(named:Project.Images.User.first.rawValue)

É, assim está meio estranho. Será que podemos melhorar isso? Claro! Vamos lá.

Inicializar o NSLocalizedString, assim como UIImage, toda vez pode ser cansativo (e feio). Uma extensão pode ser útil para colocarmos isso em propriedades. Vamos lá:

extension RawRepresentable where RawValue == String {
    var image: UIImage? {
        return UIImage(named: rawValue)
    }

    var localized: String {
        return NSLocalizedString(rawValue, comment: "")
    }
}

Swift nos dá uma gama de protocolos que são implementados por alguns tipos. Por exemplo, o enum implementa o RawRepresentable, que nos permite usar a propriedade rawValue, que é o valor correspondente ao raw, aquele que atribuímos em cada case (exemplo: “common.change”). Além disso, garantimos que seu tipo é String usando where RawValue == String. Com isso, apenas enums que têm seu rawValue string poderão utilizar as variáveis image e localized.

Hmmmm… interessante! E a chamada?

self.photo.image = Project.Images.User.first.image
self.youLabel.text = Project.Localizable.Common.wait.localize

Opa, tá bem melhor! Mas, olhando bem, a chamada abaixo pode dar problema:

Project.Localizable.Common.wait.image

Sim, não existe essa imagem, a string se refere a um Localizable. Droga! O que podemos fazer?

image00

Programação Orientada a Protocolo FTW (nota do Erick: sou meio dislexo escrevendo, olhei 10 vezes para ter certeza de que não escrevi WTF… rsrsrsrs)!

Uma das grandes features do Swift 2.0 é permitir extensões de protocolo. Com isso, conseguimos atribuir um comportamento a nossos enums separadamente. Como fazer isso? Vamos começar com os protocolos:

// MARK: - Representable Protocols
protocol ImageRepresentable: RawRepresentable {
    var image: UIImage? { get }
}

protocol LocalizeRepresentable: RawRepresentable {
    var localized: String { get }
}

Adicionamos responsabilidades específicas para cada protocolo, eles implementam RawRepresentable e possuem propriedades que definem sua responsabilidade. Assim, podemos criar extensions desses protocolos e implementar nossas propriedades como no exemplo a seguir:

// MARK: - Representable Protocols Extensions
extension ImageRepresentable where RawValue == String {
    var image: UIImage? {
        return UIImage(named: rawValue)
    }
}

extension LocalizeRepresentable where RawValue == String {
    var localized: String {
        return NSLocalizedString(rawValue, comment: "")
    }
}

Nota 1: a documentação da Apple nos diz que caso uma imagem não possa ser encontrada quando informamos o “named” dela, o retorno será nil. Dessa forma, é necessário configurar a nossa propriedade como Optional (UIImage?) e testar com um If let na hora de chamar a propriedade image.

Nota 2: normalmente, vemos o uso do NSLocalizedString(_:comment? com as frases prontas previamente, como NSLocalizedString(“Olá Mundo”, comment: nil). Dessa forma, caso essa frase não seja encontrada nos arquivos Localizable.string, essa mesma frase é retornada. No caso deste artigo, incentivamos o uso dessa estrutura de “path” (“common.change”). Se por algum motivo você seguir a sugestão do artigo e usar essa forma de identificação das frases, não se esqueça de adicionar essa chave com a frase traduzida em todos os arquivos Localizable.string. Se isso não for feito, existe o risco de você encontrar em algum label, button ou qualquer outro elemento um texto parecido com: common.change.

Implementamos nossas variáveis nas extensões, agora basta aplicar nossos protocolos aos seus devidos enums:

// MARK: - Project Constants
enum Project {
    
    // Image Names
    enum Images {
        enum User: String, ImageRepresentable {
            case first  = "icon-first-image"
            case second = "icon-second-image"
        }
    }

    // Localizable Strings
    enum Localizable {
        
        // Common
        enum Common: String, LocalizeRepresentable {
            case change     = "common.change"
            case error      = "common.error"
            case delete     = "common.delete"
            case wait       = "common.wait"
        }
    }
}

E voilà!

image02

Adicionamos comportamentos distintos para cada enum. User possui a propriedade image e não possui localized, e Common possui localized mas não image.

Você ainda pode perguntar: “mas por que diabos isso serve pra mim ?”

Então… Você pode minimizar e/ou dificultar que erros básicos possam ocorrer, como digitar errado o nome de uma imagem ou localizável ou mudar a string apenas em um lugar e não por todo o código, o que pode prevenir dores de cabeça desnecessárias.

Bem, é isso! Temos certeza de que ainda podemos melhorar de outras formas, até porque nunca está legal o suficiente. E por que não ajudar a gente a melhorar? Deixe comentários e dicas, vamos fazer algo mais legal! =)

Veja o exemplo completo aqui.

***

Artigo publicado originalmente em http://www.concretesolutions.com.br/2016/09/26/localizable-e-constantes-em-swift/.

Desenvolvedor iOS na Concrete Solutions, estudou Análise e Desenvolvimento de Sistemas no Infnet (RJ). Já tentou desenvolver em Java, mas foi pelo iOS que se apaixonou de verdade. Nas horas vagas, não dispensa cozinhar acompanhado de uma boa música brasileira.

Ver perfil