Updating Locale language swift - swift

I get Locale.current.languageCode, but I would like also to set that value. How I can do it?
let currentLanguage = Locale.current.languageCode
I use:
Defaults[.language] = "En"
MOLH.setLanguageTo("en")

i do not think that you can change the current Locale language, just a user can change their language/locale in settings.
static var current: Locale { get }

Related

SwiftUI - Localization of a dynamic Text

I am struggling with the locilization of some of my TextFields.
Usually a "normal" localization of a Text() or TextField() works without any problem in my App if the text I want to translate is hardcoded like this:
Text("English Text")
I translate it in my Localizable.strings like this:
"English Text" = "German Text";
Now I want to translate TextFields which are more dynamic, but where I know each possible entry:
TextField("New note" + (refresh ? "" : " "),text: $newToDo, onCommit: {
self.addToDo()
self.refresh.toggle()
})
(The refresh is necessary because of a SwiftUI bug sometimes not showing the placeholder-text again.)
Another example would be:
func dayWord() -> String {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone.current
dateFormatter.locale = Locale(identifier: "de_DE")
dateFormatter.dateFormat = "EEEE"
return dateFormatter.string(from: self)
}
var day: String {
return data.date.dateFromMilliseconds().dayWord()
}
Text(day.prefix(2))
The Text(day.prefix(2)) has only seven possible states, but I don't know what to write as a key in my Localizable.strings.
Use NSLocalizedString, like
TextField(NSLocalizedString("New note", comment: "") + (refresh ? "" : " "), ...
According to SwiftUI convention, the Text() label is automatically localized only when it has a string "literal". When the text is a variable, you need to use LocalizedStringKey(). Other localizable texts inside Button() or TextField() also require LocalizedStringKey(). So you need to change this to:
Text(LocalizedStringKey(String(day.prefix(2))))
This converts day.prefix(2) into a String, because it is actually a substring, and then calls LocalizedStringKey() to make it localizable. In your Localizable.strings file you could add all 7 possibilities.
"Mo" = "Mo";
"Di" = "Tu";
//etc.
but why would you? Instead, use:
dateFormatter.locale = Locale(identifier: Locale.preferredLanguages.first)
...
Text(day.prefix(2))
to determine the user's current language and display that. Apple returns the text in the proper language, so this text doesn't need to be localized any further.
TextField() does need localization using LocalizedStringKey():
TextField(LocalizedStringKey("New note") + (refresh ? "" : " "),text: $newToDo, onCommit: {
self.addToDo()
self.refresh.toggle()
})
As Asperi points out, for "New note" the same can be accomplished using NSLocalizedString(), which might be better depending on how you like to work. The benefits are: easily adding a comment for the translator, and automatic export into the xliff when you choose Editor -> Export for localization…
By contrast, SwiftUI's LocalizedStringKey() requires you to manually add strings to the Localizable.strings file. For your day.prefix(2) example, I think it would make more sense to get the user's preferred language and display the localized date directly.
SwiftUI Text will only localize literal strings, strings defined in double quotes. You have to either create localized key or retrieve localized string.
Examples
Simple localization with string literal key.
Text("Label")
If you construct the label, you can use LocalizedStringKey function to localize your computed label.
let key = "Label"
let localizedKey = LocalizedStringKey(key)
Text(localizedKey)
You can also get the localized string using NSLocalizedString.
let localizedString = NSLocalizedString("Label", comment: "")
Text(localizedString)
It's also possible to have localized strings accept arguments (#, lld, and lf) through String Interpolation. For example you could have the following localizations in your project Localizable.strings file:
"Name %#" = "Name %#";
"Number %lld" = "%lld is the number";
And you can use it like this:
Text("Label \(object)")
Text("Number \(number)")
Xcode Search
If you want to search for calls of Text that aren't done with literal strings in your projects. You can use Xcode Find Regular Expression feature. View > Navigators > Find or Cmd-4 keyboard shortcut. Change the search type just above the search field to Regular Expression.
Use the following regular expression: Text\([^\"]

Strange static string constant behaviour in swift

I'm declaring two strings
public static let FILTER_PERIODS_KEY = "fi_pe"
public static let FILTER_PERIODS_DEFAULT = "M1,M5,M15,M30,H1,H4,D1"
and use them that way:
if (preferences.object(forKey: InAppProperties.FILTER_PERIODS_KEY) == nil) {
preferences.set(InAppProperties.FILTER_PERIODS_DEFAULT, forKey: InAppProperties.FILTER_PERIODS_KEY)
}
For some reason FILTER_PERIODS_KEY is not empty, while FILTER_PERIODS_DEFAULT is empty.
What is this?
The code looks correct: FILTER_PERIODS_DEFAULT is used as value and FILTER_PERIODS_KEY is used as key.
However your way to define default values for UserDefaults is wrong.
As soon as possible (in awakeFromNib or applicationWillFinishLaunching) add
let defaults = UserDefaults.standard
let defaultValues = [InAppProperties.FILTER_PERIODS_KEY: "M1,M5,M15,M30,H1,H4,D1"]
defaults.register(defaults: defaultValues)
The code has to be executed every time the application launches even if the value has changed meanwhile.

How do I make a CFLocale for a target language?

In Objective C, one can create a CFLocale as follows (taken from this post in 2012):
CFLocaleRef myLocale = CFLocaleCopyCurrent() for the current locale; or:
CFLocaleRef myLocale = CFLocaleCreate(kCFAllocatorDefault, CFSTR("ja")), for a target locale. The locale name comes from the rightmost column of the ISO 639-1/639-2 table, which Apple specifies as their standard for language codes here.*
*Note: very old code examples refer to long language codes like 'Japanese', as may be expected by versions of Mac OS X older than 10.4.
How does one create a CFLocale in Swift 3, as the API appears to have changed in several ways?
CFLocale is toll-free bridged to NSLocale, so you can simply call
let myLocale = NSLocale(localeIdentifier: "ja")
// or
let myLocale = NSLocale(localeIdentifier: NSLocale.canonicalLocaleIdentifier(from: "Japanese"))
depending on whether you have a ISO 639-1 language code or not.
The corresponding Swift 3 "overlay value type" Locale
(which is used by Calendar, DateFormatter, ...,
compare SE-0069 Mutability and Foundation Value Types)
can similarly be created with
let myLocale = Locale(identifier: "ja")
// or
let myLocale = Locale(identifier: Locale.canonicalIdentifier(from: "Japanese"))
Here are the API changes to Swift since the Objective-C example given from 2012:
CFLocaleRef has been replaced by CFLocale.
CFStringRef has been replaced by CFString, which may be created by a regular String cast to CFString type: "ja" as CFString!.
CFLocaleCreate() now expects a CFLocaleIdentifier rather than just a CFString, so we must provide one using CFLocaleCreateCanonicalLanguageIdentifierFromString().
This can be done with the following two lines:
let localeIdentifier: CFLocaleIdentifier = CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorDefault, "ja" as CFString!)
let locale: CFLocale = CFLocaleCreate(kCFAllocatorDefault, localeIdentifier)

Why does NSLocale.current.identifier include currency on macOS?

If I request the current locales identifier on iOS, it returns just the identifier string:
let identifier = NSLocale.current.identifier // en_GB
However, on macOS 10.12.2 it also returns the currency:
let identifier = NSLocale.current.identifier // en_GB#currency=GBP
Is this a bug or expected behaviour?
I ran into this recently. I'm not sure why, but apparently as of 10.12, localeIdentifier can include a bunch of stuff besides country and language.
Unfortunately, the documentation doesn't elaborate on the cases in which other metadata is included.
However, also as of 10.12, there's another property languageCode, which, in conjunction with countryCode, you can use to generate en_US.
This is what iTerm2 does.
I think the best option for me here is to generate the code myself. To help with this I have created an extension on Locale:
extension Locale {
var iso3166code: String {
guard
let language = languageCode,
let region = regionCode
else { return "en-US" }
return "\(language)-\(region)"
}
}
While this is accurate enough for my purposes, you should probably ensure it returns expected values for your project.

Use value of variable for property lookup

I am trying to build a table of current locale properties in code, and have encountered issues with trying to pass the value of a variable to a function:
let currentLocale = Locale(identifier: "en_US")
let calendar1 = currentLocale.calendar // "gregorian (fixed)"
let propertyName = "calendar"
let calendar2 = currentLocale.propertyName // Error: Value of type 'Locale' has no member 'porpertyName'
In the last line of code above, the instance of Locale thinks I am passing it "propertyName" rather than the contents of the variable "calendar".
Is there any way to pass the value of propertyName ("calendar") to the instance of Locale? I know that in other languages, you can prepend the variable name like '$propertyName', and that tells it to read the value of the variable.
I want to keep this pure Swift if possible.
You are looking for some form of key-value coding.
It's a little tricky, in that this is a purely Objective-C feature of Cocoa, so it doesn't work with the Swift overlay class Locale; you will have to cast currentLocale to Objective-C NSLocale. Moreover, NSLocale exposes its attributes through special NSLocale.Key types. After a great deal of casting, I find that this works:
let calendar2 =
(currentLocale as NSLocale).object(forKey:NSLocale.Key(rawValue:propertyName))
calendar2 is typed as Any but you can cast it down to a String.