Desenvolvimento

14 out, 2016

Um papo sobre Localizable e constants em Swift

Publicidade

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/.