5

I want to undefine or override NSLocalizedString in swift 2.3 and i search a lot about it and finally i found a way in Objective C to do this as below.

#undef NSLocalizedString
#define NSLocalizedString(key,_comment) your_function_name

I want to achieve this functionality in swift. I Only know NSLocalizedString is a macro in NSBundle.h for Objective C.So we can redefine it.but for swift we can't achieve this.I just want to redefine or Override NSLocalizedString function for swift.Please help me to sort out this.Any help would be appreciated.

Daniel
  • 20,420
  • 10
  • 92
  • 149
Sumit Jangra
  • 651
  • 5
  • 16
  • did you consider method swizzling? Here is an example on how to do it in swift http://kostiakoval.github.io/posts/methods-swizzling-in-swift – Samer Murad Nov 08 '17 at 15:08
  • we can use swizzling for class and instance method.but how can you swizzle global functions,NSLocalizedString is like a global function in swift which can call without any class or object refrence. – Sumit Jangra Nov 10 '17 at 07:18
  • This requirement is very "unusual". What are you trying to achieve? – Daniel Nov 10 '17 at 12:55
  • you are right, my bad, totally missed that part – Samer Murad Nov 10 '17 at 13:22

3 Answers3

0

NSLocalicedString is a global method in Swift.

A global function cannot be overriden, but it can be redefined. To redefine the method just declare a new version. The selected function will be scope based. For example:

func NSLocalizedString(_ key: String, tableName: String? = nil, bundle: Bundle = Bundle.main, value: String = "", comment: String) -> String {
    return "redefined version in the project"
}

class Test {
    func NSLocalizedString(_ key: String, tableName: String? = nil, bundle: Bundle = Bundle.main, value: String = "", comment: String) -> String {
        return "redefined version in Test"
    }

    func getLocalizedString() -> String{
        return NSLocalizedString("test", comment: "t")
    }
}

class Test2 {
    func NSLocalizedString(_ key: String, tableName: String? = nil, bundle: Bundle = Bundle.main, value: String = "", comment: String) -> String {
        return "redefined version in Test2"
    }

    func getLocalizedString() -> String{
        return NSLocalizedString("test", comment: "t")
    }
}

NSLocalizedString("test", comment: "t") //will return "redefined version in the project"
Test().getLocalizedString() //will return "redefined version in Test"
Test2().getLocalizedString() //will return "redefined version in Test2"
Daniel
  • 20,420
  • 10
  • 92
  • 149
  • Where you will redefine new version in the project – Sumit Jangra Nov 10 '17 at 06:51
  • is it a global function or a protocol method or anything else. – Sumit Jangra Nov 10 '17 at 06:52
  • and do i need to redefine it in each class where i already calling this method – Sumit Jangra Nov 10 '17 at 06:55
  • your solution also not work when we call NSLocalizedString method in closures and property initializers,you need to pass reference of that class to call this method. – Sumit Jangra Nov 10 '17 at 07:15
  • @SumitDhariwal You define the new version in the project outside of any class. It is a global function. If you want to use a new implementation, you will have to redefine it in the classes (only if a new implementation is needed, otherwise it will just take the closest in the scope). The solution does also work in closures, it takes the functions closest to the scope where the block has been defined. – Daniel Nov 10 '17 at 12:54
  • i already tried this solution ,it showing error when we call NSLocalizedString function in property initalizer ,here is the error description - 'Cannot use instance member 'NSLocalizedString' within property initializer; property initializers run before 'self' is available' – Sumit Jangra Nov 13 '17 at 08:39
  • Also when we call this global function in closures it also showing following error: Call to method 'NSLocalizedString' in closure requires explicit 'self.' to make capture semantics explicit. – Sumit Jangra Nov 13 '17 at 08:40
  • hope you now understanding the actual problem. – Sumit Jangra Nov 13 '17 at 08:41
  • @SumitDhariwal Those error are not related to my answer. If you explain what you are trying to accomplish maybe I can help. (Btw. the first error can be overcome by not calling self in a property that is initiated before self, the second problem requires to call the function with `self.` in front of NSLocalizedString) – Daniel Nov 13 '17 at 10:31
  • Btw. I don't think you should be chnaging the implementation of NSLocalizedString. If you explain your problem there probably is a better solution. – Daniel Nov 13 '17 at 10:32
0

