How to get OS language and locale? - ios

My app changes the language at runtime and advises the user to restart the app. I do it like this:
typealias LanguageLocaleType = (language: String?, locale: String?, title: String)
let elements: [LanguageLocaleType] = [
(nil, nil, "System Default"),
("en", "en_US", "English"),
("ar", "ar_SA", "Arabic"),
]
//...func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)...
let element = elements[indexPath.row]
guard let language = element.language else {
// How to really reset app back to OS language/locale???
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
UserDefaults.standard.removeObject(forKey: "AppleLocale")
return
}
UserDefaults.standard.set([language], forKey: "AppleLanguages")
if let locale = element.locale, !locale.isEmpty,
Locale.current.identifier != locale,
!Locale.current.identifier.hasPrefix("\(language)_") {
UserDefaults.standard.set(locale, forKey: "AppleLocale")
}
I want to offer to set languages in a list with the right one selected, one of which being offered it to set language back to System Default. However, there's no way to find the OS-level language and locale that I could find. Since after I mess around with setting the UserDefaults, Bundle.main.preferredLocalizations is not reliable and do not match the system default (event when I remove the key from the User Default).
Is there a way to get the OS-level language and locale instead of the app-level?

You should be able to get currently selected OS language like this.
let language = NSLocale.preferredLanguages[0]
if this is not what you are looking for, refer to this answer
https://stackoverflow.com/a/30750120/809821

I will recommend you to use this library from Cocoa pods.
In your Podfile add this line:
pod 'Localize-Swift', '~> 1.7'
An then install your pods using this command:
pod install
Then if you don't have Localizable.strings, for example Localizable.strings (English) file add this lines.
"LD" = "Loading ...";
"CAMP" = "Campus";
"MAP" = "MAP";
The first word is like your TAG and the next word is your value .
Then in your ViewController you can use your string like this.
Example of use:
let progressHUD = ProgressHUD(text: "LD".localized())
let progressHUD = ProgressHUD(text: "CAMP".localized())
let progressHUD = ProgressHUD(text: "MAP".localized())
And that is all.
I hope this help.

I found an answer which seems to do something similar, by playing with the Bundle language files of your app. As in setting the proper one.
While you state that playing with Bundle properties seems to fiddle with your use case, I'd look into saving the setted language with Core Data, inside an app file, or inside and XML (.plist). You could set up a checking method in didFinishWithOptions which checks the presence of this variable then runs the language prompt if absent or leaves as is if present (adding a check to make sure the right language is set would be wise). Since this does not alter the Settings-set language, you'd always have a quick reference point outside of your app through NSLocale to switch back to it with ease if the user so chooses.
Here's the post
Disclaimer 1 : i haven't tested this, but it seems like a plausible, and Apple-Tolerated thing to do.
Disclaimer 2 : as the OP in the link states, and as some have mentioned here, changing app language is not recommended by Apple. Notably due to NSLocale keys, and how frequently Swift is updated which could lead to your code being unuseable or requiring massive overhaul.. As i mentioned in a comment above, i'd reconsider this use-case sadly :(

In reference to your question above you can either as stated above create your own bundle or use a prebuilt class like the following:https://github.com/mumensh/iOS-Localization

Related

How to keep a clean production version of your iOS App?

