Enum with localized string in swift
Asked Answered
G

7

26

I want to use enumeration with localized string, so I do like this, it works, but the problem of this solution is : I can't get easily enum value from localized string, I must have the key to do it :

let option = DietWithoutResidueOption(rawValue: "NoDiet")

If not I must to call dietWithoutResidueOptionWith method to get enum value... :/

There are a better solution to store directly localizedString and not keys in enum ?

Thanks

Enumeration

  enum DietWithoutResidueOption: String {
  case NoDiet = "NoDiet"
  case ThreeDays = "ThreeDays"
  case FiveDays  = "FiveDays"

  private func localizedString() -> String {
    return NSLocalizedString(self.rawValue, comment: "")
  }

  static func dietWithoutResidueOptionWith(#localizedString: String) -> DietWithoutResidueOption {
    switch localizedString {
    case DietWithoutResidueOption.ThreeDays.localizedString():
      return DietWithoutResidueOption.ThreeDays
    case DietWithoutResidueOption.FiveDays.localizedString():
      return DietWithoutResidueOption.FiveDays
    default:
      return DietWithoutResidueOption.NoDiet
    }
  }
}

Localizable.strings

"NoDiet" = "NON, JE N'AI PAS DE RÉGIME";
"ThreeDays" = "OUI, SUR 3 JOURS";
"FiveDays"  = "OUI, SUR 5 JOURS";

call

println(DietWithoutResidueOption.FiveDays.localizedString())
Gypsum answered 29/1, 2015 at 11:32 Comment(2)
This is a very good article about localization in Swift for a robust architectureQuass
Localizing a enum is a bad idea. Because you cannot convert a value in localizable file to the respective enum easily. The ID in the localizable may be unique, but the value will be changing and repeating.Breakable
R
14

You can use any StringLiteralConvertible, Equatable type for RawValue type of enum.

So, how about:

import Foundation

struct LocalizedString: StringLiteralConvertible, Equatable {

    let v: String

    init(key: String) {
        self.v = NSLocalizedString(key, comment: "")
    }
    init(localized: String) {
        self.v = localized
    }
    init(stringLiteral value:String) {
        self.init(key: value)
    }
    init(extendedGraphemeClusterLiteral value: String) {
        self.init(key: value)
    }
    init(unicodeScalarLiteral value: String) {
        self.init(key: value)
    }
}

func ==(lhs:LocalizedString, rhs:LocalizedString) -> Bool {
    return lhs.v == rhs.v
}

enum DietWithoutResidueOption: LocalizedString {
    case NoDiet = "NoDiet"
    case ThreeDays = "ThreeDays"
    case FiveDays  = "FiveDays"

    var localizedString: String {
        return self.rawValue.v
    }

    init?(localizedString: String) {
        self.init(rawValue: LocalizedString(localized: localizedString))
    }
}

Using this, you can construct DietWithoutResidueOption by 3 ways:

let option1 = DietWithoutResidueOption.ThreeDays
let option2 = DietWithoutResidueOption(rawValue: "ThreeDays") // as Optional
let option3 = DietWithoutResidueOption(localizedString: "OUI, SUR 3 JOURS")  // as Optional

and extract the localized string with:

let localized = option1.localizedString
Refinement answered 29/1, 2015 at 11:43 Comment(2)
Wouldn't it make more sense to put the 'var localizedString...' in an extension of RawRepresentable where RawValue is LocalizedString so you'd get it for free on all enums that use LocalizedString as their raw value type?Osier
I just talked to an Apple Engineer about localization and they recommend not to pass variables in NSLocalizedString() as it will stop the strings from beeing discovered automatically.Atreus
T
20

Try this, it's pretty simple and straight forward:

enum ChoicesTitle: String {
    case choice1 = "Choice 1"
    case choice2 = "Choice 2"
    case choice3 = "Choice 3"
    case choice4 = "Choice 4"
    case choice5 = "Choice 5"
    case choice6 = "Choice 6"
    
    func localizedString() -> String {
        return NSLocalizedString(self.rawValue, comment: "")
    }
    
    static func getTitleFor(title: ChoicesTitle) -> String {
        return title.localizedString()
    }
}

And you could use it like this:

