Dynamic Localization framework in iOS

JRLocalization is a generalized framework where we can store and configure any number of languages with the ease of writing minimum logic in the client application.

The Basic Architecture:

  1. JRLocalizationConfiguration: This is the main class for framework configuration. Public methods that are exposed to the main app are below:
     
  2. LanguageManager: This singleton class is the main bridge between the application and the framework. Access this class by calling LanguageManager.shared All the functionalities like In-App switching, getting current locale are provided by this class only. LanguageManger internally interacts with the following classes inside the framework:

    1. DirectoryManager: Class responsible for fetching and storing the language file from the directory.
    2. DownloadManager: Class responsible for managing the downloading of languages from the external server and storing the same in Language.bundle with the help of DirectoryManager.
    3. SessionServiceManager: Networking layer for JRLocalization framework.
       
  3. Class extensions: The framework consists of following class extensions to automatically handle the key-to-string conversion logic:

    1. UILabel extension
    2. UIButton extension
    3. UITextField extension
    4. UITextView extension
    5. UiNavigationItem extension
    6. UIBarButton extension
    7. UISearchBar extension
       
  4. JRLocale: The basic language model with the following parameters:
    1. name: for ex- System, English, Hindi
    2. languageCode: for ex- system, en, hi
    3. countryCode: for ex- IN

Directory Structure:

The framework stores all the languages in the cache directory of the main application, which could be purged when memory runs low on the device. The app needs to take care of the constant updation of language downloaded status. And needs to fallback to the Base language when the memory is purged.  

The framework has its custom bundle (Language.bundle) to store all the user downloaded languages. However, the Base language must exist in the Main bundle as at the framework initialization the framework copies the base languages to a cache directory.

APIs exposed by the framework:

JRLocalizationConfiguration:

@objc public class func setDefaultConfiguration(isDynamicLanguageBundleEnabled : Bool = false)

This method needs to be called from client application from Main.m/Main.swift.

The parameter isDynamicLanguageBundleEnabled indicates whether the language should be fetched from the Framework or main app bundle. 

@objc public class func setDymamicLanguageEnabled(isDynamicLanguageEnabled : Bool = false)

This method should be invoked from the main app at any time when user wants to toggle language fetching functionality from the framework or the main app bundle.

The parameter isDynamicLanguageEnabled indicates whether the language should be fetched from the Framework or main app bundle. 

LanguageManager:

@objc public var availableLocales : [JRLocale]

The property availableLocales at any time gives the list of languages that application supports.

@objc public var isDynamicLanguageEnabled

The property isDynamicLanguageEnabled indicates whether the framework is enabled or not.

@objc public func setLanguage(withLanugageCode code : String)

The method setLanguage() sets the current language to the languages passed by the user, if the language passed by the user is not present in the download directory then the system will be chosen as a fallback case.

@Parameter withLanugageCode:  The language code to be selected. For example – “en” for English.

@objc public func setSystemLanguage()

The method setSystemLanguage() sets the current language to the system language.

@objc public func getCurrentLocale() -> JRLocale?

The method getCurrentLocale() gives the current language selected for the application.

@returns: JRLocale model

@objc public func getLocale(forLanguageCode languageCode: String) -> JRLocale?

The method getLocale() gives the Locale model for a particular language code.

@parameter forLanguageCode: The Language code for which locale model is to be retrieved.

@returns: JRLocale model

@objc public func checkIfLanguageAvailable(forLanguageCode languageCode : String) -> Bool

The method checkIfLanguageAvailable() checks if the app supports a particular language.

@parameter forLanguageCode: The Language code which needs to be checked.

@returns: true or false depending upon whether the app supports the language or not.

@objc public func checkIfLanguageDownloaded(forLanguageCode languageCode : String) -> Bool

The method checkIfLanguageDownloaded() checks the download status for a particular language.

@parameter forLanguageCode: The Language code which needs to be checked.

@returns: true or false depending upon whether the language is already downloaded or not.

