Skip to content

Commit

Permalink
Adding apply(locale:) method, Refactoring, Adding documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
amosavian committed Feb 20, 2018
1 parent e82be70 commit 73e354a
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 47 deletions.
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,25 +72,32 @@ When user selected a localization, follow next step.
Nothing special here, just add following line:

```swift
let languageId = "fa"
LocaleManager.apply(identifier: languageId)
let localeID = "fa"
LocaleManager.apply(identifier: localeID)
```

If you have `Locale` object instead of identifier:

```swift
let locale = Locale(identifier: "fa")
LocaleManager.apply(locale: locale)
```

This will cause a flip animation while changing language. If you don't want that:

```swift
let languageId = "en"
LocaleManager.apply(identifier: languageId, animated: false)
let localeID = "en"
LocaleManager.apply(identifier: localeID, animated: false)
```

To remove any custom localization and allow iOS to select a localization according system language:

```swift
LocaleManager.apply(identifier: nil)
LocaleManager.apply(locale: nil)
```

If you used other libraries like [maximbilan/ios_language_manager](https://github.com/maximbilan/ios_language_manager) before,
call `LocaleManager.apply(identifier: nil)` for the first time to remove remnants in order to avoid conflicting.
call `LocaleManager.apply(locale: nil)` for the first time to remove remnants in order to avoid conflicting.

### Get active locale

Expand Down Expand Up @@ -138,9 +145,9 @@ Due to an underlying bug in iOS, if you have an image which should be flipped fo
don't use asset's direction property to mirror image,
use `image.imageFlippedForRightToLeftLayoutDirection()` to initialize flippable image instead.

### Fixing non-updating localized strings
### Extra steps

If you encounter a problem in updating localized strings (e.g. tabbar items' title) set `LocaleManager.updateHandler` variable to a block which manually fix and update UI element.
If your app needs extra steps for updating interface (e.g. clearing caches), use `LocaleManager.updateHandler` property.

## Known issues

Expand Down
96 changes: 57 additions & 39 deletions Sources/LocaleManager/LocaleManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ public class LocaleManager: NSObject {
- Parameter identifier: Locale identifier to be applied, e.g. `en`, `fa`, `de_DE`, etc.
*/
private class func applyLocale(identifier: String) {
UserDefaults.standard.set([identifier], forKey: "AppleLanguages")
private class func setLocale(identifiers: [String]) {
UserDefaults.standard.set(identifiers, forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
Locale.cachePreffered = nil
}
Expand All @@ -110,15 +110,14 @@ public class LocaleManager: NSObject {
}

/**
Overrides system-wide locale in application and reload.
Overrides system-wide locale in application and reloads interface.
- Parameter identifier: Locale identifier to be applied, e.g. `en` or `fa_IR`. `nil` value will change locale to system-wide.
*/
@objc public class func apply(identifier: String?, animated: Bool = true) {
@objc public class func apply(locale: Locale?, animated: Bool = true) {
let semantic: UISemanticContentAttribute
if let identifier = identifier {
applyLocale(identifier: identifier)
let locale = Locale(identifier: identifier)
if let locale = locale {
setLocale(identifiers: [locale.identifier])
semantic = locale.isRTL ? .forceRightToLeft : .forceLeftToRight
} else {
removeLocale()
Expand All @@ -131,6 +130,16 @@ public class LocaleManager: NSObject {
updateHandler()
}

/**
Overrides system-wide locale in application and reloads interface.
- Parameter identifier: Locale identifier to be applied, e.g. `en` or `fa_IR`. `nil` value will change locale to system-wide.
*/
@objc public class func apply(identifier: String?, animated: Bool = true) {
let locale = identifier.map(Locale.init(identifier:))
apply(locale: locale, animated: animated)
}

/**
This method MUST be called in `application(_:didFinishLaunchingWithOptions:)` method.
*/
Expand Down Expand Up @@ -213,45 +222,54 @@ extension UIApplication {
extension Bundle {
private static var savedLanguageNames: [String: String] = [:]

@objc func mn_custom_localizedString(forKey key: String, value: String?, table tableName: String?) -> String {

func languageName(for lang: String) -> String? {
if let langName = Bundle.savedLanguageNames[lang] {
return langName
}
let langName = Locale(identifier: "en").localizedString(forLanguageCode: lang)
Bundle.savedLanguageNames[lang] = langName
private func languageName(for lang: String) -> String? {
if let langName = Bundle.savedLanguageNames[lang] {
return langName
}

let langName = Locale(identifier: "en").localizedString(forLanguageCode: lang)
Bundle.savedLanguageNames[lang] = langName
return langName
}

fileprivate func resourcePath(for locale: Locale) -> String? {
/*
After swizzling localizedString() method, this procedure will be used even for system provided frameworks.
Thus we shall try to find appropriate localization resource in current bundle, not main.
Apple's framework
Trying to find appropriate lproj resource in the bundle ordered by:
1. Locale identifier (e.g: en_GB, fa_IR)
2. Locale language code (e.g en, fa)
3. Locale language name (e.g English, Japanese), used as resource name in Apple system bundles' localization (Foundation, UIKit, ...)
*/
if let path = self.path(forResource: locale.identifier, ofType: "lproj") {
return path
} else if let path = locale.languageCode.flatMap(languageName(for:)).flatMap({ self.path(forResource: $0, ofType: "lproj") }) {
return path
} else if let path = languageName(for: locale.identifier).flatMap({ self.path(forResource: $0, ofType: "lproj") }) {
return path
} else {
return nil
}
}

@objc func mn_custom_localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
if let customString = LocaleManager.customTranslation?(key) {
return customString
}

let userPreferred = Locale._userPreferred.identifier
let current = Locale.current.identifier
/*
Trying to find lproj resource first in user preferred locale, then system-wide current locale, and finally "Base"
*/
let bundle: Bundle
// Check for user preferred locale (e.g en_GB, ru)
if let _path = self.path(forResource: userPreferred, ofType: "lproj") {
bundle = Bundle(path: _path)!
} else
// Check for user preferred locale if system uses old naming (e.g English, Japanese)
if let _path = languageName(for: userPreferred).flatMap({ self.path(forResource: $0, ofType: "lproj") }) {
bundle = Bundle(path: _path)!
} else
// Check for system-defined current locale
if let _path = self.path(forResource: current, ofType: "lproj") {
bundle = Bundle(path: _path)!
} else
// Check for system-defined current locale if system uses old naming (e.g English, Japanese)
if let _path = languageName(for: current).flatMap({ self.path(forResource: $0, ofType: "lproj") }) {
bundle = Bundle(path: _path)!
} else
// Check for base locale ("Base")
if let _path = self.path(forResource: LocaleManager.base, ofType: "lproj") {
bundle = Bundle(path: _path)!
// No bundle, returning passed string
} else {
if let path = resourcePath(for: Locale._userPreferred) {
bundle = Bundle(path: path)!
} else if let path = resourcePath(for: Locale.current) {
bundle = Bundle(path: path)!
} else if let path = self.path(forResource: LocaleManager.base, ofType: "lproj") {
bundle = Bundle(path: path)!
} else {
return key
}
return (bundle.mn_custom_localizedString(forKey: key, value: value, table: tableName))
Expand Down

0 comments on commit 73e354a

Please sign in to comment.