247

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.

Juan Boero
  • 6,281
  • 1
  • 44
  • 62
Rafał Sroka
  • 39,540
  • 23
  • 113
  • 143
  • 2
    Best is to create shorter version of code snippet: NSLocalizedString("", comment: "") ... I liked the extension solution, but the problem is genstrings will not capture these strings into translation file. – Matej Ukmar Aug 06 '15 at 10:46
  • 3
    In Swift 3 you can just use `NSLocalizedString("Cancel", comment: "Cancel button title")` taking advantage of the default values. It is convenient I think. – LShi Feb 07 '17 at 01:21
  • This is a very good article about localization (string extension, different strings tables and even pluralization): https://medium.com/@marcosantadev/app-localization-tips-with-swift-4e9b2d9672c9 – LightMan Jun 14 '18 at 08:46
  • This is a very good article about localization in Swift for a robust architecture https://medium.com/@mendibarouk/enhance-your-localized-capabilities-on-your-ios-applications-d3ba17138077 – Mendy Apr 07 '19 at 07:54

18 Answers18

397

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 !!!")
dr OX
  • 4,671
  • 1
  • 17
  • 9
  • @drOX You might consider editing your code to: `return NSLocalizedString(self, comment: self)` – Avi Cohen May 02 '15 at 10:40
  • 97
    The only issue with this is that you won't be able to use the `genstrings` utility to generate your .strings files. – Ned May 04 '15 at 22:21
  • @AviCohen q.v. after "upd" – dr OX May 07 '15 at 20:39
  • 9
    That's a very good idea! I also made it a little bit smarter by changing to ```func localized(comment: String = "") -> String``` so it becomes smaller and with optional comments :) – Gui Moura Jul 01 '15 at 17:58
  • 2
    Any idea how to use `genstrings` with this? – Chris Sep 29 '15 at 08:46
  • where would you put such a snippet in the project? – Pascal Nov 26 '15 at 15:09
  • 51
    Everybody is very excited about this answer, but the BIG problem (for any serious project with several languages) is that this completely messes up your management of your translated messages, because `genstrings` only works on literal strings passed in to NSLocalizedString. With this clever workaround, you lose the ability to update your .strings files using the `genstrings` tool, and at least for me that means I will not be able to use this simplified approach. – Erik van der Neut Mar 02 '16 at 10:30
  • 15
    I found this great solution implemented in https://github.com/marmelroy/Localize-Swift. Problem of genstrings is also resolved by custom python script included by the author. I am not an author. – Tomek Cejner Apr 30 '16 at 15:01
  • 1
    Similar to the comment on `genstrings` - it will also mess up the built-in Xcode XLIFF export/import process too, because that tool is also a lexical parser of source code, and won't know how to export the strings anymore. – Ken Ko Sep 01 '16 at 06:03
  • 2
    I don't understand why people think this is a good answer? What is the point of using localization API at all (even with the extension) if you can't export/import it properly via Xcode for translation??? – David James Oct 07 '16 at 11:01
  • 1
    For those that like/need the xcode import/export feature, I created a little AppleScript that puts the NSLocalizedString function around the string you select. I linked it to a shortcut key. Simply select the string you want to localise and hit the shortcut key. This was not my idea. Got it from somewhere on the internet, but can't remember where, sorry. Here's the script: `on run {input, parameters} set new_text to "NSLocalizedString(" & input & ",comment:\"translatorComment\")" return new_text end run` – guido Dec 01 '16 at 19:40
  • Swift 3. bundle: Bundle.main – Cristian Cardoso Jan 23 '17 at 17:14
  • @drOX , Bro could you help me please https://stackoverflow.com/questions/44304498/how-to-set-font-family-to-uiviewcontroller-title-in-swift ? – May Phyu Jun 02 '17 at 08:56
  • @guido How do you make the Applescript available in Xcode? Via Services? And what shortcut did you set up? In Xcode's Preferences, or in System Preferences, or where? – Thomas Tempelmann Jun 11 '17 at 13:30
  • I set it up system wide. It's available through services, but it always use my shortcut key: cmd-shift-F12 – guido Jun 11 '17 at 13:57
  • @Warpzit Just out of curiosity, how do you collect all your strings that need to be localised. – BangOperator Dec 28 '18 at 12:34
  • @BangOperator Well right now we have a script that converts a csv file to the localized files. We're planning to transition to one of the online tools that can manage all translations on multiple platforms. In other words, we wont be importing or exporting any translations from xcode, we'll simply just override the files. – Warpzit Jan 01 '19 at 19:32
  • 1
    `extension String { func localized(withComment:String, table_name: String?) -> String { return NSLocalizedString(self, tableName: table_name, bundle: Bundle.main, value: "", comment: withComment) } }` if you use this code, default it will check the string in **Localizable.strings** file. And if you want to check in another file, you can pass the file name in parameters, `let contentHelloWorld = "hello_world".localized(withComment: "hello", table_name: "Localizable-SP")` – Wimukthi Rajapaksha Feb 18 '20 at 05:22
  • This is perfect for realtime `Info.plist` user defined build settings translation when using multiple Xcode Schemes. Thanks! – Ethan Allen Jun 16 '21 at 05:28
293

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.