I develop an iOS App called Swordy Quest:
https://apps.apple.com/us/app/swordy-quest-an-rpg-adventure/id1446641513
It contains Game Center integration for Leaderboards, Achievements, Player vs Player (PVP) matchmaking and Clans.
I have a local test version that I use when developing (with a test bundleID). I also have a production version of my game that I use to play the game and progress as if I was a customer. However, in order to upgrade/implement the Game Center functionality above, I need to use my production bundleID for testing. This then overwrites my 'customer game' with all my test data (ruining my 'natural' progress).
So I am wondering, is it possible to have a 'clean' production version of an app and still have a separate test version that allows me to test Game Center functionality. Or is there some way to restore a previous app state in Xcode so I could save my production clean version before polluting it with test data? I know in Mac Apps you can change the custom working directory, but I don't think you can in iOS?
I have looked into backing up my Production version of the app before working on Game Center upgrades, but it looks like this is probably not possible? Has anyone come up with a clever way around this?
Please note I have stored both CoreData and UserDefaults in the app.
Custom working directory is something only command-line tool projects. ChangeCurrentDirectoryPath option is no longer available at this place as the screenshot below in XCode 4.6.1. Sounds crazy but you can try downgrade to Xcode 4 and make it happen.
Or you will need load files using Cocoa’s NSBundle class or Core Foundation’s CFBundle functions. So make duplicate target for your Swordy Quest test. It will not affect your clean copy.
Manage schemes:
Finally click the little gear button create a clean copy to avoid touch your production code.
After you set up your keys both product and test where
Build Settings > Packaging ( write to filter Packaging )
Implement as a code below to your logic function ( for example implement in it to a function which trigger a GameHomeVC from LoginPlayerVC )
var key: String?
#if TARGET_PROD || TARGET_STORE
key = #"prodKey";
#else
key = #"testKey";
as a precursor, i'm not familiar with Game Center, so there may be concerns there that i haven't accounted for. so, with that, my instinct in solving this starts out with launch arguments. there is a great article on how to do this here: https://www.swiftbysundell.com/articles/launch-arguments-in-swift/.
Now that you're able to start changing behavior based off of launch arguments from different schemes, you can start to look at how to segment your test / prod data.
As I'm not a CoreData expert, i can't say with 100% confidence that this is possible (or easy), but i would investigate how to setup separate persistent stores based off of a launch argument. using this article as a reference, it seems like you could roughly do something like the below after creating a -testGameCenter launch argument to a new TestGameCenter scheme to create an in-memory data store when testing Game Center
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "YourDataStore")
if CommandLine.arguments.contains("-testGameCenter") {
let description = NSPersistentStoreDescription()
description.url = URL(fileURLWithPath: "/dev/null")
container.persistentStoreDescriptions = [description]
}
container.loadPersistentStores(completionHandler: { _, error in
if let error = error as NSError? {
fatalError("Failed to load stores: \(error), \(error.userInfo)")
}
})
return container
}()
if you're able to solve the CoreData problem above, it's time to start looking at how to segment your UserDefaults data. this gross but easy solution that immediately comes to mind is prefixing your UserDefault keys with test when running from your test scheme. below is an example of how could structure a wrapper around UserDefaults to manage this
struct UserDefaultsWrapper {
let userDefaults: UserDefaults
let keyPrefix: String
init(userDefaults: UserDefaults, keyPrefix: String) {
self.userDefaults = userDefaults
self.keyPrefix = keyPrefix
}
func setValue(_ value: Any?, forKey key: String) {
self.userDefaults.setValue(value, forKey: prefixedKey(forKey: key))
}
func value(forKey key: String) -> Any? {
self.userDefaults.value(forKey: prefixedKey(forKey: key))
}
func prefixedKey(forKey key: String) -> String {
return "\(keyPrefix)\(key)}"
}
}
where you could make use of the wrapper like so
let userDefaultsPrefix = CommandLine.arguments.contains("-testGameCenter") ? "testGameCenter_" : ""
let userDefaultsWrapper = UserDefaultsWrapper(userDefaults: .standard, keyPrefix: userDefaultsPrefix)
to get something more elegant, you could look a little more into UserDefaults to see if you could apply a solution similar to the one for CoreData where there are two entirely separate stores. from a quick glance at this initializer, maybe you could do something as simple as this with your wrapper instead
struct UserDefaultsWrapper {
let userDefaults: UserDefaults
init(userDefaults: UserDefaults) {
self.userDefaults = userDefaults
}
func setValue(_ value: Any?, forKey key: String) {
self.userDefaults.setValue(value, forKey: key)
}
func value(forKey key: String) -> Any? {
self.userDefaults.value(forKey: key)
}
}
where you construct it like so
let userDefaultsSuiteName: String? = CommandLine.arguments.contains("-testGameCenter") ? myTestingGameCenterSuiteName : nil
let userDefaults = UserDefaults(suiteName: userDefaultsSuiteName)
let userDefaultsWrapper = UserDefaultsWrapper(userDefaults: userDefaults)
lastly, from a comment you made on another reply, it sounds like you are also concerned with fresh install scenarios. that said, the approaches i've outlined will not help (at least i don't think) with persisting data across deletes/installs. but, what i think you should think about is if it's necessary to test those delete/install concerns from your production bundle id. could you instead either manually test those concerns from your test bundle id and/or write unit tests around the components that involve those concerns? when you are approaching your testing strategy, it's important to make sure that you're testing the right things at the right layers; testing the wrong things at the wrong layers makes each testing layer much, much harder to execute
Targets is designed to do just that. You set pre-processor macros values to get the compiler to compile specific code based on target / macros values.
In your case, you change path to the customer game / test data file based on selected the target / macro combination.
You can also set a different bundleID for each target.
Once this is all setup you simply just switch between target and compile. The whole thing should just work seamlessly.
Make a backup of your project and then follow this tutorial which covers exactly how to do this:
https://www.appcoda.com/using-xcode-targets/
If the link above is broken in future, just search "Xcode target tutorials"

