How Can I set Localised Direction within application?(RTL if user select arabic, LTR is selected language is English)
Asked Answered
B

4

22

My Application must support Arabic(right to left direction) and English(left to right direction) language, I need to set UI and based on user select language from my application.

I will implement UI with NSLayoutConstraint so it can update UI automatically, based on leading and trailing constraints.

Now My Question is how can i achieve this? As My device language is say English and from my application user select Arabic(limited to my app only), So my Flow,UI,Animations etc should be right to left.

here are the sample snap for ui Direction enter image description here enter image description here

Thanks

Barret answered 7/8, 2015 at 11:55 Comment(5)
As far as I now there is no other solution. So, if your boss insists on implementing the language inside the app. and didn't convince to follow apple's guide lines. Unfortunately you have to implement it :)Helvetia
@hasan83 I found that if we set "AppleLanguages" in main.m file that time its works, But UI will update only after relaunching the app.Barret
yes I said that in my answer you must relaunch the app. and added a link for an app that asks the user to relaunch the app.Helvetia
Why are you setting your app up this way? Is there any reason why you're not simply basing off of the device language automatically?Dumpcart
@hasan83, The language change is not device language, its limited to app only. And its my app requirement that user can switch to any language at any time. Thanks for you answer.Barret
H
44

Bottom line you can change the language from inside the app. But, an extra work is needed. I am listing a background on limitations for each iOS version and then providing the solution. because while explaining the solution you need to understand the reason behind it.

Update (iOS 9+)

Solution Key: semanticContentAttribute

Some says that this way is not official and may cause efficiency problems.

UI direction can be forced now without closing the app:

UIView.appearance().semanticContentAttribute = .forceRightToLeft

Note, bars in iOS 9 can't be forced to RTL (Navigation and TabBar). While it get forced with iOS 10.

The only remaining thing that can't be forced RTL in iOS 10 is the default back button.

Changing app the language along with forcing the layout doesn't automatically forces the system to use the localized string file associated with the language.

Below iOS 9

Short answer:

Changing the app language will not force the layout direction according to the selected language without an app restart.

Apple's guide line:

Apple doesn't provide that because they don't prefere to allow the user to change language from inside the app. Apple pushes the developers to use the device language. and not to change it from inside the app at all.

Workarwound:

Some apps that supports Arabic language notes the user, in the settings page where the user can change the language, that the layout will not take effect without an app restart. store link for sample app

Example code to change app language to arabic:

[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"ar", @"en", nil] forKey:@"AppleLanguages"];

That won't take effect without a restart


How to change app language without an app restart

Solution is to provide your own localization solution:

  1. Use Base Internationalization but don't localize storyboards.
  2. Create one strings file and localise it to all languages and base for (english).
  3. Place the change language feature in a ViewController that starts before any other ViewController even before your main one. OR use an unwindSegue to go back to the root view controller after changing the language.
  4. Set strings for your views in viewDidLoad method for all view controllers.

Don't satisfy points 5 and 6 if you are not supporting below iOS 9

  1. Set the constraints for your views in viewDidLoad method for all view controllers.

  2. When you set the constraints use right/left according to the language selected instead of leading/trailing.


Language class sample (Swift): Initialise an object of this class in your singlton class.

class LanguageDetails: NSObject {

    var language: String!
    var bundle: Bundle!
    let LANGUAGE_KEY: String = "LANGUAGE_KEY"

    override init() {
        super.init()

        // user preferred languages. this is the default app language(device language - first launch app language)!
        language = Bundle.main.preferredLocalizations[0]

        // the language stored in UserDefaults have the priority over the device language.
        language = Common.valueForKey(key: LANGUAGE_KEY, default_value: language as AnyObject) as! String

        // init the bundle object that contains the localization files based on language
        bundle = Bundle(path: Bundle.main.path(forResource: language == "ar" ? language : "Base", ofType: "lproj")!)

        // bars direction
        if isArabic() {
            UIView.appearance().semanticContentAttribute = .forceRightToLeft
        } else {
            UIView.appearance().semanticContentAttribute = .forceLeftToRight
        }
    }

    // check if current language is arabic
    func isArabic () -> Bool {
        return language == "ar"
    }

    // returns app language direction.
    func rtl () -> Bool {
        return Locale.characterDirection(forLanguage: language) == Locale.LanguageDirection.rightToLeft
    }

    // switches language. if its ar change it to en and vise-versa
    func changeLanguage()
    {
        var changeTo: String
        // check current language to switch to the other.
        if language == "ar" {
            changeTo = "en"
        } else {
            changeTo = "ar"
        }

        // change language
        changeLanguageTo(lang: changeTo)

        Log.info("Language changed to: \(language)")
    }

