Browse Source

Add a new feature: `MJRefresh components` will be rerendered automatically with `MJRefreshConfig.default.language` setting. (#1527)

* Add a new feature: `MJRefresh components` will be re-rendered automatically with `MJRefreshConfig.default.language` setting.
Frank 4 years ago
parent
commit
7edc769c56
29 changed files with 370 additions and 61 deletions
  1. 7 0
      MJRefresh/Base/MJRefreshComponent.h
  2. 7 0
      MJRefresh/Base/MJRefreshComponent.m
  3. 15 4
      MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.m
  4. 14 5
      MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.m
  5. 15 4
      MJRefresh/Custom/Header/MJRefreshStateHeader.m
  6. 14 4
      MJRefresh/Custom/Trailer/MJRefreshStateTrailer.m
  7. 1 0
      MJRefresh/MJRefresh.h
  8. 13 2
      MJRefresh/MJRefreshConfig.h
  9. 19 1
      MJRefresh/MJRefreshConfig.m
  10. 2 0
      MJRefresh/MJRefreshConst.h
  11. 2 0
      MJRefresh/MJRefreshConst.m
  12. 55 24
      MJRefresh/NSBundle+MJRefresh.m
  13. 46 1
      MJRefreshExample.xcodeproj/project.pbxproj
  14. 16 10
      MJRefreshExample/Base.lproj/Main.storyboard
  15. 1 1
      MJRefreshExample/Classes/First/MJTempViewController.m
  16. BIN
      MJRefreshExample/Classes/i18n/CustomLanguages.bundle/en.lproj/Localizable.strings
  17. BIN
      MJRefreshExample/Classes/i18n/CustomLanguages.bundle/ko.lproj/Localizable.strings
  18. BIN
      MJRefreshExample/Classes/i18n/CustomLanguages.bundle/ru.lproj/Localizable.strings
  19. BIN
      MJRefreshExample/Classes/i18n/CustomLanguages.bundle/uk.lproj/Localizable.strings
  20. BIN
      MJRefreshExample/Classes/i18n/CustomLanguages.bundle/zh-Hans.lproj/Localizable.strings
  21. BIN
      MJRefreshExample/Classes/i18n/CustomLanguages.bundle/zh-Hant.lproj/Localizable.strings
  22. 90 0
      MJRefreshExample/Classes/i18n/I18NExampleViewController.swift
  23. BIN
      MJRefreshExample/Classes/i18n/en.lproj/MJRefresh_i18n.strings
  24. BIN
      MJRefreshExample/Classes/i18n/ko.lproj/MJRefresh_i18n.strings
  25. BIN
      MJRefreshExample/Classes/i18n/ru.lproj/MJRefresh_i18n.strings
  26. BIN
      MJRefreshExample/Classes/i18n/uk.lproj/MJRefresh_i18n.strings
  27. BIN
      MJRefreshExample/Classes/i18n/zh-Hans.lproj/MJRefresh_i18n.strings
  28. BIN
      MJRefreshExample/Classes/i18n/zh-Hant.lproj/MJRefresh_i18n.strings
  29. 53 5
      README.md

+ 7 - 0
MJRefresh/Base/MJRefreshComponent.h

@@ -107,6 +107,13 @@ typedef void (^MJRefreshComponentAction)(void);
 /** 当scrollView的拖拽状态发生改变的时候调用 */
 - (void)scrollViewPanStateDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER;
 
+/** 多语言配置 language 发生变化时调用
+ 
+ `MJRefreshConfig.defaultConfig.language` 发生改变时调用.
+ 
+ ⚠️ 父类会调用 `placeSubviews` 方法, 请勿在 placeSubviews 中调用本方法, 造成死循环. 子类在需要重新布局时, 在配置完修改后, 最后再调用 super 方法, 否则可能导致配置修改后, 定位先于修改执行.
+ */
+- (void)i18nDidChange NS_REQUIRES_SUPER;
 
 #pragma mark - 其他
 /** 拉拽的百分比(交给子类重写) */

+ 7 - 0
MJRefresh/Base/MJRefreshComponent.m

@@ -8,6 +8,7 @@
 
 #import "MJRefreshComponent.h"
 #import "MJRefreshConst.h"
+#import "MJRefreshConfig.h"
 
 @interface MJRefreshComponent()
 @property (strong, nonatomic) UIPanGestureRecognizer *pan;
@@ -92,6 +93,8 @@ - (void)addObservers
     [self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentSize options:options context:nil];
     self.pan = self.scrollView.panGestureRecognizer;
     [self.pan addObserver:self forKeyPath:MJRefreshKeyPathPanState options:options context:nil];
+    
+    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(i18nDidChange) name:MJRefreshDidChangeLanguageNotification object:MJRefreshConfig.defaultConfig];
 }
 
 - (void)removeObservers
