How do I grab all of the checked languages for Localization programmatically in Swift with Xcode 12?
I want to have these languages in an array that I can display as a picker view so the user can seamlessly change the language without having to change the language of their device. I want to avoid hard coding this.
Screenshot of the Localization check boxes in Xcode from my .strings file
This might do the trick:
func localizations() -> [String] {
guard let contentsURLs = try? FileManager.default.contentsOfDirectory(at: Bundle.main.bundleURL, includingPropertiesForKeys: nil) else { return [] }
let identifiers = contentsURLs.compactMap { anURL -> String? in
guard anURL.pathExtension == "lproj" else { return nil }
return anURL.deletingPathExtension().lastPathComponent
}
let humanReadableNames = identifiers.compactMap { anIdentifier in
return Locale.current.localizedString(forIdentifier: anIdentifier)?.localizedCapitalized
}
return humanReadableNames
}
I even transformed them into human readable languages names, instead of having for instance "zh-Hans" for "Chinese (Simplified)".
Of course, if you handle yourself your locale, your might want to use a different Locale.current, but you get the idea...
Related
I have an iOS app where I'm trying to show the current selected language, but also include its variation, i.e. "English (Canada), English (US), ...", as seen in the attached image.
I am already using the preferred language from Bundle, like so:
guard let langCode = Bundle.main.preferredLocalizations.first else { return nil }
let locale = Locale.autoupdatingCurrent
guard let language = locale.localizedString(forLanguageCode: langCode)?.localizedCapitalized else { return nil }
But I also want to include the variation information (Canada, US, etc). Any ideas?
Thank you!
Found the answer.
Just get the identifier as you normally would from the bundle:
guard let langCode = Bundle.main.preferredLocalizations.first else { return nil }
Then use the locale's localizedString(forIdentifier:):
Locale.autoupdatingCurrent.localizedString(forIdentifier: langCode)
This will return "English (Canada)" or whatever the language code is, same like in the Settings app.
My device running iOS 12 and has English language as its primary language and Hebrew as it's secondary language.
Now I'm opening my application with English as it's Base localization.
In the application I have list of three languages: English, Hebrew and French.
At first the attribute Locale.preferredLanguages result is: ["en-IL", "he-IL", "fr-IL"]
If I want to localize my entire application without to change the operating system language I'm changing only AppleLanguages array on UserDefaults like that:
func currentAppleLanguage() -> String {
return (UserDefaults.standard.object(forKey: "AppleLanguages") as? NSArray)?
.firstObject as? String ?? ""
}
func setAppleLanguageTo(lang: String) {
UserDefaults.standard.set([lang], forKey: "AppleLanguages")
}
And after I make that change I'm restarting my application and the language changes.
The thing is after I change AppleLanguages at UserDefaults for example to "he", the Locale.preferredLanguages attribute turns into: ["he"]
So now I don't have the preferredLanguages fallback localizations that set on the operating system at the settings application.
Moreover, I'd like to now how can I get the current running device language on the operating system even after I change the application language with AppleLanguages like facebook does
I'd like to mention I notice that when I edit the running scheme of Application Language to another language it changes the AppleLanguages as well.
Instead of replacing the array of "AppleLanguages" with just a single value, update the existing list so the specified language is moved to the top.
func currentAppleLanguage() -> String {
return UserDefaults.standard.stringArray(forKey: "AppleLanguages")?.first ?? ""
}
func setAppleLanguageTo(lang: String) {
// Get the current list
var languages = UserDefaults.standard.stringArray(forKey: "AppleLanguages") ?? []
// Get all locales using the specified language
let matching = languages.filter { $0.hasPrefix(lang) }
if matching.count > 0 {
// Remove those entries from the list
languages.removeAll { $0 == lang }
// Add them back at the start of the list
languages.insert(contentsOf: matching, at: 0)
} else {
// It wasn't found in the list so add it at the top
languages.insert(lang, at: 0)
}
UserDefaults.standard.set(languages, forKey: "AppleLanguages")
}
This keeps the full list. It just reorders the values so the desired language is first.
I was doing localization of an app in which I face an issue regarding dialects of a country's language. My main question is, is there any provision of adding custom language.
Eg:
Suppose there are two languages:
PL for Poland
UK for Ukraine
I need to support pl-uk i.e Poland Ukraini
Adding a pl-UK.lproj would have made sense if this dialect could be chosen from the system preferences, which is not the case. If you have a local setting, I'm afraid there's no other solution than managing the localisations yourself - and it won't work for Interface Builder files.
The simplest is to store all the pl-UK differences in a separate file (it can be a .strings that you store into the pl.lproj folder (that you localise in Polish Polish - to respect the semantics of the system). Then in a custom function, you load those strings:
func localize(_ string : String, comment: comment) {
guard !isUkrainianPolish else {
return NSLocalizedString(string, comment: comment)
}
// retrieve the cache and check if a key with string exists
if let url = Bundle.main.url(forResource: "localizable_pl_UK" /* or any other name*/, withExtension: "strings", subdirectory: nil, localization:"pl"),
let data = try? Data(contentsOf: url),
let plist = (try? PropertyListSerialization.propertyList(from: data, options: [], format: nil)) as? [String:String] {
// cache the dictionary where you want
return plist[string] ?? NSLocalizedString(string, comment: comment)
}
}
Depending on the organisation of your code, you can implement the function in a singleton or the class that handle localizations.
I am creating an app that for now I would like to offer in English and German. I checked the Base localization mark in the project configuration and added German. I left English as the development language.
Then I created a file Translation.plist which basically consists of dictionaries that I call categories, e.g. I have one dictionary for button texts, label texts etc. Each of the category dictionaries again consists of dictionaries that contain two Strings: value and comment. Translation.plist is localized via XCode. The folders Base.lproj, en.lproj and de.lproj exist and contain a copy of the plist-file as expected.
Then I created a class Translator.swift that is supposed to load the Translation.plist file as an NSDictionary depending on the user's preferred locale. The code looks like this:
func relevantDictionary(category: String) -> NSDictionary {
let preferredLocale = Bundle.main.preferredLocalizations.first ?? "Base"
NSLog("User's preferred locale is \(preferredLocale)")
guard let url = Bundle.main.url(forResource: "Translation", withExtension: "plist") else {
fatalError("Could not find Translation.plist")
}
NSLog("Using \(url.absoluteURL) for translation")
guard let root = NSDictionary(contentsOf: url) else {
fatalError("Could not find dictionary for category (locale=\(preferredLocale)")
}
guard let relevant = root.value(forKey: category) as? NSDictionary else {
fatalError("Could not create dictionary from Translation.plist")
}
return relevant
}
Then I created a String extension that uses the Translator as follows:
func localize(category: String) -> String {
return Translator.instance.translate(category: category, string: self)
}
With this I call the Translator by "yes".localize("button"). In English I would expect "Yes", in German I would expect "Ja". The log says the following:
2017-07-05 08:45:24.728 myApp[13598:35048360] User's preferred locale is de_DE
2017-07-05 08:45:24.728 myApp[13598:35048360] Using file:///Users/me/Library/Developer/CoreSimulator/Devices/A39D3318-943D-4EFE-BB97-5C2218279132/data/Containers/Bundle/Application/4614E696-B52E-4C30-BBE8-3C76F6392413/myApp.app/Base.lproj/Translation.plist for translation
I wonder why this happens and what I have missed. I would have expected that de.lproj/Translation.plist is loaded instead of Base.lproj/Translation.plist.
Any help is highly appreciated.
You can do it with single .plist file. You don't need to create different .plist files for it.
Firstly, Add English and German countries with locale in Project -> info
https://i.stack.imgur.com/M4QIY.png
Once, you added countries with locale in Project -> info then add localizable.strings file in your bundle.
https://i.stack.imgur.com/lnjgL.png
At the end, just add country's locale in your language support class.
NSArray* languages = #[#"en", #"de"];`enter code here`
NSString *current = [languages objectAtIndex:0];
[self setLanguage:current];
Hope it would help you.
I am creating an iMessage Sticker Pack Extension that have stickers that contain texts. I don't see any option in Xcode to localize the stickers? Is it possible to localize stickers?
Maybe it's obvious, but you can manage it inside of an extension. If you create not a pack, but extension, you can fetch exact stickers for your localization
If you want to show different sticker-assets dependant on e.g. your device's language, you need to create a iMessage Application instead of a Sticker Pack Application.
And you need to write some code as it's not possible to have this behaviour within a simple Sticker Pack Application.
However this is pretty simple. For a start, follow this tutorial:
http://blog.qbits.eu/ios-10-stickers-application/
Some code-syntax there is outdated, but XCode will help you to easily fix that.
You can place your localized resources inside different folders and drag'n'drop them into you XCode project (make sure to check "Create folder references"):
Screenshot of project structure
You can then do something like this in your viewDidLoad():
//-- get current language set
var imageSetPath = "/EN"; //-- default to english
let languageCode = NSLocale.preferredLanguages[0]
if languageCode.hasPrefix("zh-Hans") { imageSetPath = "/CNS" }
else if languageCode.hasPrefix("zh-Hans") { imageSetPath = "/CNT" }
else if languageCode.hasPrefix("ko") { imageSetPath = "/KR" }
else if languageCode.hasPrefix("ja") { imageSetPath = "/JP" }
//-- load localized stickers
imageUrls = recursivePathsForResources(path: Bundle.main.bundlePath + imageSetPath, type: "png")
loadStickers(urls: imageUrls)
where
func recursivePathsForResources(path: String, type: String) -> [URL] {
// Enumerators are recursive
let enumerator = FileManager.default.enumerator(atPath: path)
var filePaths = [URL]()
while let filePath = enumerator?.nextObject() as? String {
if NSURL(fileURLWithPath: filePath).pathExtension == type {
let url = URL(fileURLWithPath: path).appendingPathComponent(filePath)
filePaths.append(url)
}
}
return filePaths
}
(altered from https://stackoverflow.com/a/5860015/6649403)