    // change language to a specfic one.
    func changeLanguageTo(lang: String) {
        language = lang

        // set language to user defaults
        Common.setValue(value: language as AnyObject, key: LANGUAGE_KEY)

        // set prefered languages for the app.
        UserDefaults.standard.set([lang], forKey: "AppleLanguages")
        UserDefaults.standard.synchronize()

        // re-set the bundle object based on the new langauge
        bundle = Bundle(path: Bundle.main.path(forResource: language == "ar" ? language : "Base", ofType: "lproj")!)

        // app direction
        if isArabic() {
            UIView.appearance().semanticContentAttribute = .forceRightToLeft
        } else {
            UIView.appearance().semanticContentAttribute = .forceLeftToRight
        }

        Log.info("Language changed to: \(language)")
    }

    // get local string
    func getLocale() -> NSLocale {
        if rtl() {
            return NSLocale(localeIdentifier: "ar_JO")
        } else {
            return NSLocale(localeIdentifier: "en_US")
        }
    }

    // get localized string based on app langauge.
    func LocalString(key: String) -> String {
        let localizedString: String? = NSLocalizedString(key, bundle: bundle, value: key, comment: "")
//        Log.ver("Localized string '\(localizedString ?? "not found")' for key '\(key)'")
        return localizedString ?? key
    }

    // get localized string for specific language
    func LocalString(key: String, lan: String) -> String {
        let bundl:Bundle! = Bundle(path: Bundle.main.path(forResource: lan == "ar" ? lan : "Base", ofType: "lproj")!)
        return NSLocalizedString(key, bundle: bundl, value: key, comment: "")
    }
}

Language class sample (Objective-c): Swift sample provides full solution. sorry you need to convert it yourself.

@implementation LanguageDetails

@synthesize language;
@synthesize bundle;

#define LANGUAGE_KEY @"LANGUAGE_KEY"

// language var is also the strings file name ar or en

- (id)init
{
    if (self = [super init]) {

        language = [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0];

        if (![language isEqualToString:@"ar"])
            language = @"en";

        language = [Common valueForKey:LANGUAGE_KEY defaultValue:language];

        bundle = [NSBundle mainBundle];
    }
    return  self;
}

- (BOOL)rtl {
    return [NSLocale characterDirectionForLanguage:language] == NSLocaleLanguageDirectionRightToLeft;
}

- (void)changeLanguage
{
    if ([language isEqualToString:@"ar"])
        language = @"en";
    else
        language = @"ar";

    [Common setValue:language forKey:LANGUAGE_KEY];
}

- (void)changeLanguageTo:(NSString *)lang
{
    language = lang;

    [Common setValue:language forKey:LANGUAGE_KEY];
}

- (NSString *) LocalString:(NSString *)key {
    return NSLocalizedStringFromTableInBundle(key, language, bundle, nil);
}

@end
Helvetia answered 7/8, 2015 at 12:27 Comment(8)
Thanks for the great method to change language. After changing language I am initializing my root view controller from storyboard and assigning to window root. But then my navigation transition(Push or pop) becomes too slow . Please suggest any solutionHugo
I mentioned in the answer that: Some says that this way is not official and may cause efficiency problems. But, myself I didn't experience such a thing. In my implementation I don't change the root view.Helvetia
I have same problem as @Mr. SS, when i change language I set new root view. But my navigation transition(Push or pop) becomes too slow . any solution ?Guardsman
@Guardsman what if u don't change the direction at all and just set the root view controller? try that maybe its not because of the direction.Helvetia
I just removed the code that change language and navigation transition work fine, so the problem come from localizationGuardsman
I just debugged, the problem come from UIView.appearance().semanticContentAttribute but it's not possible to change UI without this line :sGuardsman
@Guardsman this guy here changes some values in extra UserDefaults. src-bin.com/en/q/1c98b4e maybe it works like this! tell me with the resultsHelvetia
Still same problem :(Guardsman
F
5

use Directional Layout Margins (iOS 11.0+)

enter image description here

and set Views to

Right To Left

 UIView.appearance().semanticContentAttribute = .forceRightToLeft

Left To Right

 UIView.appearance().semanticContentAttribute = .forceLeftToRight
Faludi answered 13/2, 2019 at 6:46 Comment(0)
W
3

Use this line of code it will do your magic and will change layout without closing application. From right to left

UIView.appearance().semanticContentAttribute = .forceRightToLeft

And for Right to Left 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
        }
    }
}
}
Wenda answered 29/11, 2018 at 7:5 Comment(0)
U
1

