Localization is working fine in my application. I want localization the permission dialog text messages. It is working fine with the device language changes but I want to change message according to my application language.
I have tried the following code
import UIKit
class LocalizeHelper: NSObject {
private var myBundle: Bundle? = nil
static let shared: LocalizeHelper = {
let instance = LocalizeHelper()
return instance
}()
override init() {
super.init()
// use systems main bundle as default bundle
myBundle = Bundle.main
}
func localizedString(forKey key: String) -> String {
return myBundle!.localizedString(forKey: key, value: "", table: nil)
}
// Converted with Swiftify v1.0.6331 - https://objectivec2swift.com/
func setLanguage(_ lang: String) {
// path to this languages bundle
let path: String? = Bundle.main.path(forResource: lang, ofType: "lproj")
if path == nil {
// there is no bundle for that language
// use main bundle instead
myBundle = Bundle.main
}
else {
// use this bundle as my bundle from now on:
myBundle = Bundle(path: path!)
// to be absolutely shure (this is probably unnecessary):
if myBundle == nil {
myBundle = Bundle.main
}
}
}
func getLanguage() -> String {
print("\(String(describing: myBundle?.bundlePath.last))")
//return myBundle!.bundlePath.last >> Error
return myBundle!.bundlePath.lastCharacter!
}
}
extension String {
public var lastCharacter: String? {
guard let aLast = self.last else {
return nil
}
return String(aLast)
}
}
I have surfed in StackOverflow but didn't find any solution. Any help shell we appreciated.
Your best bet is to save the application's language in the user defaults as a string for example and then when you take it out of user defaults parse it as a custom app language enum. If you don't have any app language save you can always fall through to the device language but prefer or override it with the app language in user defaults not being nil. Also preferably you should wrap your user defaults interactions with a user defaults manager that you access in your view controller.
So the suggested steps are:
create userdefualts manager
define language enum
implement setLanguage func language parameter
implement getLanguage func language output with device language or
english as guard return
Related
I'm trying to build an app, from where user can select the language and i want to change the content of whole application. For example i have two labels and two languages english and german. two files of Localizable (en , de). on my screen user press turn to german and i want to change the language of the application and want to update the UI but i dont want to the user to close the application. I found some of solution over here but that didn't work out like similar question
extension Bundle {
private static var bundle: Bundle!
public static func localizedBundle() -> Bundle! {
if bundle == nil {
let appLang = UserDefaults.standard.string(forKey: "app_lang") ?? "ru"
let path = Bundle.main.path(forResource: appLang, ofType: "lproj")
bundle = Bundle(path: path!)
}
return bundle;
}
public static func setLanguage(lang: String) {
UserDefaults.standard.set(lang, forKey: "app_lang")
let path = Bundle.main.path(forResource: lang, ofType: "lproj")
bundle = Bundle(path: path!)
}}
and this
extension String {
func localized() -> String {
return NSLocalizedString(self, tableName: nil, bundle: Bundle.localizedBundle(), value: "", comment: "")
}
func localizeWithFormat(arguments: CVarArg...) -> String{
return String(format: self.localized(), arguments: arguments)
}}
and using it like
#IBAction func englishAction(_ sender: Any) {
let localisedSt = "en".localized()
Bundle.setLanguage(lang: "en")
}
after this i tried to reload app , reload view but nothing change.
NSLocalizedString will look up inside the localization table appropriate for the application which is determined by device or UserDefaults. There is a custom key that you can set inside UserDefaults and determine what language you're wishing to use, then on the next launch of the application it will load the appropriate bundle.
If you want to change localization inside the application(on the fly), you need to lookup the table manually from Bundle representing that Locale. Just look for the file with extension .lproj and recreate another Bundle with fetched path, and query generated Bundle for localized string.
let locale = Locale(identifier: "en");
guard let path = Bundle.main.path(forResource: locale.identifier, ofType: "lproj")
else { return };
let localizedBundle = Bundle(path: path)
localizedBundle.localizedString(forKey:_, value:_, table:_) // you can pass NULL in table to look into "Localizable.strings"
But of-course, after changing the locale, you also need to update all currently configured texts.
I wrote a library called Styled that can help you with that part (that and also Interpolation, Color/Font synchronization with device font system)
I've read lots of answers about this, but most are many years old and I don't know what's the latest info.
On first launch, my app will ask the user which language (s)he prefers, rather than just using the OS default language.
How can I set the localization rest of the app to the selected language?
How can I get which language the user has selected in other views?
I thought the following code would set the language, but it didn't do the job:
UserDefaults.standard.set("AR", forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
I have handled this with a LanguageManager singleton class, which handles all the localization. Here is some psuedo code, as its typically a fairly large class.
It has a list of all locales that are supported, for example:
let supportedLocales = ['en-US', 'en-CA', 'fr', 'es-ES', 'es-MX']
It also stores the selected language in UserDefaults. When the selectedLocale changes, it sends a Notification, in case you want to notify your views or anything else:
var selectedLocale: String? {
get {
return UserDefaults.standard.object(forKey: UserDefaultsKeys.selectedLocale) as? String
}
set (newLocale) {
let didChange = self.selectedLocale != newLocale
UserDefaults.standard.set(newLocale, forKey: UserDefaultsKeys.selectedLocale)
UserDefaults.standard.synchronize()
if didChange {
NotificationCenter.default.post(name: Notification.Name.localeDidChange, object: nil)
}
}
}
Now, in order to pull strings out of your localized.strings files, you can't use the standard Apple methods - you have to provide your own. Use LanguageManager.shared.getString(for key:String, alt:String) to reference keys in your .strings file to pull out localized strings.
var selectedLanguage:String? {
//returns just the language portion of the locale - eg: 'en' from 'en-US'
if let selectedLocale = selectedLocale {
return selectedLocale.components(separatedBy: "-")[0]
}
return nil
}
func getString(for key:String, alt:String) -> String
{
var val:String? = getString(for:key, language: selectedLocale)
if val == nil {
val = getString(for:key, language: selectedLanguage)
}
if val == nil {
val = getString(for:key, language: "en") //default to English
}
if let val = val {
return val
}
return alternate //use fallback
}
func getString(for key:String, language:String) -> String?
{
let path = Bundle.main.path(forResource:language, ofType:"lproj")
if let languageBundle = Bundle(path:path) {
return languageBundle.localizedString(for: key)
}
return nil
}
By default is the English Language // You can set Language according you
UserDefaults.standard.set("en", forKey: "Apple")
UserDefaults.standard.synchronize()
If you want to current system language than use code
let langStr = Locale.current.languageCode //and set in UserDeafaults
UserDefaults.standard.set(langStr, forKey: "Apple")
var currentlanguage: String?
self.currentlanguage = UserDefaults.standard.object(forKey: "Apple") as! String?
print("current language ---%#",self.currentlanguage)
Is it possible to mock an applications NSBundle to return predictable results during TDD?
For example:
I want to test that my application handles when a file is not saved to the NSBundle:
//Method to test
func getProfileImage() -> UIImage {
if let profileImagePath = getProfilePhotoPath() {
UIImage(contentsOfFile: profileImagePath)
}
return UIImage(named: "defaultProfileImage")
}
private func getProfilePhotoPath() -> String? {
return NSBundle.mainBundle().pathForResource("profileImage", ofType: "png")
}
Is it possible to mock the NSBundle.mainBundle() to return false for a pathForResource?
As it stands, NSBundle.mainBundle() is a hard-coded dependency. What we'd like is the ability to specify any bundle, perhaps with mainBundle as the default. The answer is Dependency Injection. Let's use the preferred form of Constructor Injection, and take advantage of Swift's default parameters:
class ProfileImageGetter {
private var bundle: NSBundle
init(bundle: NSBundle = NSBundle.mainBundle()) {
self.bundle = bundle
}
func getProfileImage() -> UIImage {
if let profileImagePath = getProfilePhotoPath() {
return UIImage(contentsOfFile: profileImagePath)!
}
return UIImage(named: "defaultProfileImage")!
}
private func getProfilePhotoPath() -> String? {
return bundle.pathForResource("profileImage", ofType: "png")
}
}
Now a test can instantiate a ProfileImageGetter and specify any bundle it likes. This could be the test bundle, or a fake.
Specifying the test bundle would allow you to have a situation where profileImage.png doesn't exist.
Specifying a fake would let you stub the result of calling pathForResource().
I am aware of quite a few posts that say this should not be done, or is not possible. I tinkered with a few ideas and now I'm asking this question, because I want to be absolutely sure there are no other options.
Option 1:
The most popular solution is to change AppleLanguages as in this post. I do not mind the idea of requiring a restart, so this would be an acceptable solution for me, except that you cannot restart your app programmatically (can't find the method, or would be rejected by Apple). Asking the user to manually restart the application wouldn't be ideal.
Option 2:
The next solution is to get the appropriate bundle and perform a localizedStringForKey lookup on each and every UILabel, UIButton, etc. This can be a little tedious but is okay for me, since I already added localizationProperties (similar to this) to these views so that I can have a centralized strings file.
AppDelegate.swift:
static var userLanguage: String?
{
set
{
let defaults = NSUserDefaults.standardUserDefaults();
defaults.setObject(newValue, forKey: LanguageKey);
defaults.synchronize();
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle());
instance.window?.rootViewController = storyboard.instantiateInitialViewController();
}
get
{
let defaults = NSUserDefaults.standardUserDefaults();
return defaults.stringForKey(LanguageKey);
}
}
Localization.swift:
private var bundle: NSBundle
{
get
{
let bundle: NSBundle;
#if TARGET_INTERFACE_BUILDER
bundle = NSBundle(forClass: self.dynamicType);
#else
bundle = NSBundle.mainBundle();
#endif
let lang: String;
if(AppDelegate.userLanguage == nil || AppDelegate.userLanguage == "en")
{
lang = "Base";
}
else
{
lang = AppDelegate.userLanguage!;
}
let path = bundle.pathForResource(lang, ofType: "lproj");
if(path != nil)
{
let toreturn = NSBundle(path: path!);
if(toreturn != nil)
{
return toreturn!;
}
}
return bundle;
}
}
extension UILabel
{
#IBInspectable var localizedText: String?
{
get { return "" }
set
{
if(newValue != nil)
{
text = bundle.localizedStringForKey(newValue!, value:"", table: nil);
}
}
}
}
The problem with option 2 is that this only sets the language, for those fields. Layout direction will be unchanged, and files such as language specific layouts would not be used.
By extending UIApplication I am able to specify a custom userInterfaceLayoutDirection which successfully swaps all layouts between LTR and RTL.
DemoApplication.swift:
class DemoApplication: UIApplication
{
override internal var userInterfaceLayoutDirection: UIUserInterfaceLayoutDirection
{
get
{
if(AppDelegate.userLanguage == "ar")
{
return UIUserInterfaceLayoutDirection.RightToLeft;
}
return UIUserInterfaceLayoutDirection.LeftToRight;
}
}
}
Now when I set AppDelegate.userLanguage the application will reset to the initial view controller, displaying the new language, flipping the layout between LTR and RTL. This does not address the issue of language specific files, and I've also noticed that text remains left or right aligned within its own bounds.
Since I can't find the source code for native iOS classes, I can't see what language specific variables are set at startup so I assumed it is linked to the NSBundle.mainBundle. I tried to override it by using method swizzling.
extension NSBundle
{
override public class func initialize()
{
struct Static
{
static var token: dispatch_once_t = 0;
}
// make sure this isn't a subclass
if (self !== NSBundle.self)
{
return;
}
dispatch_once(&Static.token)
{
do
{
try jr_swizzleClassMethod("mainBundle", withClassMethod: "mainBundleExt");
}
catch
{
print("\(error)");
}
}
super.initialize();
}
public class func mainBundleExt() -> NSBundle
{
let bundle = self.mainBundleExt(); // Due to swizzling, this is the "super" method
let lang: String;
if(AppDelegate.userLanguage == nil || AppDelegate.userLanguage == "en")
{
lang = "Base";
}
else
{
lang = AppDelegate.userLanguage!;
}
let path = bundle.pathForResource(lang, ofType: "lproj");
if(path != nil)
{
let toreturn = NSBundle(path: path!);
if(toreturn != nil)
{
return toreturn!;
}
}
}
}
This does not work though, it seems as though the default mainBundle is still used.
So my question is this: How is mainBundle assigned a value? Which other language specific variables are set at startup, such as userInterfaceLayoutDirection.
I assume there are 2 or 3 of these variables. Finally, is it possible for this to work or am I just wasting my time?
Thanks.
I had this problem before and I have used a library. it helped me to change the language on the fly.
try to use this:
https://github.com/Decybel07/L10n-swift
L10n.shared.language = "en"
L10n.shared.language = "en-GB"
At runtime, you can switch the language at any time by setting the language property
Use this line of code it will change layout without closing application. From right to left
UIView.appearance().semanticContentAttribute = .forceRightToLeft
And for Left to Right Flip
UIView.appearance().semanticContentAttribute = .forceLeftToRight
and if you want to change textfield layout or text change then use this code because i faced this issue . textfield's texts was not changning layout. check this code to change layout of textfield text
extension UITextField {
open override func awakeFromNib() {
super.awakeFromNib()
if UserDefaults.languageCode == "ar" {
if textAlignment == .natural {
self.textAlignment = .right
}
}
}
}
Hello I'm trying to publish a iOS (SWIFT) personal project in GitHub but I'm afraid of sharing my private API keys and secrets with everybody.
I'm using parse so I have in my AppDelegate something like this:
let applicationId = "mySecretApplicationId"
let clientKey = "mySecretClientKey"
Parse.setApplicationId(applicationId!, clientKey: clientKey!)
I would like to hide "mySecretApplicationId" and "mySecretClientKey", is there private place or directory in my project where I can put this variables?
Thanks!
You can use a .plist file where you store all your important keys. It is very important to put this file into your .gitignore file.
In your case, you need to set your keys.plist file like this:
And use it inside your AppDelegate as follows:
var keys: NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("Keys", ofType: "plist") {
keys = NSDictionary(contentsOfFile: path)
}
if let dict = keys {
let applicationId = dict["parseApplicationId"] as? String
let clientKey = dict["parseClientKey"] as? String
// Initialize Parse.
Parse.setApplicationId(applicationId!, clientKey: clientKey!)
}
SWIFT 3 Update:
if let path = Bundle.main.path(forResource: "Keys", ofType: "plist") {
keys = NSDictionary(contentsOfFile: path)
}
Put them in a configuration file that you add to the .gitignore file. Check in a sample configuration file that every developer can use to create their own configuration.
If you want to share your project without keys then:
Add Keys( as you prefer - enum, struct, or even object/singleton)
struct Keys {
static let sandboxToken = "Tpk_hh43nneu3jwsu3u"
static let productionToken = "pk_b5h4uend8ejwnw8"
}
In your code add follow code:
extension APIManager {
enum Environment {
case sandbox, production
var apiKey: String {
switch self {
case .sandbox:
return Keys.iexSandboxToken // <- Here
case .production:
return Keys.iexProductionToken // <- Here
}
}
}
}
or if you want to deal with optionals then you can add something similar to:
struct Keys {
static let sandboxToken: String? = "Tpk_hh43nneu3jwsu3u"
static let productionToken: String?
}
and on use add assert
var apiKey: String {
switch self {
case .sandbox:
guard let token = Keys.iexSandboxToken else {
assertionFailure("Please fill the tokent in Keys.swift")
return "anything you want"
}
return token
case .production:
guard let token = Keys.iexProductionToken else {
assertionFailure("Please fill the tokent in Keys.swift")
return "anything you want"
}
return token
}
}
So, in production, it will fail.
Add it on .gitignore. So, your keys are hidden.