Using the wrong Localizable.string

I have an iOS app with a default language (English).
It also has a French localization and works as expected in both languages.
Here is the problem I am having:
When setting the device to a language other than English(default) or a language for which localization is provided (for example setting the device to Japanese); the app does not fall as expected in the default language. But it keeps the last language used for the app.
Why is that? And how can I fix it?
I have found a few post with a similar issue, but the solutions proposed did not work for me. For example this one, where the problem is similar to mine.
I am using Xcode version 11.1 and iOS version 12.4.2.
iOS will have a list of preferred language order based on the previously selected languages.
So, if you changed from English then to an unmapped language (e.g ko), the application will have as AppleLanguages the array:
[ko-(ZoneCode), en-(ZoneCode)].
You can avoid this procedure using this code below:
let defaultCultureCode: String = "en"
let defaults = UserDefaults.standard
let currentAppleLanguages = defaults.stringArray(forKey: "AppleLanguages")
if let currentLanguages = currentAppleLanguages {
if(!currentLanguages.isEmpty && !(currentLanguages.first?.contains(["en", "fr"]))!) {
defaults.removeObject(forKey: "AppleLanguages")
defaults.set([defaultCultureCode], forKey: "AppleLanguages")
defaults.synchronize()
}
}
I used this string extension to check if the current languages contain the available language:
extension String {
public func contains(_ elements: [String]) -> Bool {
var haveElementOnString = false
if elements.count == 0 { return false }
else { elements.forEach{ element in haveElementOnString = haveElementOnString || self.contains(element)} }
return haveElementOnString
}
}
The next step it's up to you to define the best strategy.
In my application context, it will always be defined as English by default because it is the only .strings file available. All the other supported strings will be downloaded and then a message will be displayed to the user warning that new languages are available and will be applied next time.
There's a lot of information on StackOverflow about bundle.localizedStringForKey or NSLocalizedString that could help you to found the best solution for what you need.
Also, this is only the validation and update for the current locale and the default language. this doesn't include the Region section (e.g. ko-KR (KR)).
I found an interesting text on this topic that could be useful
How not to do localization.
I hope it solves your problem!

Swift: How to change the language inside of the app?