let stringOfChoice1: String = ChoicesTitle.getTitleFor(title: .choice1)

Hope this works for you

Tilden answered 23/3, 2017 at 0:59 Comment(1)
You must not forget to add the localized versions of the string to 'Localizable.strings', otherwise there would be no translated version.Paragrapher
R
14

You can use any StringLiteralConvertible, Equatable type for RawValue type of enum.

So, how about:

import Foundation

struct LocalizedString: StringLiteralConvertible, Equatable {

    let v: String

    init(key: String) {
        self.v = NSLocalizedString(key, comment: "")
    }
    init(localized: String) {
        self.v = localized
    }
    init(stringLiteral value:String) {
        self.init(key: value)
    }
    init(extendedGraphemeClusterLiteral value: String) {
        self.init(key: value)
    }
    init(unicodeScalarLiteral value: String) {
        self.init(key: value)
    }
}

func ==(lhs:LocalizedString, rhs:LocalizedString) -> Bool {
    return lhs.v == rhs.v
}

enum DietWithoutResidueOption: LocalizedString {
    case NoDiet = "NoDiet"
    case ThreeDays = "ThreeDays"
    case FiveDays  = "FiveDays"

    var localizedString: String {
        return self.rawValue.v
    }

    init?(localizedString: String) {
        self.init(rawValue: LocalizedString(localized: localizedString))
    }
}

Using this, you can construct DietWithoutResidueOption by 3 ways:

let option1 = DietWithoutResidueOption.ThreeDays
let option2 = DietWithoutResidueOption(rawValue: "ThreeDays") // as Optional
let option3 = DietWithoutResidueOption(localizedString: "OUI, SUR 3 JOURS")  // as Optional

and extract the localized string with:

let localized = option1.localizedString
Refinement answered 29/1, 2015 at 11:43 Comment(2)
Wouldn't it make more sense to put the 'var localizedString...' in an extension of RawRepresentable where RawValue is LocalizedString so you'd get it for free on all enums that use LocalizedString as their raw value type?Osier
I just talked to an Apple Engineer about localization and they recommend not to pass variables in NSLocalizedString() as it will stop the strings from beeing discovered automatically.Atreus
A
14

this is a late answer, but I just had a chat with Apple Engineers about that topic they recommend to do it that way:

    enum LocalizedStrings {
        case title

        var localized: String {
            switch self {
            case .title:
                return NSLocalizedString("My Title", comment: "My Comment")
            }
        }
    }

In your case the solution would be not much different from the original code:

    enum DietWithoutResidueOption {
        case NoDiet
        case ThreeDays
        case FiveDays

        var localizedString: String {
            switch self {
            case .NoDiet:
                return NSLocalizedString("NoDiet", comment: "Some comment")
            case .ThreeDays:
                return NSLocalizedString("ThreeDays", comment: "Some comment")
            case .FiveDays:
                return NSLocalizedString("FiveDays", comment: "Some comment")
            }
        }

        static func dietWithoutResidueOptionWith(localizedString: String) -> DietWithoutResidueOption {
            switch localizedString {
            case DietWithoutResidueOption.ThreeDays.localizedString:
                return DietWithoutResidueOption.ThreeDays
            case DietWithoutResidueOption.FiveDays.localizedString:
                return DietWithoutResidueOption.FiveDays
            default:
                return DietWithoutResidueOption.NoDiet
            }
        }
    }

The reason is that they don't want you to pass variables into NSLocalizedString(). This has something to do with optimization and parsing the strings. Imagine Xcode generating the localizable.strings file on it's own at some point, but it could not find the strings, because they are passed as variables.

Atreus answered 3/6, 2019 at 0:31 Comment(0)
D
4

A nice approach is to create a struct of Localizable Strings with static variables like so:

LocalizableStrings.swift

struct LocalizableStrings {
    static let noDiet  = NSLocalizedString("NoDiet", comment: "")
    static let threeDays  = NSLocalizedString("ThreeDays", comment: "")
    static let fiveDays  = NSLocalizedString("FiveDays", comment: "")
}

Localizable.strings

"NoDiet" = "NON, JE N'AI PAS DE RÉGIME";
"ThreeDays" = "OUI, SUR 3 JOURS";
"FiveDays"  = "OUI, SUR 5 JOURS";