@@ -125,6 +128,10 @@ - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{}
 - (void)scrollViewContentSizeDidChange:(NSDictionary *)change{}
 - (void)scrollViewPanStateDidChange:(NSDictionary *)change{}
 
+- (void)i18nDidChange {
+    [self placeSubviews];
+}
+
 #pragma mark - 公共方法
 #pragma mark 设置回调对象和回调方法
 - (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action

+ 15 - 4
MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.m

@@ -52,6 +52,13 @@ - (void)stateLabelClick
     }
 }
 
+- (void)textConfiguration {
+    // 初始化文字
+    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterIdleText] forState:MJRefreshStateIdle];
+    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterRefreshingText] forState:MJRefreshStateRefreshing];
+    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterNoMoreDataText] forState:MJRefreshStateNoMoreData];
+}
+
 #pragma mark - 重写父类的方法
 - (void)prepare
 {
@@ -60,16 +67,20 @@ - (void)prepare
     // 初始化间距
     self.labelLeftInset = MJRefreshLabelLeftInset;
     
-    // 初始化文字
-    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterIdleText] forState:MJRefreshStateIdle];
-    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterRefreshingText] forState:MJRefreshStateRefreshing];
-    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterNoMoreDataText] forState:MJRefreshStateNoMoreData];
+    [self textConfiguration];
     
     // 监听label
     self.stateLabel.userInteractionEnabled = YES;
     [self.stateLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(stateLabelClick)]];
 }
 
+- (void)i18nDidChange {
+    [self textConfiguration];
+    
+    [super i18nDidChange];
+}
+
+
 - (void)placeSubviews
 {
     [super placeSubviews];

+ 14 - 5
MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.m

@@ -48,6 +48,14 @@ - (NSString *)titleForState:(MJRefreshState)state {
   return self.stateTitles[@(state)];
 }
 
+- (void)textConfiguration {
+    // 初始化文字
+    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterIdleText] forState:MJRefreshStateIdle];
+    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterPullingText] forState:MJRefreshStatePulling];
+    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterRefreshingText] forState:MJRefreshStateRefreshing];
+    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterNoMoreDataText] forState:MJRefreshStateNoMoreData];
+}
+
 #pragma mark - 重写父类的方法
 - (void)prepare
 {
@@ -55,12 +63,13 @@ - (void)prepare
     
     // 初始化间距
     self.labelLeftInset = MJRefreshLabelLeftInset;
+    [self textConfiguration];
+}
+
+- (void)i18nDidChange {
+    [self textConfiguration];
     
-    // 初始化文字
-    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterIdleText] forState:MJRefreshStateIdle];
-    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterPullingText] forState:MJRefreshStatePulling];
-    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterRefreshingText] forState:MJRefreshStateRefreshing];
-    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterNoMoreDataText] forState:MJRefreshStateNoMoreData];
+    [super i18nDidChange];
 }
 
 - (void)placeSubviews

+ 15 - 4
MJRefresh/Custom/Header/MJRefreshStateHeader.m

@@ -102,6 +102,15 @@ - (void)setLastUpdatedTimeKey:(NSString *)lastUpdatedTimeKey
     }
 }
 
+
+- (void)textConfiguration {
+    // 初始化文字
+    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderIdleText] forState:MJRefreshStateIdle];
+    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderPullingText] forState:MJRefreshStatePulling];
+    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderRefreshingText] forState:MJRefreshStateRefreshing];
+    self.lastUpdatedTimeKey = MJRefreshHeaderLastUpdatedTimeKey;
+}
+
 #pragma mark - 覆盖父类的方法
 - (void)prepare
 {
@@ -109,11 +118,13 @@ - (void)prepare
     
     // 初始化间距
     self.labelLeftInset = MJRefreshLabelLeftInset;
+    [self textConfiguration];
+}
+
+- (void)i18nDidChange {
+    [self textConfiguration];
     
-    // 初始化文字
-    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderIdleText] forState:MJRefreshStateIdle];
-    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderPullingText] forState:MJRefreshStatePulling];
-    [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderRefreshingText] forState:MJRefreshStateRefreshing];
+    [super i18nDidChange];
 }
 
 - (void)placeSubviews

