Concrete | Um papo sobre Localizable e Constants em Swift
Antes de tudo, importante dizer que esse post 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).
Esse post 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 dos strings necessários. 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).
// 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";
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" } } }
case change = "common.change"
case error = "common.error"
case delete = "common.delete"
case wait = "common.wait"
É 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 o string desejado.
// 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" } } }
case first = "icon-first-image"
Muito bom! Agora, para usar esse string, basta:
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: "") } }
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:
Sim, não existe essa imagem, o string se refere a um Localizable. Droga! O que podemos fazer?
Programação Orientada a Protocolo FTW! (Nota do Erick: sou meio dislexo escrevendo, olhei 10 vezes para ter certeza que não escrevi WTF.. rsrsrsrs)!
// MARK: - Representable Protocols protocol ImageRepresentable: RawRepresentable { var image: UIImage? { get } } protocol LocalizeRepresentable: RawRepresentable { var localized: String { get } }
// 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 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 desse artigo, incentivamos o uso dessa estrutura de "path" ("common.change"). Se por algum motivo você seguir a sugestão do post e usar essa forma de identificação das frases, não 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.
// 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" } } }
Então… Você pode minimizar e/ou dificultar que erros básicos possam ocorrer, como por exemplo 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 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! =)