Found this, it will only be helpful you are going to use NSLocalizedString in side classes. tested it on swift 4 with the following tweaks

extension NSObject {
    func NSLocalizedString(_ key: String, comment: String) -> String {
        return "My custom localization"
    }

    static func NSLocalizedString(_ key: String, comment: String) -> String {
        return "My custom localization"
    }
}
class ViewController: UIViewController {

    @IBOutlet weak var myLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        myLabel.text = NSLocalizedString("mystring",comment:"")
    }
}

should work the same for swift 2.3 (without the underscore before the key param)

Update

This needs a workaround when working with closures, because swift will expect of you to use the NSLocalizedString with self like so, self.NSLocalizedString("mystring",comment:""). The workaround in this scenario is to assign the string to a let/var outside the closure.

Example:

 // Doesn't work
 self.present(anotherVc, animated: true) { [weak self] in
      self?.myLabel.text = NSLocalizedString("mystring", comment: "") // compiler will throw an error
 } 
 // Does work
 let string = NSLocalizedString("mystring", comment: "")
 self.present(anotherVc, animated: true) { [weak self] in
      self?.myLabel.text = string 
 } 

For property initializers the compiler will use the static function, that's why it's important to set them both

Example:

class MyClass: NSObject {
   // This let
   let myStr = NSLocalizedString("mystring",comment:"")
}

extension NSObject {
    func NSLocalizedString(_ key: String, comment: String) -> String {
        return "My custom localization"
    }
    // Uses the static method    
    static func NSLocalizedString(_ key: String, comment: String) -> String {
        return "My custom localization"
    }
}
Samer Murad
  • 2,479
  • 25
  • 27
  • try to call NSLocalizedString in property intializer and closoures. – Sumit Jangra Nov 13 '17 at 07:50
  • you mean that the need for `self` breaks the behavior that you are trying to achieve? you can always assign the needed string to a `let` outside the closure and and use the `let` inside: `let string = NSLocalizedString("mystring", comment: ""); self.present(anotherVc, animated: true) { [weak self] in self?.myLabel.text = string }` – Samer Murad Nov 13 '17 at 09:27
  • for property initializers the compiler uses the static function – Samer Murad Nov 13 '17 at 09:32
  • you mean that the need for self breaks the behavior that you are trying to achieve? - Yes. update your answer what u are trying to suggest. – Sumit Jangra Nov 13 '17 at 09:32
  • and you can't set the string outside the closure due to a third party library usage? or is it just too much overhead? – Samer Murad Nov 13 '17 at 09:35
-1

#undef is a C preprocessor directive, and Swift does not support any preprocessor directives, including the complex #define macro used for NSLocalizedString. In fact, it is imported as:

/// Returns a localized string, using the main bundle if one is not specified.
public func NSLocalizedString(_ key: String, tableName: String? = default, bundle: Bundle = default, value: String = default, comment: String) -> String

However, you could easily accomplish the intended behavior by just writing your own implementation, which should automatically be used instead. For example:

public func NSLocalizedString(_ key: String, tableName: String? = nil, bundle: Bundle = Bundle.main, value: String = "", comment: String) -> String {
    // Do your magic here!
    return "test"
}

But please don't do this. It would be much better to just create a different function. Otherwise, it will not be clear whether or not your function is the one being used, or you may get: error: ambiguous use of 'NSLocalizedString(_:tableName:bundle:value:comment:)' if you need to change the signature, even if you just want to make something optional.

Coder-256
  • 5,212
  • 2
  • 23
  • 51
  • you mean create different function with name NSLocalizedString for each class – Sumit Jangra Nov 06 '17 at 06:01
  • Yes, that or just one function that takes `Any`, but again *I recommend that you name that function something else* and mark my answer as accepted if it helped you. – Coder-256 Nov 06 '17 at 13:24
  • No,it's not helpful for me.I already know that it can work with custom function but i need to redefine,read my question again – Sumit Jangra Nov 07 '17 at 05:04
  • It would help if you could provide some context such as why this is necessary and what exact parameters you will be passing it. I am only asking because this seems to be a very rare use case. – Coder-256 Nov 07 '17 at 12:35
  • Scenerio is We are calling this method in more then 100 classes,so now we are trying to override this method at a one place to handle language fallback situation – Sumit Jangra Nov 10 '17 at 07:17
  • Ok but *what exactly will be passed to it*? – Coder-256 Nov 10 '17 at 15:28