+ 14 - 4
MJRefresh/Custom/Trailer/MJRefreshStateTrailer.m

@@ -41,16 +41,26 @@ - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state {
     return self;
 }
 
-#pragma mark - 覆盖父类的方法
-- (void)prepare {
-    [super prepare];
-    
+- (void)textConfiguration {
     // 初始化文字
     [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerIdleText] forState:MJRefreshStateIdle];
     [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerPullingText] forState:MJRefreshStatePulling];
     [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerPullingText] forState:MJRefreshStateRefreshing];
 }
 
+#pragma mark - 覆盖父类的方法
+- (void)prepare {
+    [super prepare];
+    
+    [self textConfiguration];
+}
+
+- (void)i18nDidChange {
+    [self textConfiguration];
+    
+    [super i18nDidChange];
+}
+
 - (void)setState:(MJRefreshState)state {
     MJRefreshCheckState
     // 设置状态文字

+ 1 - 0
MJRefresh/MJRefresh.h

@@ -13,3 +13,4 @@
 #import "MJRefreshAutoGifFooter.h"
 
 #import "MJRefreshNormalTrailer.h"
+#import "MJRefreshConfig.h"

+ 13 - 2
MJRefresh/MJRefreshConfig.h

@@ -14,8 +14,19 @@ NS_ASSUME_NONNULL_BEGIN
 /** 默认使用的语言版本, 默认为 nil. 将随系统的语言自动改变 */
 @property (copy, nonatomic, nullable) NSString *languageCode;
 
-/** @return Singleton Config instance */
-+ (instancetype)defaultConfig;
+/** 默认使用的语言资源文件名, 默认为 nil, 即默认的 Localizable.strings.
+ 
+ - Attention: 文件名不包含后缀.strings
+ */
+@property (copy, nonatomic, nullable) NSString *i18nFilename;
+/** i18n 多语言资源加载自定义 Bundle.
+ 
+ - Attention: 默认为 nil 采用内置逻辑. 这里设置后将忽略内置逻辑的多语言模式, 采用自定义的多语言 bundle
+ */
+@property (nonatomic, nullable) NSBundle *i18nBundle;
+
+/** Singleton Config instance */
+@property (class, nonatomic, readonly) MJRefreshConfig *defaultConfig;
 
 - (instancetype)init NS_UNAVAILABLE;
 + (instancetype)new NS_UNAVAILABLE;

+ 19 - 1
MJRefresh/MJRefreshConfig.m

@@ -6,6 +6,14 @@
 //
 
 #import "MJRefreshConfig.h"
+#import "MJRefreshConst.h"
+#import "NSBundle+MJRefresh.h"
+
+@interface MJRefreshConfig (Bundle)
+
++ (void)resetLanguageResourceCache;
+
+@end
 
 @implementation MJRefreshConfig
 
@@ -19,6 +27,16 @@ + (instancetype)defaultConfig {
     return mj_RefreshConfig;
 }
 
-
+- (void)setLanguageCode:(NSString *)languageCode {
+    if ([languageCode isEqualToString:_languageCode]) {
+        return;
+    }
+    
+    _languageCode = languageCode;
+    // 重置语言资源
+    [MJRefreshConfig resetLanguageResourceCache];
+    [NSNotificationCenter.defaultCenter
+     postNotificationName:MJRefreshDidChangeLanguageNotification object:self];
+}
 
 @end

+ 2 - 0
MJRefresh/MJRefreshConst.h

@@ -64,6 +64,8 @@ UIKIT_EXTERN NSString *const MJRefreshHeaderLastTimeText;
 UIKIT_EXTERN NSString *const MJRefreshHeaderDateTodayText;
 UIKIT_EXTERN NSString *const MJRefreshHeaderNoneLastDateText;
 
+UIKIT_EXTERN NSString *const MJRefreshDidChangeLanguageNotification;
+
 // 状态检查
 #define MJRefreshCheckState \
 MJRefreshState oldState = self.state; \

+ 2 - 0
MJRefresh/MJRefreshConst.m

@@ -35,3 +35,5 @@
 NSString *const MJRefreshHeaderLastTimeText = @"MJRefreshHeaderLastTimeText";
 NSString *const MJRefreshHeaderDateTodayText = @"MJRefreshHeaderDateTodayText";
 NSString *const MJRefreshHeaderNoneLastDateText = @"MJRefreshHeaderNoneLastDateText";
+
+NSString *const MJRefreshDidChangeLanguageNotification = @"MJRefreshDidChangeLanguageNotification";

+ 55 - 24
MJRefresh/NSBundle+MJRefresh.m

@@ -10,6 +10,9 @@
 #import "MJRefreshComponent.h"
 #import "MJRefreshConfig.h"
 
+static NSBundle *mj_defaultI18nBundle = nil;
+static NSBundle *mj_systemI18nBundle = nil;
+
 @implementation NSBundle (MJRefresh)
 + (instancetype)mj_refreshBundle
 {
@@ -45,37 +48,65 @@ + (NSString *)mj_localizedStringForKey:(NSString *)key
 
 + (NSString *)mj_localizedStringForKey:(NSString *)key value:(NSString *)value
 {
-    static NSBundle *bundle = nil;
-    if (bundle == nil) {
+    NSString *table = MJRefreshConfig.defaultConfig.i18nFilename;
+    
+    // 如果没有缓存, 则走初始化逻辑
+    if (mj_defaultI18nBundle == nil) {
         NSString *language = MJRefreshConfig.defaultConfig.languageCode;
         // 如果配置中没有配置语言
         if (!language) {
-            // (iOS获取的语言字符串比较不稳定)目前框架只处理en、zh-Hans、zh-Hant三种情况,其他按照系统默认处理
             language = [NSLocale preferredLanguages].firstObject;
         }
+        NSBundle *bundle = MJRefreshConfig.defaultConfig.i18nBundle;
+        // 首先优先使用公共配置中的 i18nBundle, 如果为空则使用 mainBundle
+        bundle = bundle ? bundle : NSBundle.mainBundle;
+        // 按语言选取语言包
+        NSString *i18nFolderPath = [bundle pathForResource:language ofType:@"lproj"];
+        mj_defaultI18nBundle = [NSBundle bundleWithPath:i18nFolderPath];
+        // 检查语言包, 如果没有查找到, 则默认使用 mainBundle
+        mj_defaultI18nBundle = mj_defaultI18nBundle ? mj_defaultI18nBundle : NSBundle.mainBundle;
         
-        if ([language hasPrefix:@"en"]) {
-            language = @"en";
-        } else if ([language hasPrefix:@"zh"]) {
-            if ([language rangeOfString:@"Hans"].location != NSNotFound) {
-                language = @"zh-Hans"; // 简体中文
-            } else { // zh-Hant\zh-HK\zh-TW
-                language = @"zh-Hant"; // 繁體中文
-            }
-        } else if ([language hasPrefix:@"ko"]) {
-            language = @"ko";
-        } else if ([language hasPrefix:@"ru"]) {
-            language = @"ru";
-        } else if ([language hasPrefix:@"uk"]) {
-            language = @"uk";
-        } else {
-            language = @"en";
+        // 获取 MJRefresh 自有的语言包
+        if (mj_systemI18nBundle == nil) {
+            mj_systemI18nBundle = [self mj_defaultI18nBundleWithLanguage:language];
         }
-        
-        // 从MJRefresh.bundle中查找资源
-        bundle = [NSBundle bundleWithPath:[[NSBundle mj_refreshBundle] pathForResource:language ofType:@"lproj"]];
     }
-    value = [bundle localizedStringForKey:key value:value table:nil];
-    return [[NSBundle mainBundle] localizedStringForKey:key value:value table:nil];
+    // 首先在 MJRefresh 内置语言文件中寻找
+    value = [mj_systemI18nBundle localizedStringForKey:key value:value table:nil];
+    // 然后在 MainBundle 对应语言文件中寻找
+    value = [mj_defaultI18nBundle localizedStringForKey:key value:value table:table];
+    return value;
+}
+
++ (NSBundle *)mj_defaultI18nBundleWithLanguage:(NSString *)language {
+    if ([language hasPrefix:@"en"]) {
+        language = @"en";
+    } else if ([language hasPrefix:@"zh"]) {
+        if ([language rangeOfString:@"Hans"].location != NSNotFound) {
+            language = @"zh-Hans"; // 简体中文
+        } else { // zh-Hant\zh-HK\zh-TW
+            language = @"zh-Hant"; // 繁體中文
+        }
+    } else if ([language hasPrefix:@"ko"]) {
+        language = @"ko";
+    } else if ([language hasPrefix:@"ru"]) {
+        language = @"ru";
+    } else if ([language hasPrefix:@"uk"]) {
+        language = @"uk";
+    } else {
+        language = @"en";
+    }
+    
+    // 从MJRefresh.bundle中查找资源
+    return [NSBundle bundleWithPath:[[NSBundle mj_refreshBundle] pathForResource:language ofType:@"lproj"]];
 }
 @end
+
+@implementation MJRefreshConfig (Bundle)
+
++ (void)resetLanguageResourceCache {
+    mj_defaultI18nBundle = nil;
+    mj_systemI18nBundle = nil;
+}
+
+@end

+ 46 - 1
MJRefreshExample.xcodeproj/project.pbxproj

@@ -7,6 +7,9 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		0133FBBF2693FA8E00E98F66 /* CustomLanguages.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0133FBBE2693FA8E00E98F66 /* CustomLanguages.bundle */; };
+		0193FF132692E99500B52B11 /* I18NExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0193FF122692E99500B52B11 /* I18NExampleViewController.swift */; };
+		0193FF162692F8EB00B52B11 /* MJRefresh_i18n.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0193FF182692F8EB00B52B11 /* MJRefresh_i18n.strings */; };
 		01A2CE0C21ACE01500BEE365 /* MJRefreshConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 01A2CE0B21ACE01500BEE365 /* MJRefreshConfig.m */; };
 		01A5EB2B25EF62B80091554E /* MJWKWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A5EB2A25EF62B80091554E /* MJWKWebViewController.swift */; };
 		2D4698861D0EE6A400CB8025 /* NSBundle+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D4698851D0EE6A400CB8025 /* NSBundle+MJRefresh.m */; };
