3
\$\begingroup\$

I'm making an iOS app which supports multiple languages but not in the built-in localized way. In iOS, for localization to work, the user has to change the device's language. But in the app I'm working on, the user needs to be able to choose the language to effect only this app. So for example, I can have the device's language set to English but the user can choose Spanish to be set as the language within the app.

I took some concepts from iOS localization and added some things on my own. First I created plists to contain strings for each language I support.

English

enter image description here

Spanish

enter image description here

Next I created a singleton which handles the setting on the chosen language and serving the strings based on that selected language. I use a third-party library called SwiftyPlist for easier manipulation of plists.

enum Language: String {
    case english = "en"
    case spanish = "es"
}

class LanguageManager {
    static let shared = LanguageManager()

    private var plist: Plist!

    // Get/Set the language of choice
    var language: Language? {
        set {
            UserDefaults.standard.set(newValue?.rawValue, forKey: "Language")
            reloadPlist()
        }
        get {
            if let code = UserDefaults.standard.object(forKey: "Language") as? String {
                return Language(rawValue: code)
            } else {
                return nil
            }
        }
    }


    private init() {
        reloadPlist()
    }

    // Reload the corresponding plist
    private func reloadPlist() {
        if language == nil || language! == .english {
            let dict = NSDictionary(contentsOfFile: Bundle.main.path(forResource: "English", ofType: "plist")!)
            plist = Plist(plistObject: dict)
        } else if language! == .spanish {
            let dict = NSDictionary(contentsOfFile: Bundle.main.path(forResource: "Spanish", ofType: "plist")!)
            plist = Plist(plistObject: dict)
        }
    }

    var name: String {
        return plist["YOUR_NAME"]!.string!
    }

}

Now I can set the chosen language like this.

LanguageManager.shared.language = .spanish

And I can simply access the strings like so.

LanguageManager.shared.name

I feel like this is a good, scalable approach to my requirement. But I'd like to see if it has room to improvement.

Demo project

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

I did something like this. You can also just use a Strings file. What I did was create a spreadsheet in the Google Drive with all the translations (one column for the identifier BUTTON_TITLE_OK and one each language the app supports). It's then very easy to make a little formula in Google Drive that takes all the identifiers, and turns it into an enum. Like so:

    enum LocalizedStringIdentifier: String {
        case BUTTON_TITLE_OK
    }

    func string(_ identifier: LocalizedStringIdentifier) -> String? {
        guard let value = plists[identifier.rawValue] else { return nil }
        return value
    }

The advantage of this that you get code completion in Xcode and thus less chance of mistakes. Usage:

LanguageManager.shared.string(.BUTTON_TITLE_OK)

The advantage of the spreadsheet approach is that you have all localizations combined so you have a better overview (and it is also easier to share with your client so that they can provide you with the right translations, unless this is your own project).

Side note: If you do use plists, you should try use the new Codable protocol to load Plists directly into your models.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.