Working With Localization in swift
Environment
- Apple Swift version 2.1.1 (swiftlang-700.1.101.15 clang-700.1.81)
- Xode 7.1
- iOS 9
- Apple Swift version 2.1.1 (swiftlang-700.1.101.15 clang-700.1.81)
- Xode 7.1
- iOS 9
Targets
- User should be able to use another language from within the app.
- Layout should responds to the change (RTL(Right to Left)/LTR(Left to Right)).
- Using built in xliff generator.
- StoryBoard with Base localization and .String compliments “no multiple story boards” .
- simplicity.
I will use English and Arabic here, since English is LTR and arabic isRTL.
- User should be able to use another language from within the app.
- Layout should responds to the change (RTL(Right to Left)/LTR(Left to Right)).
- Using built in xliff generator.
- StoryBoard with Base localization and .String compliments “no multiple story boards” .
- simplicity.
I will use English and Arabic here, since English is LTR and arabic isRTL.
Project Setup
STEP ONE
Create new fresh Xcode project. Lets Name it Localization102
STEP TWO
Click on project file from the navigation area as in figure(1–3). SelectLocalization102 under PROJECT,figure(2–1).
Click on the plus sign under Localizations and add Arabic(ar), you will get screen listing files to be localized. click finish.
STEP THREE (Adding UI)
Lets add some UI elements in the storyboard to test with. We will have a UILabel, UIButton to switch language, UIImageView, UIButton for navigation, 2 UIViewControllers and one UINaviationcontroller.
Go to Main.Storyboard and From Object Library add a navigation controller and another UIViewController, set the UIViewController of class ViewController as the root of the navigation controller. Set the Navigation Controller as Initial View Controller from the Attribute Inspector.
They should look like figure(3–1).
Then add UILabel, an imageView, two buttons as in figure (3–2).
Its better to use an arrow image to test flipping effect when we go from RTL To LTR and vice versa. I am going to use this ARROWimage.
STEP 4 (Localizing the Storyboard)
Now we have setup up our layout, lets localize it. First Click on the main.storyboard file and in the Utility Area -> file inspector -> Localization,
uncheck the Arabic box, you will get this message
Click remove. Then go to the same box again and check it again, you will get this message.
Click on Replace file, since we need to add the updated UI Objects we added to the storyboard.
Now Take a look at the main.storyboard(Arabic) file. it should look like this.
Lets test the arabic version . From the Toolbar -> scheme -> edit Scheme ->Run -> options -> Application language -> chooseArabic.
Run the app
Verify that you get the arabic translation screens.
in order to get back to the english we can revert back and from Application Languagechoose English. Note that System Language means it depends on the language of the OS chosen by the user. but of course instead of going to the setting app and change the language we can always change that from thescheme for testing, as we did.
The text in the label apparently is flipped, because by default it’s TextAlignment is Natural(depends on the direction of the language), so its RTL now.
STEP 5 (Switch language)
Lets implement the switch language button so we changes the language not from theScheme but from our app.
- Create a Method and name itswitchLanguage, and connect it to the switch Language button in the MainView.
@IBAction func switchLanguage(sender: UIButton) {
}
- Reset the Application Language from Scheme to System Language.
- Lets code
To be able to modify the current Language set by the user we need to use the “AppleLanguages” global key.
Add this class
// constants
let APPLE_LANGUAGE_KEY = “AppleLanguages”
/// L102Language
class L102Language {
/// get current Apple language
class func currentAppleLanguage() -> String{
let userdef = NSUserDefaults.standardUserDefaults()
let langArray = userdef.objectForKey(APPLE_LANGUAGE_KEY) as! NSArray
let current = langArray.firstObject as! String
return current
}
/// set @lang to be the first in Applelanguages list
class func setAppleLAnguageTo(lang: String) {
let userdef = NSUserDefaults.standardUserDefaults()
userdef.setObject([lang,currentAppleLanguage()], forKey: APPLE_LANGUAGE_KEY)
userdef.synchronize()
}
}
L102Language is responsible for getting/setting language from/in the UserDefaults.
- Add these lines into the switchLanguage Method
if L102Language.currentAppleLanguage() == “en” {
L102Language.setAppleLAnguageTo(“ar”)
} else {
L102Language.setAppleLAnguageTo(“en”)
}
We check if the language is english then set it to be arabic and vice versa.
Lets try the app, build and run then tap on the switch language button.
Restart the app and you should be seeing the arabic version of our app.
“ you might not get the language switched in the second run, just try again. don’t worry its not a bug.”
So now we managed to change the language and provide localizable storyboard, but we have 2 main issues .
- we need to restart the app.
- UI Elements are not flipped to the right when language is Arabic.
ISSUE #1
If you go to the project directory in Finder you will find folders named like this “ar.proj” and inside them the localized resources.
When the app run and the System Language is Arabic(ar) the ar.proj bundle is used whenever the app asks for a localized resources. and so with other languages.
A bundle is a directory with a standardized hierarchical structure that holds executable code and the resources used by that code.[apple]
So we need to switch ‘lproj’ bundle in order to get it it localized without restarting the app.
One way to do that is through Swizzling, in order to use this technique we need to look for the method used to localize any string and change its implementation to consider our language preferences.
Note that bundles have direct effect in localization so we can look at NSBundle methods, we can find now that This method ->
/* Method for retrieving localized strings. */
- (NSString *)localizedStringForKey:(NSString *)key value:(nullable NSString *)value table:(nullable NSString *)tableName NS_FORMAT_ARGUMENT(1);
is used whenever we try to localize a string, which makes it our target.
Now create a file and name it L012Localizer.swift, and these lines
class L012Localizer: NSObject {
class func DoTheSwizzling() {
// 1
MethodSwizzleGivenClassName(NSBundle.self, originalSelector: Selector(“localizedStringForKey:value:table:”), overrideSelector: Selector(“specialLocalizedStringForKey:value:table:”))
}
}
extension NSBundle {
func specialLocalizedStringForKey(key: String, value: String?, table tableName: String?) -> String {
/*2*/let currentLanguage = L102Language.currentAppleLanguage()
var bundle = NSBundle();
/*3*/if let _path = NSBundle.mainBundle().pathForResource(currentLanguage, ofType: “lproj”) {
bundle = NSBundle(path: _path)!
} else {
let _path = NSBundle.mainBundle().pathForResource(“Base”, ofType: “lproj”)!
bundle = NSBundle(path: _path)!
}
/*4*/return (bundle.specialLocalizedStringForKey(key, value: value, table: tableName))
}
}
/// Exchange the implementation of two methods for the same Class
func MethodSwizzleGivenClassName(cls: AnyClass, originalSelector: Selector, overrideSelector: Selector) {
let origMethod: Method = class_getInstanceMethod(cls, originalSelector);
let overrideMethod: Method = class_getInstanceMethod(cls, overrideSelector);
if (class_addMethod(cls, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(cls, overrideSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, overrideMethod);
}
}
- we exchange the implementation of “localizedStringForKey:value:table:” with our “specialLocalizedStringForKey:value:table:”, passing the class which isNSBundle(.self to reference the object type).
- we get the preferred language.(e.g. en)
- we check if there is a bundle for that language (e.g. en.lproj), if not we user Base.proj, and we get store a reference in bundle var.
- we use localizedStringForKey:: method to return the localized method, note in swizzling we exchange the IMPlementation not the reference on the function, hence calling this method will use the original implementation of localizedStringForKey:: Method.
Now add this line in the Appdelegate in the didFinishLaunchingWithOptions delegate method.
L102Localizer.DoTheSwizzling()
OK NOW BUILD AND RUN :)
On the main screen Tap on “Switch button”.
Yeah nothing happened 😂
To solve this we need to reload our viewControllers . to do that add these lines at the end of the “switchLanguage” method, and in the Main.storyboard set the StoryboardId for the navigation controller to be “rootnav” .
let rootviewcontroller: UIWindow = ((UIApplication.sharedApplication().delegate?.window)!)!
rootviewcontroller.rootViewController = self.storyboard?.instantiateViewControllerWithIdentifier(“rootnav”)
let mainwindow = (UIApplication.sharedApplication().delegate?.window!)!
mainwindow.backgroundColor = UIColor(hue: 0.6477, saturation: 0.6314, brightness: 0.6077, alpha: 0.8)
UIView.transitionWithView(mainwindow, duration: 0.55001, options: .TransitionFlipFromLeft, animations: { () -> Void in
}) { (Bool finished) -> Void in
}
Build, run, and tap on SwitchLanguage button.
Now it should be working , when we switch the language, all the strings are changed with respect to the selected language.
SUM UP
Now we found a way to localize our storyboard, change the language, and update UI in runtime.
Next
in iOS9 the semantic changes as well depending on the language, but ! it happens only on the entrance of the app Just like bundle in issue#1.
Another issue is textAlignment in UILabel,TextField,TextView do not change automatically.
UIImages do not get flipped Automatically as well.
As you see in our project the UI Elements do not flip as well.
what about numbers!!!
STEP ONE
Create new fresh Xcode project. Lets Name it Localization102
STEP TWO
Click on project file from the navigation area as in figure(1–3). SelectLocalization102 under PROJECT,figure(2–1).
Click on the plus sign under Localizations and add Arabic(ar), you will get screen listing files to be localized. click finish.
STEP THREE (Adding UI)
Lets add some UI elements in the storyboard to test with. We will have a UILabel, UIButton to switch language, UIImageView, UIButton for navigation, 2 UIViewControllers and one UINaviationcontroller.
Go to Main.Storyboard and From Object Library add a navigation controller and another UIViewController, set the UIViewController of class ViewController as the root of the navigation controller. Set the Navigation Controller as Initial View Controller from the Attribute Inspector.
They should look like figure(3–1).
Then add UILabel, an imageView, two buttons as in figure (3–2).
Its better to use an arrow image to test flipping effect when we go from RTL To LTR and vice versa. I am going to use this ARROWimage.
STEP 4 (Localizing the Storyboard)
Now we have setup up our layout, lets localize it. First Click on the main.storyboard file and in the Utility Area -> file inspector -> Localization,
uncheck the Arabic box, you will get this message
Click remove. Then go to the same box again and check it again, you will get this message.
Click on Replace file, since we need to add the updated UI Objects we added to the storyboard.
Now Take a look at the main.storyboard(Arabic) file. it should look like this.
Lets test the arabic version . From the Toolbar -> scheme -> edit Scheme ->Run -> options -> Application language -> chooseArabic.
Run the app
Verify that you get the arabic translation screens.
in order to get back to the english we can revert back and from Application Languagechoose English. Note that System Language means it depends on the language of the OS chosen by the user. but of course instead of going to the setting app and change the language we can always change that from thescheme for testing, as we did.
The text in the label apparently is flipped, because by default it’s TextAlignment is Natural(depends on the direction of the language), so its RTL now.
STEP 5 (Switch language)
Lets implement the switch language button so we changes the language not from theScheme but from our app.
- Create a Method and name itswitchLanguage, and connect it to the switch Language button in the MainView.
@IBAction func switchLanguage(sender: UIButton) {
}
- Reset the Application Language from Scheme to System Language.
- Lets code
To be able to modify the current Language set by the user we need to use the “AppleLanguages” global key.
Add this class
// constants
let APPLE_LANGUAGE_KEY = “AppleLanguages”
/// L102Language
class L102Language {
/// get current Apple language
class func currentAppleLanguage() -> String{
let userdef = NSUserDefaults.standardUserDefaults()
let langArray = userdef.objectForKey(APPLE_LANGUAGE_KEY) as! NSArray
let current = langArray.firstObject as! String
return current
}
/// set @lang to be the first in Applelanguages list
class func setAppleLAnguageTo(lang: String) {
let userdef = NSUserDefaults.standardUserDefaults()
userdef.setObject([lang,currentAppleLanguage()], forKey: APPLE_LANGUAGE_KEY)
userdef.synchronize()
} }
L102Language is responsible for getting/setting language from/in the UserDefaults.
- Add these lines into the switchLanguage Method
if L102Language.currentAppleLanguage() == “en” {
L102Language.setAppleLAnguageTo(“ar”)
} else {
L102Language.setAppleLAnguageTo(“en”)
}
We check if the language is english then set it to be arabic and vice versa.
Lets try the app, build and run then tap on the switch language button.
Restart the app and you should be seeing the arabic version of our app.
“ you might not get the language switched in the second run, just try again. don’t worry its not a bug.”
So now we managed to change the language and provide localizable storyboard, but we have 2 main issues .
- we need to restart the app.
- UI Elements are not flipped to the right when language is Arabic.
ISSUE #1
If you go to the project directory in Finder you will find folders named like this “ar.proj” and inside them the localized resources.
When the app run and the System Language is Arabic(ar) the ar.proj bundle is used whenever the app asks for a localized resources. and so with other languages.
A bundle is a directory with a standardized hierarchical structure that holds executable code and the resources used by that code.[apple]
So we need to switch ‘lproj’ bundle in order to get it it localized without restarting the app.
One way to do that is through Swizzling, in order to use this technique we need to look for the method used to localize any string and change its implementation to consider our language preferences.
Note that bundles have direct effect in localization so we can look at NSBundle methods, we can find now that This method ->
/* Method for retrieving localized strings. */
- (NSString *)localizedStringForKey:(NSString *)key value:(nullable NSString *)value table:(nullable NSString *)tableName NS_FORMAT_ARGUMENT(1);
is used whenever we try to localize a string, which makes it our target.
Now create a file and name it L012Localizer.swift, and these lines
class L012Localizer: NSObject {
class func DoTheSwizzling() {
// 1 MethodSwizzleGivenClassName(NSBundle.self, originalSelector: Selector(“localizedStringForKey:value:table:”), overrideSelector: Selector(“specialLocalizedStringForKey:value:table:”))
}
}
extension NSBundle {
func specialLocalizedStringForKey(key: String, value: String?, table tableName: String?) -> String {
/*2*/let currentLanguage = L102Language.currentAppleLanguage()
var bundle = NSBundle();
/*3*/if let _path = NSBundle.mainBundle().pathForResource(currentLanguage, ofType: “lproj”) {
bundle = NSBundle(path: _path)!
} else {
let _path = NSBundle.mainBundle().pathForResource(“Base”, ofType: “lproj”)!
bundle = NSBundle(path: _path)!
}
/*4*/return (bundle.specialLocalizedStringForKey(key, value: value, table: tableName))
}
}
/// Exchange the implementation of two methods for the same Class
func MethodSwizzleGivenClassName(cls: AnyClass, originalSelector: Selector, overrideSelector: Selector) {
let origMethod: Method = class_getInstanceMethod(cls, originalSelector);
let overrideMethod: Method = class_getInstanceMethod(cls, overrideSelector);
if (class_addMethod(cls, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(cls, overrideSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, overrideMethod);
}
}
- we exchange the implementation of “localizedStringForKey:value:table:” with our “specialLocalizedStringForKey:value:table:”, passing the class which isNSBundle(.self to reference the object type).
- we get the preferred language.(e.g. en)
- we check if there is a bundle for that language (e.g. en.lproj), if not we user Base.proj, and we get store a reference in bundle var.
- we use localizedStringForKey:: method to return the localized method, note in swizzling we exchange the IMPlementation not the reference on the function, hence calling this method will use the original implementation of localizedStringForKey:: Method.
Now add this line in the Appdelegate in the didFinishLaunchingWithOptions delegate method.
L102Localizer.DoTheSwizzling()
OK NOW BUILD AND RUN :)
On the main screen Tap on “Switch button”.
Yeah nothing happened 😂
To solve this we need to reload our viewControllers . to do that add these lines at the end of the “switchLanguage” method, and in the Main.storyboard set the StoryboardId for the navigation controller to be “rootnav” .
let rootviewcontroller: UIWindow = ((UIApplication.sharedApplication().delegate?.window)!)!
rootviewcontroller.rootViewController = self.storyboard?.instantiateViewControllerWithIdentifier(“rootnav”)
let mainwindow = (UIApplication.sharedApplication().delegate?.window!)!
mainwindow.backgroundColor = UIColor(hue: 0.6477, saturation: 0.6314, brightness: 0.6077, alpha: 0.8)
UIView.transitionWithView(mainwindow, duration: 0.55001, options: .TransitionFlipFromLeft, animations: { () -> Void in
}) { (Bool finished) -> Void in
}
Build, run, and tap on SwitchLanguage button.
Now it should be working , when we switch the language, all the strings are changed with respect to the selected language.
SUM UP
Now we found a way to localize our storyboard, change the language, and update UI in runtime.
Next
in iOS9 the semantic changes as well depending on the language, but ! it happens only on the entrance of the app Just like bundle in issue#1.
Another issue is textAlignment in UILabel,TextField,TextView do not change automatically.
UIImages do not get flipped Automatically as well.
As you see in our project the UI Elements do not flip as well.
what about numbers!!!
AutoLayout
Autolayout not only help us building our Interface to adapt many screens, but also can help us in positioning the views correctly regarding the Language Direction, it does that by flipping the right and left Attributes of UILayoutConstraint. so what is a left Attribute in english is right attribute in arabic and so.
So Let add some constraints to our UI first.
BUILD AND RUN, tap on switch language button, close the app, run it again. You will see that the layout has been flipped.
ISSUE #2 (Mirroring)
As I mentioned above One of the main issues is the textAlignment, notice that setting the alignment as Natural in the Interface building for our UILabel does not flip the Text!!.
Mirroring User interface is part of internationalizing the app.
To solve that go to ViewController.swift and in switchLanguage Method update the if condition to look like this .
if L102Language.currentAppleLanguage() == “en” {
L102Language.setAppleLAnguageTo(“ar”)
UIView.appearance().semanticContentAttribute = .ForceRightToLeft
} else {
L102Language.setAppleLAnguageTo(“en”)
UIView.appearance().semanticContentAttribute = .ForceLeftToRight
}
in iOS9 we can force the semantics from the appearance. :) (we will get to how to make it work on iOS8 later )
run and try , you should get a result like this.
So the now the Views are flipping and texts are correctly aligned! and thats all on runtime!!
UIImageView (Mirroring) (1)
You can see that the arrows are not flipping correctly. our custom image is not going to flip even after restarting the app, but the Back Navigation Bar button arrow IS flipped after restarting the app.
First Lets Mirror out own custom arrow image, for that we can use
public init(CGImage cgImage: CGImage, scale: CGFloat, orientation: UIImageOrientation)
initializer.
To make it more dynamic, instead of flipping each image a side, I created a superViewController that loops through its subViews and when it fins an image it flip it if the its tag is less than 0 .
So lets create new UIViewController and Name it MirroringViewController .
And modify our ViewController to be a subClass of MirroringViewController.
class ViewController: MirroringViewController
Add these lines to that class
import UIKit
extension UIViewController {
func loopThroughSubViewAndFlipTheImageIfItsAUIImageView(subviews: [UIView]) {
if subviews.count > 0 {
for subView in subviews {
if subView.isKindOfClass(UIImageView.self) && subView.tag < 0 {
let toRightArrow = subView as! UIImageView
if let _img = toRightArrow.image {
/*1*/toRightArrow.image = UIImage(CGImage: _img.CGImage!, scale:_img.scale , orientation: UIImageOrientation.UpMirrored)
}
}
/*2*/loopThroughSubViewAndFlipTheImageIfItsAUIImageView(subView.subviews)
}
}
}
}
class MirroringViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if L102Language.currentAppleLanguage() == “ar” {
loopThroughSubViewAndFlipTheImageIfItsAUIImageView(self.view.subviews)
}
}
}
- 1 ) is where flipping the image
- 2) we call this function recursively to make sure we cover all subView.
- This will not work on UITableView cells subViews. So you need to flip the cell images if needed inside the UITableView delegate.
UIImageView (Mirroring) (2)
Now lets solve the navigation back button arrow.
our loop Method did not work because the navbar is not a subView of our ViewController. UIBarbuttonItem /UINavigationItem are not of UIView type so we can’t reach them hierarchaly .
for that we need to use the userInterfaceLayoutDirection computed property in UIAplication class, as we know each app has one single UIApplication instance or a subclass of it , this instance has a big role in any app. one thing that interest us is the userInterfaceLayoutDirection.
This method specifies the general user interface layout flow direction
One way to update its implementation is by subclassing the UIApplication by adding main.swift file , and adding this line
UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(MyApp), NSStringFromClass(AppDelegate))
- MyApp* is our custom subclass where we override the userInterfaceLayoutDirection property . like this
override var userInterfaceLayoutDirection: UIUserInterfaceLayoutDirection {
get {
var direction = UIUserInterfaceLayoutDirection.LeftToRight
if Languages.currentAppleLanguage() == “ar” {
direction = .RightToLeft
}
return direction
}
}
Another way is through the lovely Swizzling :) OF COURSE
add this line to the DoTheSwizzling() Method
-----------------------------------------------
MethodSwizzleGivenClassName(UIApplication.self, originalSelector: Selector(“userInterfaceLayoutDirection”), overrideSelector: Selector(“cstm_userInterfaceLayoutDirection”))
and this extension
extension UIApplication {
var cstm_userInterfaceLayoutDirection : UIUserInterfaceLayoutDirection {
get {
var direction = UIUserInterfaceLayoutDirection.LeftToRight
if L102Language.currentAppleLanguage() == “ar” {
direction = .RightToLeft
}
return direction
}
}
}
This method simply will check what is the language we are using and if it RTL we return .RightToLeft .
Lets celebrate by a successful run :)
- as I noted userInterfaceLayoutDirection is called for every UIView whenever the view is updated or created. so we should not add a big time consuming functionality in it.
from now on we can use UIApplication.sharedApplication().userInterfaceLayoutDirection
to check the direction of the app.
Autolayout not only help us building our Interface to adapt many screens, but also can help us in positioning the views correctly regarding the Language Direction, it does that by flipping the right and left Attributes of UILayoutConstraint. so what is a left Attribute in english is right attribute in arabic and so.
So Let add some constraints to our UI first.
BUILD AND RUN, tap on switch language button, close the app, run it again. You will see that the layout has been flipped.
ISSUE #2 (Mirroring)
As I mentioned above One of the main issues is the textAlignment, notice that setting the alignment as Natural in the Interface building for our UILabel does not flip the Text!!.
Mirroring User interface is part of internationalizing the app.
To solve that go to ViewController.swift and in switchLanguage Method update the if condition to look like this .
if L102Language.currentAppleLanguage() == “en” {
L102Language.setAppleLAnguageTo(“ar”)
UIView.appearance().semanticContentAttribute = .ForceRightToLeft
} else {
L102Language.setAppleLAnguageTo(“en”)
UIView.appearance().semanticContentAttribute = .ForceLeftToRight
}
in iOS9 we can force the semantics from the appearance. :) (we will get to how to make it work on iOS8 later )
run and try , you should get a result like this.
So the now the Views are flipping and texts are correctly aligned! and thats all on runtime!!
UIImageView (Mirroring) (1)
You can see that the arrows are not flipping correctly. our custom image is not going to flip even after restarting the app, but the Back Navigation Bar button arrow IS flipped after restarting the app.
First Lets Mirror out own custom arrow image, for that we can use
public init(CGImage cgImage: CGImage, scale: CGFloat, orientation: UIImageOrientation)
initializer.
To make it more dynamic, instead of flipping each image a side, I created a superViewController that loops through its subViews and when it fins an image it flip it if the its tag is less than 0 .
So lets create new UIViewController and Name it MirroringViewController .
And modify our ViewController to be a subClass of MirroringViewController.
class ViewController: MirroringViewController
Add these lines to that class
import UIKit
extension UIViewController {
func loopThroughSubViewAndFlipTheImageIfItsAUIImageView(subviews: [UIView]) {
if subviews.count > 0 {
for subView in subviews {
if subView.isKindOfClass(UIImageView.self) && subView.tag < 0 {
let toRightArrow = subView as! UIImageView
if let _img = toRightArrow.image {
/*1*/toRightArrow.image = UIImage(CGImage: _img.CGImage!, scale:_img.scale , orientation: UIImageOrientation.UpMirrored)
}
}
/*2*/loopThroughSubViewAndFlipTheImageIfItsAUIImageView(subView.subviews)
}
}
}
}
class MirroringViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if L102Language.currentAppleLanguage() == “ar” {
loopThroughSubViewAndFlipTheImageIfItsAUIImageView(self.view.subviews)
}
}
}
- 1 ) is where flipping the image
- 2) we call this function recursively to make sure we cover all subView.
- This will not work on UITableView cells subViews. So you need to flip the cell images if needed inside the UITableView delegate.
UIImageView (Mirroring) (2)
Now lets solve the navigation back button arrow.
our loop Method did not work because the navbar is not a subView of our ViewController. UIBarbuttonItem /UINavigationItem are not of UIView type so we can’t reach them hierarchaly .
for that we need to use the userInterfaceLayoutDirection computed property in UIAplication class, as we know each app has one single UIApplication instance or a subclass of it , this instance has a big role in any app. one thing that interest us is the userInterfaceLayoutDirection.
This method specifies the general user interface layout flow direction
One way to update its implementation is by subclassing the UIApplication by adding main.swift file , and adding this line
UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(MyApp), NSStringFromClass(AppDelegate))
- MyApp* is our custom subclass where we override the userInterfaceLayoutDirection property . like this
override var userInterfaceLayoutDirection: UIUserInterfaceLayoutDirection {
get {
var direction = UIUserInterfaceLayoutDirection.LeftToRight
if Languages.currentAppleLanguage() == “ar” {
direction = .RightToLeft
}
return direction
}
}
Another way is through the lovely Swizzling :) OF COURSE
add this line to the DoTheSwizzling() Method -----------------------------------------------
MethodSwizzleGivenClassName(UIApplication.self, originalSelector: Selector(“userInterfaceLayoutDirection”), overrideSelector: Selector(“cstm_userInterfaceLayoutDirection”))
and this extension
extension UIApplication {
var cstm_userInterfaceLayoutDirection : UIUserInterfaceLayoutDirection {
get {
var direction = UIUserInterfaceLayoutDirection.LeftToRight
if L102Language.currentAppleLanguage() == “ar” {
direction = .RightToLeft
}
return direction
}
}
}
This method simply will check what is the language we are using and if it RTL we return .RightToLeft .
Lets celebrate by a successful run :)
- as I noted userInterfaceLayoutDirection is called for every UIView whenever the view is updated or created. so we should not add a big time consuming functionality in it.
from now on we can use UIApplication.sharedApplication().userInterfaceLayoutDirection to check the direction of the app.
iOS8
As it mentioned above you can’t useUIView.appearance().semanticContentAttribute
in iOS 8, so lets remove those lines and run the app.
switch the app and go to next control and so, you will notice that the UILabel textAlignment is not aligned correctly on one of the languages unless you restart the app of course.
One way to solve it, is by creating Custom UILabel, lets call itMirroringLabel
Set the UILabel Class to be MirroringLabel and the tag to be -1.
inside MirroringLabel class, we need to override the layoutSubViews method , add these lines :-
override func layoutSubviews() {
// check if its already correctly aligned
if self.tag < 0 {
if UIApplication.isRTL() {
if self.textAlignment == .Right {
return
}
} else {
if self.textAlignment == .Left {
return
}}}
// if not align it based on the Direction , check first if the tag is less than 0 (which means we want this label to be directional not for example centered)
if self.tag < 0 {
if UIApplication.isRTL() {
self.textAlignment = .Right
} else {
self.textAlignment = .Left
}}
}
and add this helper method in UIApplication extensions inside L102Localizer.swift
extension UIApplication {
class func isRTL() -> Bool{
return UIApplication.sharedApplication().userInterfaceLayoutDirection == .RightToLeft
}
}
ok now, lets run again. you should be able to get the right behavior.
For UITextField, UITextView its the same story add a superclass for them and update the tags to be less than 0 (btw, this less than 0 thing can be anything you want. its just a criteria to determine if a UIView should flip its content or not)
As it mentioned above you can’t useUIView.appearance().semanticContentAttribute
in iOS 8, so lets remove those lines and run the app.
switch the app and go to next control and so, you will notice that the UILabel textAlignment is not aligned correctly on one of the languages unless you restart the app of course.
One way to solve it, is by creating Custom UILabel, lets call itMirroringLabel
Set the UILabel Class to be MirroringLabel and the tag to be -1.
inside MirroringLabel class, we need to override the layoutSubViews method , add these lines :-
override func layoutSubviews() { // check if its already correctly aligned
if self.tag < 0 {
if UIApplication.isRTL() {
if self.textAlignment == .Right {
return
}
} else {
if self.textAlignment == .Left {
return
}}} // if not align it based on the Direction , check first if the tag is less than 0 (which means we want this label to be directional not for example centered)
if self.tag < 0 {
if UIApplication.isRTL() {
self.textAlignment = .Right
} else {
self.textAlignment = .Left
}}
}
and add this helper method in UIApplication extensions inside L102Localizer.swift
extension UIApplication {
class func isRTL() -> Bool{
return UIApplication.sharedApplication().userInterfaceLayoutDirection == .RightToLeft
}
}
ok now, lets run again. you should be able to get the right behavior.
For UITextField, UITextView its the same story add a superclass for them and update the tags to be less than 0 (btw, this less than 0 thing can be anything you want. its just a criteria to determine if a UIView should flip its content or not)
Localizable.String
So we could localize the storyboard Texts, what about the dynamic strings we add in code/programatically or maybe you don’t even use storyboard.
its simple we need to add a Strings file and name it Localizable.string
- Press CMD + N
- under iOS -> Resource -> Choose Strings File
- Name it Localizable and create
- Click on the created file and from File Inspector, click on the Localize… button
- When the “Do you want to localize this file?” window shows, click localize.
- Check the Arabic option box.
- you will get this look
We don’t need to add anything for now, lets do it in a nicer way.
As promised we will use the built in export/import xliff files feature in xcode.
Before that lets create another UILabel and connect it as an outlet in our ViewController, lets call it programmaticallylocalizedLabel (yeah long name but suitable), add this line at the start of ViewController.
@IBOutlet weak var programmaticallylocalizedLabel: UILabel!
Create a UILabel in the storyBoard, name it anything, and connect it to the outlet . Just make sure the tag is less than 0.
In ViewDidLoad() add this line
self.programmaticallylocalizedLabel.text = NSLocalizedString(“localize me please”, comment: “Localize me Label in the main scene”)
Its always a good idea to add a useful comments or Notes so the guy or you later understand what and why this text should be localized.
ok now The fun .
XLIFF Export/Import
- Click on the blue project Under the project Navigator and then click onEditor from the Menu
- Now Click on Export For Localization .
- you can chose an existing Translations or Development Language Only
- since we want to translate Arabic lets not change anything and click on save.
- In The finder you will find the file named ar.xliff
- There are many ways to translate this file, I like this website (http://xliff.brightec.co.uk/)
- from the Main page choose the file which is ar.xliff and click on start translating.
- its very great and useful service (hope it stays free :) )
- You can see 9- Localize me please field, thats were we need to fill in.
- write the localization you want and then Tap on the floating Green Button.
- you will get new.xliff generated file
- ok now go back to xcode and from the Editor in the Menu, click on “Import Localizations” and choose the generated file “new.xliff”
- After that you will get this screen
- Click on import
- got to Localizable.strings the arabic version, you will see the localized string.
/* Localize me Label in the main scene */
“localize me please” = “لوكالايز مي”;
- Run the app and see the localization working.
So we could localize the storyboard Texts, what about the dynamic strings we add in code/programatically or maybe you don’t even use storyboard.
its simple we need to add a Strings file and name it Localizable.string
- Press CMD + N
- under iOS -> Resource -> Choose Strings File
- Name it Localizable and create
- Click on the created file and from File Inspector, click on the Localize… button
- When the “Do you want to localize this file?” window shows, click localize.
- Check the Arabic option box.
- you will get this look
We don’t need to add anything for now, lets do it in a nicer way.
As promised we will use the built in export/import xliff files feature in xcode.
Before that lets create another UILabel and connect it as an outlet in our ViewController, lets call it programmaticallylocalizedLabel (yeah long name but suitable), add this line at the start of ViewController.
@IBOutlet weak var programmaticallylocalizedLabel: UILabel!
Create a UILabel in the storyBoard, name it anything, and connect it to the outlet . Just make sure the tag is less than 0.
In ViewDidLoad() add this line
self.programmaticallylocalizedLabel.text = NSLocalizedString(“localize me please”, comment: “Localize me Label in the main scene”)
Its always a good idea to add a useful comments or Notes so the guy or you later understand what and why this text should be localized.
ok now The fun .
XLIFF Export/Import
- Click on the blue project Under the project Navigator and then click onEditor from the Menu
- Now Click on Export For Localization .
- you can chose an existing Translations or Development Language Only
- since we want to translate Arabic lets not change anything and click on save.
- In The finder you will find the file named ar.xliff
- There are many ways to translate this file, I like this website (http://xliff.brightec.co.uk/)
- from the Main page choose the file which is ar.xliff and click on start translating.
- its very great and useful service (hope it stays free :) )
- You can see 9- Localize me please field, thats were we need to fill in.
- write the localization you want and then Tap on the floating Green Button.
- you will get new.xliff generated file
- ok now go back to xcode and from the Editor in the Menu, click on “Import Localizations” and choose the generated file “new.xliff”
- After that you will get this screen
- Click on import
- got to Localizable.strings the arabic version, you will see the localized string.
/* Localize me Label in the main scene */
“localize me please” = “لوكالايز مي”;
- Run the app and see the localization working.
Source Code
You can find the source code for the finished project here
You can find the source code for the finished project here
Original Post
which is better formatted at
https://medium.com/@dark_torch/working-with-localization-in-swift-4a87f0d393a4#.l2ev2f9i2
FINALE
I hope you enjoyed this kinda long post. This post by now has done its Targets, well its not that simple, but it worth it!.
If you have a comment/question/Edit please add it, thank you.
Follow me at https://twitter.com/dark_torch
I hope you enjoyed this kinda long post. This post by now has done its Targets, well its not that simple, but it worth it!.
If you have a comment/question/Edit please add it, thank you.
Follow me at https://twitter.com/dark_torch
Follow me at https://twitter.com/dark_torch
No comments:
Post a Comment