And your enum would look like that:

Enum

enum DietWithoutResidueOption {
    case NoDiet,
    ThreeDays,
    FiveDays

    var description : String {
        get {
            switch(self) {
            case .NoDiet:
                return LocalizableStrings.noDiet
            case .ThreeDays:
                return LocalizableStrings.threeDays
            case .FiveDays:
                return LocalizableStrings.fiveDays
            }
        }
    }
}

So, for instance, to get your description you can do like below:

DietWithoutResidueOption.NoDiet.description

The good thing about this approach is that you put the keys of your localizable strings on a single file. So, for instance, if you change the NoDiet key on your Localizable.strings file you only need to update the LocalizableStrings.swift file, instead of all the places where we have the NoDiet key as a string. Furthermore, you take the risk of spelling wrong the NoDiet key in some file where it is being used and your code will compile with no errors, meanwhile using a static variable from LocalizableStrings.swift you can avoid that, as your code will not compile and you will see an error message saying where the error is.

Declinatory answered 7/3, 2019 at 1:11 Comment(1)
I really like your approach as it uses Apple's recommendations and just uses a different NSLocalizedString() for every string!Atreus
G
1

Ohter alternative :

Enum

enum Title : String {

  case CEO = "CEOKey"
  case CTO = "CTOKey"
  case CFO = "CFOKey"

  private static let allTitles = [CEO, CTO, CFO]

  var localizedString: String {
    return NSLocalizedString(self.rawValue, comment: "")
  }

  init!(rawValue: String) {
    var keys =  Title.allTitles
    var filtered = keys.filter { $0.rawValue == rawValue }

    self = filtered.first!
  }

  init!(localizedString: String) {
    var keys =  Title.allTitles
    var filtered = keys.filter { $0.localizedString == localizedString }

    self = filtered.first!
  }
}

Localizable.strings

"CEOKey" = "Chief Executive Officer";
"CTOKey" = "Chief Technical Officer";
"CFOKey" = "Chief Financial Officer";

Constract enum :

let option1 = Title.CFO
let option2 = Title(rawValue: "CTOKey") // init from key
let option3 = Title(localizedString: NSLocalizedString("CEOKey", comment: ""))  // init from value

Extract the localized strings :

println("option1 localized string : \(option1.localizedString)")
println("option2 localized string : \(option2.localizedString)")
println("option3 localized string : \(option3.localizedString)")

Input

option1 localized string : Chief Financial Officer
option2 localized string : Chief Technical Officer
option3 localized string : Chief Executive Officer

This code will generate exception, if localized strings or keys not found

Gypsum answered 2/2, 2015 at 22:56 Comment(0)
D
1

This is my example

enum Localization: String {
    case appName = "app_name"
    case appOk = "app_ok"
    case appError = "app_error"
    case placeholderNoContent = "placeholder_no_content"
    case homeTitle = "home_title"

public func localized(args: CVarArg...) -> String {
    let localizedString = NSLocalizedString(self.rawValue, comment: "")
    return withVaList(args, { (args) -> String in
        return NSString(format: localizedString, locale: Locale.current, arguments: args) as String
    })
}
}

Usage

self.homeTitleLabel = Localization.homeTitle.localized()

This Localization enum can easily be used with string formats.

Dost answered 23/7, 2019 at 13:34 Comment(0)
P
0

Try this protocol which I created, and you can import, use it like below.

https://github.com/Wei18/ZWExt/blob/master/ZWExt/Classes/Protocol/Localizable.swift

enum SomeKey: String, Localizable {
  case MenuGreeting = "lb_menu_greeting"
  case HaveBook = "I have %@ books"
}

// Sample
let menuGreeting: String = SomeKey.MenuGreeting.localized()
let iHaveBoxes: String = SomeKey.HaveBook.localized([3])

/*
// You also can make it with html.
SomeKey.CustomCase.localizedHTML()
SomeKey.CustomCase.localizedHTML([])
*/
Please answered 22/3, 2019 at 6:26 Comment(1)
link doesn't existsUmeko

© 2022 - 2024 — McMap. All rights reserved.