32

I am trying to localize markers shown in my AppleMapView using SwiftUI.

However, MKAnnotation's marker title's type is fixed to String. And I don't want to inherit or create custom class because it is too bothering.

What I need is just cast LocalizedStringKey to String to set marker's title. Any help on this?

HangarRash
  • 7,314
  • 5
  • 5
  • 32
jonye._.jin
  • 591
  • 1
  • 5
  • 17
  • 3
    I would do this in opposite order, store/use everywhere String (because it is model) and only in needed places created LocalizedStringKey(String) (because it is UI-only). – Asperi Mar 25 '20 at 04:28
  • @Asperi I don't get it. MKAnnotation().title 's type is String and that's the problem. – jonye._.jin Mar 25 '20 at 05:25

7 Answers7

31

EDIT: This answer has been edited once for cleaner code and a bit better performance in stringKey.

LocalizedStringKey has a member called key which contains the key string which corresponds to the localized string that is in the localization files. We can't access the key directly unfortunately, so we need to workaround getting the key.

// An Example that won't work:
let localizedKey = LocalizedStringKey.init("SOME_LOCALIZED_KEY_HERE")

localizedKey.key // ERRROOOOORR! `key` is an internal member of `LocalizedStringKey` and you can't access it! 

workaround extension, plus an example of how it works, to get the key out of the LocalizedStringKey:

extension LocalizedStringKey {

    // This will mirror the `LocalizedStringKey` so it can access its 
    // internal `key` property. Mirroring is rather expensive, but it 
    // should be fine performance-wise, unless you are  
    // using it too much or doing something out of the norm.
    var stringKey: String? {
        Mirror(reflecting: self).children.first(where: { $0.label == "key" })?.value as? String
    }
}

// An Example:
let localizedKey = LocalizedStringKey("KEY_HERE")
print(localizedKey.stringkey)
// prints `KEY_HERE`

now that we have the key as an string, you can easily get the localized string which is pointed to by the key of LocalizedStringKey.

extension String {
    static func localizedString(for key: String,
                                locale: Locale = .current) -> String {
        
        let language = locale.languageCode
        let path = Bundle.main.path(forResource: language, ofType: "lproj")!
        let bundle = Bundle(path: path)!
        let localizedString = NSLocalizedString(key, bundle: bundle, comment: "")
        
        return localizedString
    }
}

to understand this, take a look at https://stackoverflow.com/a/27879342/11837341

now you can easily convert a LocalizedStringKey's value to string:

extension LocalizedStringKey {
func stringValue(locale: Locale = .current) -> String {
        return .localizedString(for: self.stringKey, locale: locale)
    }
}

TL; DR (Summary)

add these extensions to your project:

extension LocalizedStringKey {
    var stringKey: String? {
        Mirror(reflecting: self).children.first(where: { $0.label == "key" })?.value as? String
    }
}

extension String {
    static func localizedString(for key: String,
                                locale: Locale = .current) -> String {
        
        let language = locale.languageCode
        let path = Bundle.main.path(forResource: language, ofType: "lproj")!
        let bundle = Bundle(path: path)!
        let localizedString = NSLocalizedString(key, bundle: bundle, comment: "")
        
        return localizedString
    }
}

extension LocalizedStringKey {
    func stringValue(locale: Locale = .current) -> String {
        return .localizedString(for: self.stringKey, locale: locale)
    }
}

Examples

let localizedKey = LocalizedStringKey("KEY_NAME_HERE")

print(localizedKey.stringKey)
//prints `KEY_NAME_HERE`

print(localizedKey.stringValue())
// prints Localized value of `KEY_NAME_HERE`
// DOESNT print `KEY_NAME_HERE`
Mahdi BM
  • 1,826
  • 13
  • 16
  • This might go without saying but since this is relying on undocumented, internal representations of `LocalizedStringKey` this is brittle. `components[1][0]` could cause crashes if the output of description ever changes (which could happen with any new version of SwiftUI). – Devin McKaskle May 21 '22 at 17:48
  • Yes you are correct. In production you should be safe and use guard, if let etc... at the time of writing this answer i wasn't too much worried about that, but now that i see more people are upvoting this answer, i might as well edit so it doesn't possibly make problems for people in a future version of SwiftUI – Mahdi BM May 22 '22 at 09:14
  • 3
    For everyone who will stumble upon this answer, please have a look at the native solution from @rafael-bitencourt. It is as simple as `String(localized: "YOUR_LOCALIZED_KEY")` – herve May 25 '22 at 12:55
  • "Type of expression is ambiguous without more context" on the reflecting string – Andrew_STOP_RU_WAR_IN_UA Aug 25 '22 at 08:23
  • 1
    @Andrew____Pls_Support_Ukraine fixed. only needed adding `as? String`. – Mahdi BM Aug 25 '22 at 21:04
22

You can do it simply by using: String(localized: "YOUR_LOCALIZED_KEY")

