How do I make a CFLocale for a target language? - swift

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)

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\([^\"]

Swift Override Default Init No Subclass

Is it possible to override a default init without subclassing?
I want every instance of DateFormatter created in my app to have the same locale attached to it. Right now, upon each instance creation, I have to:
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX"
Is it possible to have this happen without the need to write out the locale every single time? I'd prefer to not create a DateFormatter subclass as I feel that's a bit of overkill
Thanks!
There is no way to override an existing method of a class without subclassing it or modifying the source code (which you obviously can't do in case of built-in types).
However, for your specific case, you could simply add a method/static variable that returns a DateFormatter specific to your needs.
extension DateFormatter {
static func usPosix() -> DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
return dateFormatter
}
}
And then instead of calling let dateFormatter = DateFormatter(), you do let dateFormatter = DateFormatter.usPosix()
While I personally HIGHLY advise against swizzling, that is certainly an option here.
First, start by creating your own locale that you want to be used throughout your app:
extension DateFormatter {
// Note the "#objc" here, this is necessary for altering the runtime logic
#objc var swizzledLocale: Locale {
return Locale(identifier: "en_US_POSIX")
}
}
Next, you'll want to tell Swift that whenever a DateFormatter's locale is accessed, you want your new swizzledLocale to be used instead:
// get reference to the two getter methods for the properties you want to "switch"
let orig = #selector(getter: DateFormatter.locale)
let new = #selector(getter: DateFormatter.swizzledLocale)
let origMethod = class_getInstanceMethod(DateFormatter.self, orig)!
let newMethod = class_getInstanceMethod(DateFormatter.self, new)!
// performs the actual switch so that whenever "locale" is accessed in code, the returned value will be the value of "swizzledLocale"
method_exchangeImplementations(origMethod, newMethod)
And that's all there is to it! Now whenever you access any DateFormatter's locale, a Locale("en_US_POSIX") will be returned.
Some things to note: first, this will alter ALL instances of locale, not just the locales that you personally use in your own code. That is, if there's any internal code that relies on a formatter's locale, en_US_POSIX will always be used. Second, the second block of code (the one that performs the actual swizzling) should be called exactly one time; look into wrapping it in a dispatch_once block to ensure it only runs once.

Can I create a Date object from a predefined string (typescript)?

I have a value returned in a string (numbers separated by commas) and I'd like to make a Date object out of it. It looks like this is not possible, can someone confirm and/or suggest me a solution.
This does not work :
let dateString='2017,3,22,0';
let dateFromString = new Date(dateString);
This works though (when I pass a list of numbers) :
let dateFromString = new Date(2017,3,22,0);
And this works also :
let dateString = '2008/05/10 12:08:20';
let dateFromString = new Date(dateString);
The goal would be to create a Date object from a uniform string. Is that possible ?
Can I create a Date object from a predefined string, which has only one type of separator (comma, colon, slash or whatever) ?
If your environment is compatible with ES6 (eg. Babel, TypeScript, modern Chrome/Firefox etc), you can use the string's .split(',') and decompose the array into arguments like the following:
const dateString = '2017,3,22,0';
const date = new Date(...dateString.split(',')); // date object for 2017/03/22
ES5 compatible version:
var dateString = '2017,1,2,0';
var date = new (Function.prototype.bind.apply(Date, [null].concat(dateString.split(','))));
As for how the .bind.apply method works with new, you can take a look at Use of .apply() with 'new' operator. Is this possible?
Note: Thanks to the two comments below for spotting my errors šŸ‘

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.