@@ -128,6 +131,14 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+		0133FBB82693F7E200E98F66 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MJRefresh_i18n.strings"; sourceTree = "<group>"; };
+		0133FBB92693F7EA00E98F66 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MJRefresh_i18n.strings"; sourceTree = "<group>"; };
+		0133FBBB2693F80400E98F66 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/MJRefresh_i18n.strings; sourceTree = "<group>"; };
+		0133FBBC2693F81700E98F66 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/MJRefresh_i18n.strings; sourceTree = "<group>"; };
+		0133FBBD2693F81F00E98F66 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/MJRefresh_i18n.strings; sourceTree = "<group>"; };
+		0133FBBE2693FA8E00E98F66 /* CustomLanguages.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = CustomLanguages.bundle; sourceTree = "<group>"; };
+		0193FF122692E99500B52B11 /* I18NExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = I18NExampleViewController.swift; sourceTree = "<group>"; };
+		0193FF172692F8EB00B52B11 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MJRefresh_i18n.strings; sourceTree = "<group>"; };
 		01A2CE0A21ACE01500BEE365 /* MJRefreshConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJRefreshConfig.h; sourceTree = "<group>"; };
 		01A2CE0B21ACE01500BEE365 /* MJRefreshConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJRefreshConfig.m; sourceTree = "<group>"; };
 		01A5EB2925EF62B80091554E /* MJRefreshExample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MJRefreshExample-Bridging-Header.h"; sourceTree = "<group>"; };
@@ -257,6 +268,16 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		0193FF112692E86200B52B11 /* i18n */ = {
+			isa = PBXGroup;
+			children = (
+				0193FF122692E99500B52B11 /* I18NExampleViewController.swift */,
+				0193FF182692F8EB00B52B11 /* MJRefresh_i18n.strings */,
+				0133FBBE2693FA8E00E98F66 /* CustomLanguages.bundle */,
+			);
+			path = i18n;
+			sourceTree = "<group>";
+		};
 		01A5EB2825EF62930091554E /* SwiftExample */ = {
 			isa = PBXGroup;
 			children = (
@@ -417,6 +438,7 @@
 				2D9BEAE11BB15F4A00AED473 /* DIY */,
 				2D9BEAEE1BB15F4A00AED473 /* First */,
 				2D9BEAFE1BB15F4A00AED473 /* Second */,
+				0193FF112692E86200B52B11 /* i18n */,
 				01A5EB2825EF62930091554E /* SwiftExample */,
 			);
 			path = Classes;
@@ -654,6 +676,11 @@
 			knownRegions = (
 				en,
 				Base,
+				"zh-Hans",
+				"zh-Hant",
+				ru,
+				uk,
+				ko,
 			);
 			mainGroup = 2DA7F91C1AA6B4C4005627AB;
 			productRefGroup = 2DA7F9261AA6B4C4005627AB /* Products */;
@@ -680,10 +707,12 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				0133FBBF2693FA8E00E98F66 /* CustomLanguages.bundle in Resources */,
 				2DA7F9341AA6B4C4005627AB /* Main.storyboard in Resources */,
 				2D9BEB1C1BB15F4A00AED473 /* MJWebViewViewController.xib in Resources */,
 				2DB2EA251BECBE6700D58F6A /* MJRefresh.bundle in Resources */,
 				2DA7F9391AA6B4C4005627AB /* LaunchScreen.xib in Resources */,
+				0193FF162692F8EB00B52B11 /* MJRefresh_i18n.strings in Resources */,
 				2D9BEB1A1BB15F4A00AED473 /* MJTestViewController.xib in Resources */,
 				2DA7F9361AA6B4C4005627AB /* Images.xcassets in Resources */,
 			);