If your localized key is programmatically generated, you must store it in a variable that conforms to "LocalizationValue" like this:

let localizedKey = String.LocalizationValue(stringLiteral: yourLocalizedVar)

Then you can use String(localized: localizedKey) to get your localized text content.

Mahdi BM's solution has a problem because Swift returns only the language code even if you use variants of a language like Spanish, Portuguese, and so many others. The language code for both examples will always return ES and PT, but the names of folders with the localized keys will be different: PT can be 'pt-PT' or 'pt-BR', Spanish can be 'es' or 'es-419' (Latin America) and these cases will cause your app to crash. Locale is also a terrible idea to get this reference because it's composed by both current language and region, both can be from different places, example, if the user is from Japan but is in the US, the user's locale will very likely be jp_US@japaneseCalendar.

3

You can use NSLocalizedString.

let localizedString = NSLocalizedString("LOCALIZED-STRING-KEY", comment: "Describe what is being localized here")
emjeexyz
  • 376
  • 3
  • 6
  • 7
    not relevant to SwiftUI. LocalizedStringKey is a type. – Tiziano Coroneo Jul 28 '20 at 13:35
  • 8
    I understood @jonye._.jin's problem as that he needs to provide a string but he wants to display the title in different languages/localize it. I know this isn't the cast originally asked for but it solves the problem. In the end there is the translation as a String. – emjeexyz Jul 29 '20 at 14:32
1

I modified the top answer to compile on Xcode14, remove forced unwraps and to return the key when a translation wasn't found:

extension LocalizedStringKey {
    var stringKey: String? {
        Mirror(reflecting: self).children.first(where: { $0.label == "key" })?.value as? String
    }
    
    func stringValue(locale: Locale = .current) -> String? {
        guard let stringKey = self.stringKey else { return nil }
        let language = locale.languageCode
        guard let path = Bundle.main.path(forResource: language, ofType: "lproj") else { return stringKey }
        guard let bundle = Bundle(path: path) else { return stringKey }
        let localizedString = NSLocalizedString(stringKey, bundle: bundle, comment: "")
        return localizedString
    }
}
Learn OpenGL ES
  • 4,759
  • 1
  • 36
  • 38
0

I am currently using this, but I would like something official and documented. The deployment target is iOS 14

extension String {
    func slice(start: String, end: String) -> String? {
        (range(of: start)?.upperBound).flatMap { substringFrom in
            (range(of: end, range: substringFrom..<endIndex)?.lowerBound).map { substringTo in
                String(self[substringFrom..<substringTo])
            }
        }
    }
}

extension LocalizedStringKey {
    /// Extracts the string value from LocalizedStringKey.
    var string: String {
        "\(self)".slice(start: "LocalizedStringKey(key: \"", end: "\", hasFormatting") ?? "Bad string"
    }
}
Manicek
  • 71
  • 1
  • 3
-1

2022

  • Works well on macOS 12 and higher.
  • Works in most cases on macOS 10.15 and higher.
import Foundation
import SwiftUI

@available(macOS 10.15, *)
public extension LocalizedStringKey {
    private var keyStr: String {
        return "\(self)".keyFromLocalizedStringKey
    }
    
    func localizedStr(locale: Locale = .current) -> String {
        if #available(macOS 12, *) {
            return String(localized: "\(self.keyStr)")
        } else {
            return String.localizedString(for: self.keyStr, locale: locale)
        }
    }
}

fileprivate extension String {
    static func localizedString(for key: String, locale: Locale = .current) -> String {
            let language = locale.languageCode
            
            guard let path = Bundle.main.path(forResource: language, ofType: "lproj"),
                  let bundle = Bundle(path: path)
            else { return "Failed To Get Localized String" }
            
            let localizedString = NSLocalizedString(key, bundle: bundle, comment: "")
            
            return localizedString
    }
    
    var keyFromLocalizedStringKey: String {
        let comp2 = self.substring(from: 25).components(separatedBy:"\", hasFormatting")
        
        if comp2.count == 2 {
            return comp2.first!
        }
        
        return "failed to get stringKey"
    }
}

public extension String {
    func substring(from: Int) -> String {
        return self[min(from, count) ..< count]
    }
}

usage:

let lsk = LocalizedStringKey("KEY_NAME_HERE")

let key = lsk.keyStr
let translatedToCurrLocale = lsk.localizedStr()
Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101
  • let comp2 = self.substring(from: 25)... // Cannot convert value of type 'Int' to expected argument type 'String.Index' – Arif Acar May 10 '23 at 20:08
-3

Add an extension to the string to read the localized language

extension String {
    func localized() -> String {
        let path = Bundle.main.path(forResource: "your language", ofType: "lproj")!
        if let bundle = Bundle(path: path) {
            let str = bundle.localizedString(forKey: self, value: nil, table: nil)
            return str
        }
        return ""
    }
}

Use LocalizedStringKey to load sample code

let title: String = "LocalizedStringKey".localized()
Tema Tian
  • 331
  • 3
  • 12