[ios] What's NSLocalizedString equivalent in Swift?

Is there an Swift equivalent of NSLocalizedString(...)? In Objective-C, we usually use:

NSString *string = NSLocalizedString(@"key", @"comment");

How can I achieve the same in Swift? I found a function:

func NSLocalizedString(
    key: String,
    tableName: String? = default,
    bundle: NSBundle = default,
    value: String = default,
    #comment: String) -> String

However, it is very long and not convenient at all.

This question is related to ios swift localization nslocalizedstring

The answer is


Helpfull for usage in unit tests:

This is a simple version which can be extended to different use cases (e.g. with the use of tableNames).

public func NSLocalizedString(key: String, referenceClass: AnyClass, comment: String = "") -> String 
{
    let bundle = NSBundle(forClass: referenceClass)
    return NSLocalizedString(key, tableName:nil, bundle: bundle, comment: comment)
}

Use it like this:

NSLocalizedString("YOUR-KEY", referenceClass: self)

Or like this with a comment:

NSLocalizedString("YOUR-KEY", referenceClass: self, comment: "usage description")

Though this doesnt answer to the shortening problem, but this helped me to organize the messages, I created a structure for error messages like below

struct Constants {
    // Error Messages
    struct ErrorMessages {
        static let unKnownError = NSLocalizedString("Unknown Error", comment: "Unknown Error Occured")
        static let downloadError = NSLocalizedString("Error in Download", comment: "Error in Download")
    }
}

let error = Constants.ErrorMessages.unKnownError

This way you can organize the messages and make genstrings work.

And this is the genstrings command used

find ./ -name \*.swift -print0 | xargs -0 genstrings -o .en.lproj

Localization with default language:

extension String {
func localized() -> String {
       let defaultLanguage = "en"
       let path = Bundle.main.path(forResource: defaultLanguage, ofType: "lproj")
       let bundle = Bundle(path: path!)

       return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
    }
}

Actually, you can use two phases to translate your texts in Swift projects:

1) The first phase is using the old way to create all your translatable strings:

NSLocalisedString("Text to translate", comment: "Comment to comment")

1.1) Then you should use genstrings to generate Localizable.strings:

$ genstrings *swift

2) Afterwards, you should use this answer.

2.1) Use your XCode "Find and Replace" option based on the regular expression. As for the given example (if you have no comments) the regular expression will be:

NSLocalizedString\((.*)\, comment:\ \"\"\) 

and replace it with

$1.localized

or (if you have comments)

NSLocalizedString\((.*)\, comment:\ (.*)\)

and replace it with

$1.localizedWithComment(comment: $2)

You are free to play with regex and different extension combinations as you wish. The general way is splitting the whole process in two phases. Hope that helps.


I've created my own genstrings sort of tool for extracting strings using a custom translation function

extension String {

    func localizedWith(comment:String) -> String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: comment)
    }

}

https://gist.github.com/Maxdw/e9e89af731ae6c6b8d85f5fa60ba848c

It will parse all your swift files and exports the strings and comments in your code to a .strings file.

Probably not the easiest way to do it, but it is possible.


Created a small helper method for cases, where "comment" is always ignored. Less code is easier to read:

public func NSLocalizedString(key: String) -> String {
    return NSLocalizedString(key, comment: "")
}

Just put it anywhere (outside a class) and Xcode will find this global method.


When you to translate, say from English, where a phrase is the same, to another language where it is different (because of the gender, verb conjugations or declension) the simplest NSString form in Swift that works in all cases is the three arguments one. For example, the English phrase "previous was", is translated differently to Russian for the case of "weight" ("?????????? ???") and for "waist" ("?????????? ????").

In this case you need two different translation for one Source (in terms of XLIFF tool recommended in WWDC 2018). You cannot achieve it with two argument NSLocalizedString, where "previous was" will be the same both for the "key" and the English translation (i.e. for the value). The only way is to use the three argument form

NSLocalizedString("previousWasFeminine", value: "previous was", comment: "previousWasFeminine")

NSLocalizedString("previousWasMasculine", value: "previous was", comment: "previousWasMasculine")

where keys ("previousWasFeminine" and "previousWasMasculine") are different.

I know that the general advice is to translate the phrase as the whole, however, sometimes it too time consuming and inconvenient.


I use next solution:

1) create extension:

extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }
}

2) in Localizable.strings file:

"Hi" = "??????";

3) example of use:

myLabel.text = "Hi".localized

enjoy! ;)

--upd:--

for case with comments you can use this solution:

1) Extension:

extension String {
    func localized(withComment:String) -> String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: withComment)
    }
}

2) in .strings file:

/* with !!! */
"Hi" = "??????!!!";

3) using:

myLabel.text = "Hi".localized(withComment: "with !!!")

Probably the best way is this one here.

fileprivate func NSLocalizedString(_ key: String) -> String {
    return NSLocalizedString(key, comment: "")
}

and

import Foundation
extension String {
    static let Hello = NSLocalizedString("Hello")
    static let ThisApplicationIsCreated = NSLocalizedString("This application is created by the swifting.io team")
    static let OpsNoFeature = NSLocalizedString("Ops! It looks like this feature haven't been implemented yet :(!")
}

you can then use it like this

let message: String = .ThisApplicationIsCreated
print(message)

to me this is the best because

  • The hardcoded strings are in one specific file, so the day you want to change it it's really easy
  • Easier to use than manually typing the strings in your file every time
  • genstrings will still work
  • you can add more extensions, like one per view controller to keep things neat

When you are developing an SDK. You need some extra operation.