@@ -742,6 +771,7 @@
 				2D9BEB131BB15F4A00AED473 /* MJNavigationController.m in Sources */,
 				2DB2EA271BECBE6700D58F6A /* UIScrollView+MJExtension.m in Sources */,
 				2DB2EA1F1BECBE6700D58F6A /* MJRefreshBackGifFooter.m in Sources */,
+				0193FF132692E99500B52B11 /* I18NExampleViewController.swift in Sources */,
 				2DB2EA1C1BECBE6700D58F6A /* MJRefreshAutoGifFooter.m in Sources */,
 				2DB2EA231BECBE6700D58F6A /* MJRefreshNormalHeader.m in Sources */,
 				2DB2EA191BECBE6700D58F6A /* MJRefreshComponent.m in Sources */,
@@ -796,6 +826,19 @@
 /* End PBXTargetDependency section */
 
 /* Begin PBXVariantGroup section */
+		0193FF182692F8EB00B52B11 /* MJRefresh_i18n.strings */ = {
+			isa = PBXVariantGroup;
+			children = (
+				0193FF172692F8EB00B52B11 /* en */,
+				0133FBB82693F7E200E98F66 /* zh-Hans */,
+				0133FBB92693F7EA00E98F66 /* zh-Hant */,
+				0133FBBB2693F80400E98F66 /* ru */,
+				0133FBBC2693F81700E98F66 /* uk */,
+				0133FBBD2693F81F00E98F66 /* ko */,
+			);
+			name = MJRefresh_i18n.strings;
+			sourceTree = "<group>";
+		};
 		2DA7F9321AA6B4C4005627AB /* Main.storyboard */ = {
 			isa = PBXVariantGroup;
 			children = (
@@ -906,6 +949,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
@@ -961,6 +1005,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
@@ -1134,4 +1179,4 @@
 /* End XCConfigurationList section */
 	};
 	rootObject = 2DA7F91D1AA6B4C4005627AB /* Project object */;
-}
+}

