What's NSLocalizedString equivalent in Swift?
Asked Answered
B

19

248

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.

Benoite answered 1/8, 2014 at 13:48 Comment(4)
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.Sarena
In Swift 3 you can just use NSLocalizedString("Cancel", comment: "Cancel button title") taking advantage of the default values. It is convenient I think.Cumae
This is a very good article about localization (string extension, different strings tables and even pluralization): medium.com/@marcosantadev/…Dialecticism
This is a very good article about localization in Swift for a robust architecture medium.com/@mendibarouk/…Dorcas
B
295

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.

Benoite answered 1/8, 2014 at 13:48 Comment(5)
it's only difference that comment can't be nil, and autocompletion is far from intuitive for short version.Enquire
this is not working any more I get error saying that not enough arguments are used.Mendelian
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).Flickinger
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.Dieback
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.Donegan
K
398

I use the following 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
    

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 !!!")
    
Kaz answered 1/4, 2015 at 6:38 Comment(19)
@drOX You might consider editing your code to: return NSLocalizedString(self, comment: self)Jonijonie
The only issue with this is that you won't be able to use the genstrings utility to generate your .strings files.Criminality
@AviCohen q.v. after "upd"Kaz
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 :)Cathern
Any idea how to use genstrings with this?Anecdotist
where would you put such a snippet in the project?Diabolo
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.Flitting
I found this great solution implemented in github.com/marmelroy/Localize-Swift. Problem of genstrings is also resolved by custom python script included by the author. I am not an author.Raasch
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.Obstetrics
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???Karly
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 runFlocky
Swift 3. bundle: Bundle.mainWatercool
@drOX , Bro could you help me please #44304998 ?Kazachok
@Flocky 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?Phototypy
I set it up system wide. It's available through services, but it always use my shortcut key: cmd-shift-F12Flocky
@Warpzit Just out of curiosity, how do you collect all your strings that need to be localised.Bendite
@Bendite 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.Manpower
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")Discreditable
This is perfect for realtime Info.plist user defined build settings translation when using multiple Xcode Schemes. Thanks!Donegan
B
295

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.

Benoite answered 1/8, 2014 at 13:48 Comment(5)
it's only difference that comment can't be nil, and autocompletion is far from intuitive for short version.Enquire
this is not working any more I get error saying that not enough arguments are used.Mendelian
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).Flickinger
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.Dieback
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.Donegan
D
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.

Duomo answered 9/1, 2018 at 9:41 Comment(0)
B
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 :)")
Broomstick answered 4/11, 2015 at 20:49 Comment(1)
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.lprojConnel
G
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.

Gaberdine answered 15/12, 2015 at 11:51 Comment(3)
This is bad practice. Comments are recommended and helpful unless you are doing all of the translation yourself.Lousy
Even if you're translating yourself, the comments would be helpful, especially in a large project.Kilkenny
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.Summersummerhouse
S
6

Swift 3 version :)...

import Foundation

extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }
}
Segura answered 2/9, 2016 at 20:11 Comment(0)
G
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.

Girl answered 13/12, 2016 at 22:38 Comment(3)
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") ?Cumae
@Cumae 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.Girl
What happens if you need to do another round of genstrings? Do you replace back all .localized by NSLocalizedString?Daina
D
4

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
Dnieper answered 21/11, 2016 at 20:57 Comment(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.Girish
I think it's better to use computed properties here, like this static var Hello: String = { return NSLocalizedString("Hello") }Longdrawnout
Downvoted because it doesn't follow the Swift naming guidelinesDaina
W
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)
    }
}
Warship answered 11/4, 2020 at 17:9 Comment(0)
K
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.

Kadner answered 5/7, 2018 at 1:51 Comment(0)
T
3

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")
Tipi answered 9/6, 2023 at 13:37 Comment(1)
By "more recent" you mean iOS 15.0, macOS 12.0.Alita
G
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.

Greyhen answered 1/1, 2017 at 10:49 Comment(0)
B
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")
Bowden answered 18/1, 2016 at 10:24 Comment(2)
It is bad practice to leave out the comments.Rogatory
@Duomo 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 ;)Bowden
G
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
Goodness answered 20/4, 2017 at 10:50 Comment(0)
A
1

extension:

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

use: "_YOUR_STRING_NAME_".localized()

Aleutian answered 27/11, 2022 at 18:42 Comment(0)
P
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.

Pancho answered 15/10, 2016 at 5:48 Comment(0)
B
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: "")
    }
}
Belshin answered 11/7, 2018 at 19:57 Comment(0)
W
0

If you are accepting parameters and want received string literals to automatically get into localization process within build-in Xcode tools, you can accept LocalizedStringResource type and convert to a String inside the body of your type with String(localized: parameterName) when needed. This kind of flow is

@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)

Unlike NSLocalizedString this technique handles string interpolation gracefully.

Note that for SwiftUI there is a special similar type LocalizedStringKey that is interoperable with most of SwiftUI views and modifiers. If you want your custom SwiftIU views be localized as easily as Apple's ones – take advantage of this type the way build-in types do.

Won answered 25/10, 2023 at 12:50 Comment(0)
T
-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.

Thousandth answered 24/12, 2020 at 13:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.