What's NSLocalizedString equivalent in Swift?

IosSwiftLocalizationNslocalizedstring

Ios Problem Overview


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.

Ios Solutions


Solution 1 - Ios

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 !!!")

Solution 2 - Ios

The NSLocalizedString exists also in the Swift's world.

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

The tableName, bundle, and value parameters are marked with a default keyword which means we can omit these parameters while calling the function. In this case, their default values will be used.

This leads to a conclusion that the method call can be simplified to:

NSLocalizedString("key", comment: "comment")

Swift 5 - no change, still works like that.

Solution 3 - Ios

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.

Solution 4 - Ios

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 :)")

Solution 5 - Ios

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.

Solution 6 - Ios

Swift 3 version :)...

import Foundation

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

Solution 7 - Ios

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.

Solution 8 - Ios

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

Solution 9 - Ios

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)
	}
}

Solution 10 - Ios

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.

Solution 11 - Ios

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.

Solution 12 - Ios

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")

Solution 13 - Ios

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

Solution 14 - Ios

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.

Solution 15 - Ios

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: "")
    }
}

Solution 16 - Ios

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRafa de KingView Question on Stackoverflow
Solution 1 - Iosdr OXView Answer on Stackoverflow
Solution 2 - IosRafa de KingView Answer on Stackoverflow
Solution 3 - IosJoséView Answer on Stackoverflow
Solution 4 - IosKayView Answer on Stackoverflow
Solution 5 - IosJOMView Answer on Stackoverflow
Solution 6 - IosJanView Answer on Stackoverflow
Solution 7 - IosGYFKView Answer on Stackoverflow
Solution 8 - IosRobin DorpeView Answer on Stackoverflow
Solution 9 - IosDave GView Answer on Stackoverflow
Solution 10 - IosLiamView Answer on Stackoverflow
Solution 11 - IosMaxView Answer on Stackoverflow
Solution 12 - IosGatoCuriosoView Answer on Stackoverflow
Solution 13 - Iosanoop4realView Answer on Stackoverflow
Solution 14 - IosVadim MotorineView Answer on Stackoverflow
Solution 15 - IosresearcherView Answer on Stackoverflow
Solution 16 - IosatalayasaView Answer on Stackoverflow