@objc public func downloadLanguage(forLanguageCode code: String, andRequestTuple requestTuple : JRLocalizationRequestTuple, withCompletionHandler handler : @escaping(_ success : Bool)->())

The method downloadLanguage() downloads a language and stores it in Language.bundle.

@parameter forLanguageCode: The Language code which is being downloaded.

@parameter andRequestTuple: A tuple of type JRLocalizationRequestTuple, which contains all the required details for downloading the language. The tuple’s signature is given below:

(inUrl:String, inJsonDic:AnyObject?, isCookiesRequired : Bool, inRequestType : String, inContentType : enJRLocalizationRequestContentType,

inHeaderDic : [String : String])

@paramter withCompletionHandler: The completion handler for download, the client app can switch the language if the download has been successfully completed or can show an error message if it has been failed. The completion handled has one parameter ‘success’ that depicts whether the language has been downloaded and stored in the bundle.

Things that need to be taken care of in the client application:

Localizing Storyboard:

The traditional practice for localizing storyboard is to create different strings file for each language but with the framework added, localization for storyboard/xib’s become extremely easy.

For localizing UI elements from storyboard or xib, all we have to do is to write the keys instead of actual strings and the framework will take care of localization under the hood. Forex- In storyboard TextField ‘text’ and ‘placeholder’ should contain keys instead of the text.

Exceptions: 

You can add exceptions for strings that need not be localized. Every UI items have a property called isLocalizable with three values: default, Off and On. Where default is On.

If the value for isLocalizable is Off, the text for the corresponding element won’t be localized.

Localizing Strings in the code:

There are several ways a string can be localized:

Swift:

  1. public func NSLocalizedString(_ key: String) -> String
    This function will take the parameter as the key and returns the corresponding value according to the current language selected.
    forex- NSLocalizedString(“keyToBeLocalized”)
    [NOTE]: Use only the method with the above signature. Any of the other methods of NSLocalizedString will result in the following error:
  2. “keyToBeLocalized”.localized
    This string extension will also give the localized string for any key.

Objective C:

Any of the predefined macros can be used for the localization:

  1. #define NSLocalizedString(key, comment)
  2. #define NSLocalizedStringFromTable(key, tbl, comment)
  3. #define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment)
  4. #define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment)

Localizing keyboard:

The Localization of keyboard is automatically handled by the framework and client app does not need to change anything.

Localizing Info.plist:

The Info.plist file could be manually localized in order to localize the application name and copyright info. This has to be done manually as info.plist is a small file and generating different flavors for each language should not be of much size concern.

Localizing App logo:

If required, we could also localize the app logo using  â€˜supportsAlternateIcons’. The value of this property is true only when the system allows you to change the icon of your app. To declare your app’s alternate icons, include them in the CFBundleIcons key of your app’s Info.plist file.

Localizing Numbers, Dates, currencies, etc:

The framework will provide the current locale from the method LanguageManager.shared.getCurrentLocale()  this method will return the JRLocaleModel, the Client app can then use the current locale to set the locale in dateFormatters, currencies, etc.

Configuring framework in the client application:

Follow the steps to add JRLocalization.framework to the client application:

Add a new Language.plist file with all the languages that app can support

Copy the framework in any directory in the client application and add the framework to “Embedded binaries”.

Add a new run script in Build phases and add following lines:

bash ./JRLocalization.framework/strip-frameworks.sh

Change the path as per your framework path. If it’s in the project’s root directory the above path will work seamlessly.

Open Main.m/Main.swift and initialize the framework:

Swift: 

import JRLocalization
JRLocalizationConfiguration.setDefaultConfiguration(isDynamicLanguageBundleEnabled: true)

Objective C:

#import <JRLocalization/JRLocalization-Swift.h>[JRLocalizationConfiguration setDefaultConfigurationWithIsDynamicLanguageBundleEnabled:true];

The framework is now configured and ready to use.

You can download the framework from here.