Rafał Sroka
  • 39,540
  • 23
  • 113
  • 143
  • 44
    it's only difference that comment can't be nil, and autocompletion is far from intuitive for short version. – Marcin Sep 29 '14 at 07:41
  • 1
    this is not working any more I get error saying that not enough arguments are used. – Apps 4 U Nov 14 '14 at 10:59
  • 2
    Not that the above is correct in Xcode 6.3, Swift 1.2 with the specific change from objective-c, the comment (as Marcin stated) cannot be nil, but it can be "" (empty). – Neil Apr 13 '15 at 14:34
  • 2
    A nil/empty comment makes it hard to relocate the string later in the string file; if nothing else add class/file name where it's used as the comment. – Johan Oct 26 '15 at 08:34
  • This is the correct answer. Once Apple does update it for Swift, Xcode will be able to just auto-convert this API to its new Swift API and nothing else will break. Even in Xcode's Refractor menu currently (v 11.4.1) there is a `Wrap in NSLocalizedString` option which make things really easy by just highlighting text, right-clicking, and selecting the menu item. – Ethan Allen May 11 '20 at 00:14
40

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.

José
  • 3,112
  • 1
  • 29
  • 42
18

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 :)")
Kay
  • 189
  • 1
  • 2
  • This also worked for me but instead of a signaling a . at the end for current dir I had to add the actual folder to the genstrings command like this: genstrings MyCoolApp/Views/SomeView.swift -s localize -o en.lproj – devjme Feb 04 '22 at 19:21
6

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.

JOM
  • 8,139
  • 6
  • 78
  • 111
  • 14
    This is bad practice. Comments are recommended and helpful unless you are doing all of the translation yourself. – Jeremiah Jul 01 '16 at 18:27
  • Even if you're translating yourself, the comments would be helpful, especially in a large project. – shim Oct 18 '18 at 16:10
  • I use a translation database that has a place for comments, and have no need for these comments or the empty "comment:" arguments to be in my code. So I'm using this, plus an underscore before the "key:" argument, to make the function calls even cleaner. – arlomedia Dec 15 '20 at 20:53
6

Swift 3 version :)...

import Foundation

extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }
}
Jan
  • 2,295
  • 1
  • 17
  • 16
6

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.

Community
  • 1
  • 1
GYFK
  • 151
  • 2
  • 3
  • 1
    Sorry I don't get the point of many answers here. What's the benefit of the method over using `NSLocalizedString("Cancel", comment: "Cancel button title")` ? – LShi Feb 07 '17 at 01:19
  • 1
    @LShi some people were complaining, that `NSLocalizedString` looks less Swiftier, than it should look. `String.localized` on the other hand looks more Swifty but you are unable to use `gesntrings` utility with it which is commonly used to ease your work with internationalization. My point is that it`s pretty easy to mix both approaches. So mainly it is a question of readability. – GYFK Feb 07 '17 at 15:17
  • What happens if you need to do another round of `genstrings`? Do you replace back all `.localized` by `NSLocalizedString`? – Cristik Jan 04 '20 at 08:53
5

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
Robin Dorpe
  • 344
  • 3
  • 7
  • 3
    The thing to note is that Strings defined in the described way are static strings. The app should be relaunched after changing language in iOS Settings app. If not, relaunch it by yourself in order to see changes. It can also have a memory overhead, since we initialize all strings at once, not at the time they’re needed. – iDevAmit May 25 '17 at 06:18
  • 3
    I think it's better to use computed properties here, like this `static var Hello: String = { return NSLocalizedString("Hello") }` – art-of-dreams Aug 26 '17 at 08:50
  • Downvoted because it doesn't follow the Swift [naming guidelines](https://swift.org/documentation/api-design-guidelines/#follow-case-conventions) – Cristik Jan 04 '20 at 08:51
4

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)
    }
}
Dave G
  • 12,042
  • 7
  • 57
  • 83
3

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.

Liam
  • 172
  • 1
  • 10
2

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.

Max
  • 21
  • 4
1

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")
GatoCurioso
  • 225
  • 3
  • 11
  • 1
    It is bad practice to leave out the comments. – José Apr 19 '18 at 09:39
  • @José Thanks for your comment. The code was meant as an idea, not as template for copy and paste. But I added the option to add comments if you want ;) – GatoCurioso Apr 20 '18 at 06:38
1

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
anoop4real
  • 7,598
  • 4
  • 53
  • 56
1

extension:

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

use: "_YOUR_STRING_NAME_".localized()

europeec
  • 398
  • 4
  • 8
0

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.

Vadim Motorine
  • 2,973
  • 1
  • 11
  • 7
0

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: "")
    }
}
researcher
  • 1,758
  • 22
  • 25
0

A more recent approach to solving this problem is using one of the String initializers:

public init(localized key: StaticString, 
            defaultValue: String.LocalizationValue, 
            table: String? = nil, 
            bundle: Bundle? = nil, 
            locale: Locale = .current, 
            comment: StaticString? = nil)

So you could use it like this:

let someText = String(localized: "Hello.World", 
                      defaultValue: "Hello World")
user3391404
  • 81
  • 1
  • 4
-1

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.

atalayasa
  • 3,310
  • 25
  • 42