@ashwin, I was trying to do the exact same thing as you were.

I was hoping to find I could turn my app into a RTL language (right to left) just by changing something in the info.plist, appdelegate or who knows what. The idea is that someone with a LTR device can have my app in RTL, changing it within the app and with no need to restart it.

Looks like there is not such a thing (I'll be glad to hear if anyone disagrees with this). However, I've found some options that are not that bad.

  1. It turns out you can force a specific view to be LTR or RTL. Thus you can set this property on every view of your app. The way you do it is up to you.

    self.view.semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;

Reference: UISemanticContentAttribute

  1. You can always flip views horizontally until you get the desired setup.

[view setTransform:CGAffineTransformMakeScale(1, 1)];

Here's some code that might help you.

void reloadRTLViewAndSubviews(UIView *view)
{
    reloadRTLViews(@[view]);
    reloadRTLViews([view subviews]);
}

void reloadRTLViews(NSArray *views)
{
    if (isRTL())
    {
        [views enumerateObjectsUsingBlock:^(UIView* view,
                                            NSUInteger idx,
                                            BOOL * stop)
         {
             [view setTransform:CGAffineTransformMakeScale(-1, 1)];
         }];
    }
    else
    {
        [views enumerateObjectsUsingBlock:^(UIView* view,
                                            NSUInteger idx,
                                            BOOL * stop)
         {
             [view setTransform:CGAffineTransformMakeScale(1, 1)];
         }];
    }
}

BOOL isRTL()
{
    return isRTL_app();
}

BOOL isRTL_device()
{
    BOOL isRTL = ([NSLocale characterDirectionForLanguage:[[NSLocale preferredLanguages] objectAtIndex:0]] == NSLocaleLanguageDirectionRightToLeft);

    return isRTL;
}

BOOL isRTL_scheme()
{
    BOOL isRTL = ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:[UIView new].semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft);

    return isRTL;
}

BOOL isRTL_app()
{
    NSString *languageIdentifier = [AppDataManager sharedManager].languageIdentifier;
    NSArray *rtl_languages = @[@"ar"];

    BOOL isRTL;

    if ((languageIdentifier == nil) || (languageIdentifier.length == 0))
    {
        isRTL = (isRTL_device() || isRTL_scheme());
    }
    else if ([rtl_languages containsObject:languageIdentifier])
    {
        isRTL = YES;
    }
    else
    {
        isRTL = NO;
    }

    return isRTL;
}

BOOL deviceLanguageDirectionEqualAppLanguageDirection()
{
    if ((isRTL_device() || isRTL_scheme()) && isRTL())//All RTL
    {
        return YES;
    }
    else if (!(isRTL_device() || isRTL_scheme()) && !isRTL())//All LTR
    {
        return YES;
    }

    return NO;//RTL-LTR or LTR-RTL
}

void transformViews(NSArray *views)
{
    [views enumerateObjectsUsingBlock:^(UIView* view,
                                        NSUInteger idx,
                                        BOOL * stop)
     {
         [view setTransform:CGAffineTransformMakeScale(-1, 1)];
     }];
}

So if you were to change a UIViewController to be in RTL you can make all the setup so that it would suffice to:

reloadRTLViewAndSubviews(self.view);

Note: this is not the way it should be and Apple's guidelines say that the language should be change from the iOS settings. But this solution works if the language must be changed within the app and a language direction change between LTR and RTL is involved. And no app reset needed either.

I hope it helped.

Uncaused answered 8/1, 2016 at 17:14 Comment(3)
I used the same code. It is working awesome. Now i am trying same code for UITableViewCell, but it is not working. I tried as reloadRTLViewAndSubviews(cell), reloadRTLViewAndSubviews(cell.contentView) but both are not working. Can you provide solution for it.Gynaeco
If you are using the standard UITableViewCell without subclassing, the app will work "intelligently" and will resize the labels. The solution would be to subclass the cell and create your own layout. Another option is to just transform the UILabels within the CellForRow method. I am going to add two more methods to my answer that might be helpful: BOOL deviceLanguageDirectionEqualAppLanguageDirection() void transformViews(NSArray *views)Uncaused
You can do something like: if (!deviceLanguageDirectionEqualAppLanguageDirection()) { transformViews(@[cell.contentView, cell.textLabel, cell.detailTextLabel]); }Uncaused

© 2022 - 2024 — McMap. All rights reserved.