I have an app with localized string files.
Here are what my localized string files look like.
I also have a tableView with cells containing accessoryType (check marks). I want to let the user change the language of my app when changing the check in the tableView.
Here are the codes I have in the tableView of the settings of the app:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.setValues(value: languageArray[indexPath.row]) // Upload new language settings to the server
// Here I want to set the language of my app to the selected value: languageArray[indexPath.row]
// For example, languageArray[indexPath.row] is "en"
var array = tableView.visibleCells
for i in 0 ..< array.count {
let cells: UITableViewCell = array[i]
cells.accessoryType = .none
}
let cell:UITableViewCell! = tableView.cellForRow(at: indexPath)
cell.accessoryType = .checkmark
}
Thanks!
As most of answer saying "this is not possible" but,
It is possible and can be done it by simple thing.
In iOS application there are bundle for each languages, like en.lproj for english. You just need to load that specific bundle from selected language and get localizable stuff like: string, images etc. from this bundle.
var selectedLanguage = "en"
let path = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj")
let bundle = Bundle(path: path!)
let localizedText = bundle!.localizedString(forKey: YOUR_TEXT_KEY, value: YOUR_TEXT_COMMENT, table: nil)
For reference, i cerated an LanguageDemo app, you can found all implementation and complete source code.
There is a way to do it, but as the previous answers mentioned it's not the best idea and might no be approved by Apple app review. But here is what I've tried and it worked:
UserDefaults.standard.set(["en"], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
Whenever the user select the language you need to apply this code, just replace ["en"] with your selected language. Keep in mind the user need to exit the app in order for this to work properly.
I know "this is not possible" answers are not really popular in Stack Overflow, but anyhow..
What you want to do is not possible. iOS doesn't allow you to change the language of the app from within the app. There is no API for that, and even if you somehow manage to achieve this thing by hacking (monkey patching) iOS, Apple wouldn't allow your app to be published to the itunes store.
There is a language setting within the "Settings" app (a system app), and there the user can set the language. All installed apps will use this language setting.
It is impossible to change the language of the iphone, but if you just want to change your app's language, I think there is the way: you can save the map as a plist or json file but the system .strings. And do not use the func NSLocalizedString, just encapsulation a func to get it, all the thing will be on the control.
This is so untrue about publishing etc, there is a way and you are not hacking anything, it is true there is not api for that but it is only a few lines of code, and it is nothing complicated... http://aplus.rs/2017/in-app-language-change-in-ios-app/
there is the link for your question, we have that o0n academy and this guy is our professor, if there is something you don't understand i am glad to help you with this because i got the same problem 2 years a go...

switching app language without restarting the app swift

I want my app language to change on button click..
My code is:
#IBAction func convertlang(_ sender: Any) {
if L102Language.currentAppleLanguage() == "en" {
L102Language.setAppleLAnguageTo(lang: "ar")
UIView.appearance().semanticContentAttribute = .forceRightToLeft
} else {
L102Language.setAppleLAnguageTo(lang: "en")
UIView.appearance().semanticContentAttribute = .forceLeftToRight
}
}
let APPLE_LANGUAGE_KEY = "AppleLanguages"
class L102Language {
class func currentAppleLanguage() -> String{
let userdef = UserDefaults.standard
let langArray = userdef.object(forKey: APPLE_LANGUAGE_KEY) as! NSArray
let current = langArray.firstObject as! String
return current
}
class func setAppleLAnguageTo(lang: String) {
let userdef = UserDefaults.standard
userdef.set([lang,currentAppleLanguage()], forKey: APPLE_LANGUAGE_KEY)
userdef.synchronize()
}}
and this works fine and convert the language when i click on the button..
the problem is i need to restart the app to see the language change..
i searched for this but most of them were for objective-c and tried some for swift but didn't work..
how to do this?
You can't change the language at runtime with normal button click. If you really need that, you need to use a custom localization system instead.
To change the app language during runtime, I have done this manually by creating two different storyboards. I saved the language preference in NSUserDefaults but NOT with the key AppleLanguages and then called AppDelegate's didFinishLaunchingWithOptions method to select storyboard.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainEnglish" bundle:nil];
UINavigationController *navigationcontroller=[[UINavigationController alloc]init];
RegistorViewController *registor=[storyboard instantiateViewControllerWithIdentifier:#"Registor"];
[self.window setRootViewController:navigationcontroller];
[self.window makeKeyAndVisible];
[navigationvontroller pushViewController:registor animated:NO];
Though this is not what apple wants a developer to do in runtime.
Someone even asked an Apple engineer and this is the response:
In general, you should not change the iOS system language (via use of the AppleLanguages pref key) from within your application. This goes against the basic iOS user model for switching languages in the Settings app, and also uses a preference key that is not documented, meaning that at some point in the future, the key name could change, which would break your application.
If you want to switch languages in your application, you can do so via manually loading resource files in your bundle. You can use NSBundle:pathForResource:ofType:inDirectory:forLocalization: for this purpose, but keep in mind that your application would be responsible for all loading of localized data.
I had faced the same problem in my project recently. You are using the correct code to change app language but it change only string file data not the storyboard. For complete localisation you have to restart the app again, because there is no any another way to do this. You can show a popup to user "Please restart your app to change language completely". Because we can kill the app programatically, but after doing this there is no way to restart the app again.
This might not be the most iOS-typical way of doing things, but I tend to treat static content like any other data in the app, especially when I'm using VIPER (although other patterns would work) and a unidirectional data flow.
With that in mind, for this I would have a service accessing a store of static content keyed of the user's language preference. When that changes I would trigger a refresh of the content.
This has worked well for projects when I also have server data as well as local data that needs to be localised.
To be fair, I've never used the standard tools much. And I suspect this may not be ideal for right-to-left language types.

Localized Strings are always in English in UITests (Snapshot)

I'm pretty new to Fastlane and love the idea of Snapshot, but I got a little problem.
When I'm trying to create a new set of screenshots I'm facing the problem that the UITests don't use the correct localized Strings to fetch the UI elements.
My current state is based on this Stack entries: XCode 7 UITests with localized UI
The localization method:
func localizedString(_ key: String) -> String {
let uiTestBundle = Bundle(for: MyUITests.self)
return NSLocalizedString(key, bundle: uiTestBundle, comment: "")
}
Trying to perform a tap that way:
app.navigationBars[localizedString("key_1")].buttons[localizedString("key_2")].tap()
The error I get is the following:
No matches found for "Rolling stone" NavigationBar
Rolling stone is the Base/English Localization of the key, but there should be a German one. So for any reason the UITest always picks the English Localization.
Does anybody have an idea how to troubleshoot this? I checked the SnapshotHelper and it passes a "de-DE" as the language, so that's not the point.
But I just can't find the Bug :(
Localization
This link here should be sufficient enough to solve your problem. You simple pick the correct language option during the test tab under edit scheme option.

Resources