1) create Localizable.strings as usual in YourLocalizeDemoSDK.

2) create the same Localizable.strings in YourLocalizeDemo.

3) find your Bundle Path of YourLocalizeDemoSDK.

Swift4:

// if you use NSLocalizeString in NSObject, you can use it like this
let value = NSLocalizedString("key", tableName: nil, bundle: Bundle(for: type(of: self)), value: "", comment: "")

Bundle(for: type(of: self)) helps you to find the bundle in YourLocalizeDemoSDK. If you use Bundle.main instead, you will get a wrong value(in fact it will be the same string with the key).

But if you want to use the String extension mentioned by dr OX. You need to do some more. The origin extension looks like this.

extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }
}

As we know, we are developing an SDK, Bundle.main will get the bundle of YourLocalizeDemo's bundle. That's not what we want. We need the bundle in YourLocalizeDemoSDK. This is a trick to find it quickly.

Run the code below in a NSObject instance in YourLocalizeDemoSDK. And you will get the URL of YourLocalizeDemoSDK.

let bundleURLOfSDK = Bundle(for: type(of: self)).bundleURL
let mainBundleURL = Bundle.main.bundleURL

Print both of the two url, you will find that we can build bundleURLofSDK base on mainBundleURL. In this case, it will be:

let bundle = Bundle(url: Bundle.main.bundleURL.appendingPathComponent("Frameworks").appendingPathComponent("YourLocalizeDemoSDK.framework")) ?? Bundle.main

And the String extension will be:

extension String {
    var localized: String {
        let bundle = Bundle(url: Bundle.main.bundleURL.appendingPathComponent("Frameworks").appendingPathComponent("YourLocalizeDemoSDK.framework")) ?? Bundle.main
        return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
    }
}

Hope it helps.


In addition to great extension written here if you are lazy to find and replace old NSLocalizedString you can open find & replace in Xcode and in the find section you can write NSLocalizedString\(\(".*"\), comment: ""\) then in the replace section you need to write $1.localized to change all NSLocalizedString with "blabla".localized in your project.


Swift 3 version :)...

import Foundation

extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }
}

A variation of the existing answers:

Swift 5.1:

extension String {

    func localized(withComment comment: String? = nil) -> String {
        return NSLocalizedString(self, comment: comment ?? "")
    }

}

You can then simply use it with or without comment:

"Goodbye".localized()
"Hello".localized(withComment: "Simple greeting")

Please note that genstrings won't work with this solution.


This is an improvement on the ".localized" approach. Start with adding the class extension as this will help with any strings you were setting programatically:

extension String {
    func localized (bundle: Bundle = .main, tableName: String = "Localizable") -> String {
        return NSLocalizedString(self, tableName: tableName, value: "\(self)", comment: "")
    }
}

Example use for strings you set programmatically:

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

Now Xcode's storyboard translation files make the file manager messy and don't handle updates to the storyboard well either. A better approach is to create a new basic label class and assign it to all your storyboard labels:

class BasicLabel: UILabel {
    //initWithFrame to init view from code
    override init(frame: CGRect) {
      super.init(frame: frame)
      setupView()
    }

    //initWithCode to init view from xib or storyboard
    required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      setupView()
    }

    //common func to init our view
    private func setupView() {
        let storyboardText = self.text
        text = storyboardText?.localized()
    }
}

Now every label you add and provide default default for in the storyboard will automatically get translated, assuming you've provide a translation for it.

You could do the same for UIButton:

class BasicBtn: UIButton {
    //initWithFrame to init view from code
    override init(frame: CGRect) {
      super.init(frame: frame)
      setupView()
    }

    //initWithCode to init view from xib or storyboard
    required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      setupView()
    }

    //common func to init our view
    private func setupView() {
        let storyboardText = self.titleLabel?.text
        let lclTxt = storyboardText?.localized()
        setTitle(lclTxt, for: .normal)
    }
}

By using this way its possible to create a different implementation for different types (i.e. Int or custom classes like CurrencyUnit, ...). Its also possible to scan for this method invoke using the genstrings utility. Simply add the routine flag to the command

genstrings MyCoolApp/Views/SomeView.swift -s localize -o .

extension:

import UIKit

extension String {
    public static func localize(key: String, comment: String) -> String {
        return NSLocalizedString(key, comment: comment)
    }
}

usage:

String.localize("foo.bar", comment: "Foo Bar Comment :)")

Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to swift

Make a VStack fill the width of the screen in SwiftUI Xcode 10.2.1 Command PhaseScriptExecution failed with a nonzero exit code Command CompileSwift failed with a nonzero exit code in Xcode 10 Convert Json string to Json object in Swift 4 iOS Swift - Get the Current Local Time and Date Timestamp Xcode 9 Swift Language Version (SWIFT_VERSION) How do I use Safe Area Layout programmatically? How can I use String substring in Swift 4? 'substring(to:)' is deprecated: Please use String slicing subscript with a 'partial range from' operator Safe Area of Xcode 9 The use of Swift 3 @objc inference in Swift 4 mode is deprecated?

Examples related to localization

What's NSLocalizedString equivalent in Swift? Best practice multi language website Best practice for localization and globalization of strings and labels How to change language of app when user selects language? Lint: How to ignore "<key> is not translated in <language>" errors? Using ResourceManager How do I set the default locale in the JVM? What is the list of supported languages/locales on Android? Android: how to get the current day of the week (Monday, etc...) in the user's language? Get the current language in device

Examples related to nslocalizedstring

What's NSLocalizedString equivalent in Swift? How to force NSLocalizedString to use a specific language