+ 16 - 10
MJRefreshExample/Base.lproj/Main.storyboard

@@ -1,9 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="06B-cM-i4B">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19115.3" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="06B-cM-i4B">
     <device id="retina4_7" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19107.5"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <scenes>
@@ -33,7 +34,7 @@
                     <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="kOg-FB-cx3">
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                         <connections>
                             <outlet property="dataSource" destination="bYG-Kt-eGn" id="UPe-dz-o9s"/>
                             <outlet property="delegate" destination="bYG-Kt-eGn" id="aly-IU-bHB"/>
@@ -62,14 +63,14 @@
             </objects>
             <point key="canvasLocation" x="329" y="-2177"/>
         </scene>
-        <!--Single View Controller-->
+        <!--I18NExample View Controller-->
         <scene sceneID="D5T-qz-8pS">
             <objects>
-                <tableViewController storyboardIdentifier="1" id="m94-yi-d3y" customClass="MJSingleViewController" sceneMemberID="viewController">
+                <tableViewController storyboardIdentifier="1" id="m94-yi-d3y" userLabel="I18NExample View Controller" customClass="I18NExampleViewController" customModule="MJRefreshExample" customModuleProvider="target" sceneMemberID="viewController">
                     <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="cbU-lV-mY4">
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                         <connections>
                             <outlet property="dataSource" destination="m94-yi-d3y" id="tv3-44-MfP"/>
                             <outlet property="delegate" destination="m94-yi-d3y" id="IYQ-uj-ZZ4"/>
@@ -103,10 +104,10 @@
                     <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="IS9-AM-1DI">
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                         <prototypes>
                             <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="example" textLabel="veW-N9-syp" detailTextLabel="Iv7-PA-lBt" style="IBUITableViewCellStyleSubtitle" id="xJS-7u-6pG">
-                                <rect key="frame" x="0.0" y="28" width="375" height="44"/>
+                                <rect key="frame" x="0.0" y="50" width="375" height="44"/>
                                 <autoresizingMask key="autoresizingMask"/>
                                 <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="xJS-7u-6pG" id="rMg-IH-g5u">
                                     <rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
@@ -150,7 +151,7 @@
                     <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="sAi-eu-GBh">
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                         <connections>
                             <outlet property="dataSource" destination="cR6-pa-HhO" id="HsB-FV-ckP"/>
                             <outlet property="delegate" destination="cR6-pa-HhO" id="S8o-67-plS"/>
@@ -180,4 +181,9 @@
             <point key="canvasLocation" x="348" y="-1474"/>
         </scene>
     </scenes>
-</document>
+    <resources>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 1 - 1
MJRefreshExample/Classes/First/MJTempViewController.m

@@ -42,7 +42,7 @@ - (void)viewDidLoad {
     
     self.view.backgroundColor = [UIColor clearColor];
     
-    UISegmentedControl *control = [[UISegmentedControl alloc] initWithItems:@[@"示例1", @"示例2", @"示例3"]];
+    UISegmentedControl *control = [[UISegmentedControl alloc] initWithItems:@[@"示例1", @"i18n", @"示例3"]];
     control.tintColor = [UIColor orangeColor];
     control.frame = self.view.bounds;
     control.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

BIN
MJRefreshExample/Classes/i18n/CustomLanguages.bundle/en.lproj/Localizable.strings


BIN
MJRefreshExample/Classes/i18n/CustomLanguages.bundle/ko.lproj/Localizable.strings


BIN
MJRefreshExample/Classes/i18n/CustomLanguages.bundle/ru.lproj/Localizable.strings


BIN
MJRefreshExample/Classes/i18n/CustomLanguages.bundle/uk.lproj/Localizable.strings


BIN
MJRefreshExample/Classes/i18n/CustomLanguages.bundle/zh-Hans.lproj/Localizable.strings


BIN
MJRefreshExample/Classes/i18n/CustomLanguages.bundle/zh-Hant.lproj/Localizable.strings


+ 90 - 0
MJRefreshExample/Classes/i18n/I18NExampleViewController.swift

@@ -0,0 +1,90 @@
+//
+//  I18NExampleViewController.swift
+//  MJRefreshExample
+//
+//  Created by Frank on 2021/7/5.
+//  Copyright © 2021 小码哥. All rights reserved.
+//
+import UIKit
+
+class I18NExampleViewController: UITableViewController {
+    var languages = [
+        "zh-Hans", "zh-Hant", "en", "ru", "ko", "uk"
+    ]
+    var customBundles = [
+        "zh-Hans", "zh-Hant", "en", "ru", "ko", "uk"
+    ]
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
+
+        MJRefreshNormalHeader {
+            DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
+                guard let self = self else { return }
+                self.tableView.mj_header?.endRefreshing()
+            }
+        }.autoChangeTransparency(true).link(to: tableView)
+
+        MJRefreshAutoNormalFooter {
+            DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
+                guard let self = self else { return }
+                self.tableView.mj_footer?.endRefreshing()
+            }
+        }.link(to: tableView)
+    }
+
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        return 3
+    }
+
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return languages.count
+    }
+
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
+
+        cell.textLabel?.text = languages[indexPath.row]
+        return cell
+    }
+
+    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+        switch section {
+        case 0:
+            return "MJRefresh 系统包或 MainBundle 中默认语言包"
+        case 1:
+            return "🍬自定义语言包文件(MJRefresh_i18n.strings)"
+        case 2:
+            return "♿️自定义语言包 bundle(CustomLanguages.bundle)"
+        default:
+            return nil
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        switch indexPath.section {
+        case 1:
+            MJRefreshConfig.default.i18nFilename = "MJRefresh_i18n"
+            MJRefreshConfig.default.languageCode = nil
+            MJRefreshConfig.default.i18nBundle = nil
+        case 2:
+            MJRefreshConfig.default.i18nFilename = nil
+            MJRefreshConfig.default.languageCode = nil
+            MJRefreshConfig.default.i18nBundle = Bundle(path: Bundle.main.path(forResource: "CustomLanguages", ofType: "bundle")!)
+        default:
+            MJRefreshConfig.default.i18nFilename = nil
+            MJRefreshConfig.default.languageCode = nil
+            MJRefreshConfig.default.i18nBundle = nil
+        }
+
+        MJRefreshConfig.default.languageCode = languages[indexPath.row]
+
+        let alertC = UIAlertController(title: "⚠️", message: "language changed to '\(languages[indexPath.row])'", preferredStyle: .alert)
+
+        alertC.addAction(UIAlertAction(title: "🎉", style: .destructive))
+
+        present(alertC, animated: true)
+    }
+}

BIN
MJRefreshExample/Classes/i18n/en.lproj/MJRefresh_i18n.strings


BIN
MJRefreshExample/Classes/i18n/ko.lproj/MJRefresh_i18n.strings


BIN
MJRefreshExample/Classes/i18n/ru.lproj/MJRefresh_i18n.strings


BIN
MJRefreshExample/Classes/i18n/uk.lproj/MJRefresh_i18n.strings


BIN
MJRefreshExample/Classes/i18n/zh-Hans.lproj/MJRefresh_i18n.strings


BIN
MJRefreshExample/Classes/i18n/zh-Hant.lproj/MJRefresh_i18n.strings


+ 53 - 5
README.md

@@ -9,7 +9,8 @@
 ## Contents
 
 - New Features
-  - [Swift Chaining Grammar Supported](#swift_chaining_grammar_supported)
+    - [Dynamic i18n Switching](#dynamic_i18n_switching)
+    - [Swift Chaining Grammar Supported](#swift_chaining_grammar_supported)
 
 * Getting Started
     * [Features【Support what kinds of controls to refresh】](#Support_what_kinds_of_controls_to_refresh)
@@ -46,9 +47,55 @@
 * [Hope](#Hope)
 
 ## New Features
+### <a id="dynamic_i18n_switching"></a>Dynamic i18n Switching
+
+Now `MJRefresh components` will be rerendered automatically with `MJRefreshConfig.default.language` setting.
+
+#### Example
+
+Go `i18n` folder and see lots of cases. Simulator example is behind `i18n tab` in right-top corner.
+
+#### Setting language
+
+```swift
+MJRefreshConfig.default.language = "zh-hans"
+```
+
+#### Setting i18n file name
+
+```swift
+MJRefreshConfig.default.i18nFilename = "i18n File Name(not include type<.strings>)"
+```
+
+#### Setting i18n language bundle
+
+```swift
+MJRefreshConfig.default.i18nBundle = "i18n Bundle"
+```
+
+#### Adopting the feature in your DIY component
+
+1. Just override `i18nDidChange` function and reset texts.
+
+```swift
+// must use this localization methods
+Bundle.mj_localizedString(forKey: "")
+// or
+Bundle.mj_localizedString(forKey: "", value:"")
+
+override func i18nDidChange() {
+    // Reset texts function
+    setupTexts()
+    // Make sure to call super after resetting texts. It will call placeSubViews for applying new layout.
+    super.i18nDidChange()
+}
+```
+
+2. Receiving `MJRefreshDidChangeLanguageNotification` notification.
+
 ### <a id="swift_chaining_grammar_supported"></a>Swift Chaining Grammar Supported
 
-  ```swift
+```swift
   // Example as MJRefreshNormalHeader
   func addRefreshHeader() {
       MJRefreshNormalHeader { [weak self] in
@@ -56,9 +103,10 @@
       }.autoChangeTransparency(true)
       .link(to: tableView)
   }
-  ```
+```
 
 ## <a id="Support_what_kinds_of_controls_to_refresh"></a>Support what kinds of controls to refresh
+
 * `UIScrollView`、`UITableView`、`UICollectionView`、`WKWebView`
 
 ## <a id="How_to_use_MJRefresh"></a>How to use MJRefresh
@@ -392,7 +440,7 @@ self.webView.scrollView.mj_header = [MJRefreshNormalHeader headerWithRefreshingB
 
 ## Remind
 * ARC
-* iOS>=8.0
+* iOS>=9.0
 * iPhone \ iPad screen anyway
 
 ## 寻求志同道合的小伙伴
@@ -400,4 +448,4 @@ self.webView.scrollView.mj_header = [MJRefreshNormalHeader headerWithRefreshingB
 - 因本人工作忙,没有太多时间去维护MJRefresh,在此向广大框架使用者说声:非常抱歉!😞
 - 现寻求志同道合的小伙伴一起维护此框架,有兴趣的小伙伴可以[发邮件](mailto:richermj123go@vip.qq.com)给我,非常感谢😊
 - 如果一切OK,我将开放框架维护权限(github、pod等)
-- 目前已经找到3位小伙伴(^-^)V
+- 目前已经找到3位小伙伴(^-^)V