Featured image of post Swift利用协议扩展优化项目多语言enum

Swift利用协议扩展优化项目多语言enum

1 启发

原本在看 Codable 相关的 Swift:JSON解析(上) ,看该文章右边有个相关推荐 Swift:通过Protocol封装统和入参 ,一口气把它给看完了,我日常开发的编码思路跟作者的如出一辙,但有一点儿我也是临时觉得有些不妥:如下图所示,toString 每个枚举里面都要写,其实完全没必要,通过协议扩展可以轻松解决! 改进 ToStringProtocol 的扩展,如下:

1
2
3
extension ToStringProtocol where Self: RawRepresentable, Self.RawValue == String {
    var toString: String { rawValue }
}

ClickEvent 中的枚举,无论是 AControllerBController,还是后续新增的,都只需要遵循 ToStringProtocol 即可拥有 toString 属性!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//protocol ToStringProtocol {
//    var toString: String { get }
//}

enum ClickEvent {
    
    /// 让AController遵守ToStringProtocol
    enum AController: String, ToStringProtocol {
        case oneButtonClick
        case twoButtonClick
    }
    
    /// 让BController遵守ToStringProtocol
    enum BController: String, ToStringProtocol {
        case oneButtonClick
        case twoButtonClick
    }
    
}

2 项目实战(优化)

2.1 未优化时

  • LAccountSettingsLAccountSettingsAction 中存在共同(可抽离)的代码:
1
2
3
    func language() -> String {
        rawValue.language()
    }

  • language() 的实现竟然是 Stringextension
1
2
3
4
5
extension String {
    func language(table: String? = nil) -> String {
        Bundle.main.localizedString(forKey: self, value: nil, table: table)
    }
}

这样的坏处很显然:所有 String 类型都可以调用 language 方法!

  • case 名字太长   

2.2 优化

① 先简单介绍下项目中的 .strings :

  • Localizable.strings : 系统默认;
  • InfoPlist.strings : 系统权限配置,系统会自动读取;
  • Onboarding.strings : 自定义,Onboarding 业务多语言;
  • Tool.strings : 自定义,Tool (工具)业务多语言

② 需要用到的 APIlocalizedString(forKey:value:table:)

1
2
    /* Methods for retrieving localized strings. */
    open func localizedString(forKey key: String, value: String?, table tableName: String?) -> String
  • key: 要查找的字符串键。
  • value: 如果在指定的 Localizable.strings 文件中未找到 key,将返回此值。如果为 nil,则返回 key 本身。
  • tableName: Localizable.strings 文件的名称。如果为 nil,默认使用 Localizable.strings

NOTE:当前开发中,只需要 keytableName

③ 设计枚举 Table ,管理 .strings

InfoPlist.strings 是系统读取,所以工程要配置,但是开发不用管!

1
2
3
4
5
6
7
8
9
enum Table: String {
    case `default` = "Localizable"
    case Onboarding
    case Tool
    
    func language(forKey key: String) -> String {
        Bundle.main.localizedString(forKey: key, value: nil, table: rawValue)
    }
}

NOTE:一个宗旨:项目中多语言字符串,全部映射为相应的枚举,防止因字符串出错(如少一个字母、多一个字母等)而不易排查问题!

采用 Table 枚举的好处:

  • 便于管理所有 table ,防止因字符串出错而不易排查;
  • localizedString(forKey:value:table:) 的封装,便于内外部调用 (外部调用,就算不遵循 MultiLanguage 也可以便捷调用)

④ 设计 MultiLanguage 协议,扩展 MultiLanguage 协议

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
protocol MultiLanguage {
    var specialKey: String { get }
    var table: Table { get }
    var language: String { get }
}

extension MultiLanguage where Self: RawRepresentable, Self.RawValue == String {
    var specialKey: String { rawValue }
    var table: Table { .default }
    var language: String {
        return table.language(forKey: specialKey)
    }
}
  • MultiLanguage 扩展采用 where Self: RawRepresentable, Self.RawValue == String,来限制遵循 MultiLanguage 协议的对象为原始值类型为 String 的枚举
  • MultiLanguage 扩展提供了默认实现  
specialKey

Q:为什么 MultiLanguage 协议要设计一个 specialKey 属性?明明上面提到 localizedString(forKey:value:table:) 只需要 keytableName 就可以。

A:在 XX.strings 文件中,多语言的 key 我个人更倾向绑定上业务,如 account_settings_Emailaccount_settings_Nameaccount_settings_action_logoutaccount_settings_action_delete_account (详见上 .strings 处的截图),而不是单一的 EmailNamelogoutdelete_account

印象最深的一个翻译:Next ,项目中有下一次,也有下一步,所以不能单一的 Next (这个真实项目写一写,就能体会其中滋味)。  

⑤ 新增继承协议,处理自定义的 .strings

1
2
3
4
5
6
7
8
9
protocol ToolMultiLanguage: MultiLanguage { }
extension ToolMultiLanguage {
    var table: Table { .Tool }
}

protocol OnboardingMultiLanguage: MultiLanguage { }
extension OnboardingMultiLanguage {
    var table: Table { .Onboarding }
}

如上代码所示,对于 ToolOnboarding,只需要新建继承自 MultiLanguage 的协议,并重写 table 即可!  

⑥ 示例

1、新建空的枚举 LTxt
1
2
/// 文字
enum LTxt {}
2、在 LTxtextension 中新增各种 enum,并使其遵循 MultiLanguage 等协议
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//!!!: Profile - Account Settings
extension LTxt {
    enum AccountSettings: String, MultiLanguage {
        case Email
        case Name
        case Gender
        case Age
        case Height
        
        var specialKey: String {
            "account_settings_" + rawValue
        }
        
        enum Action: String, MultiLanguage {
            case logout
            case delete_account
            
            var specialKey: String {
                "account_settings_action_" + rawValue
            }
        }
    }
}

调用:

1
make.setTitle(LTxt.AccountSettings.Action.logout.language, for: .normal)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//!!!: 饱腹感
extension LTxt {
    enum FeelingFullNess: String, CaseIterable, ToolMultiLanguage {
        case Starving
        case Hungry
        case Neutral
        case Satisfied
        case Full
        
        var specialKey: String {
            "feeling_fullness_" + rawValue
        }
         
        static var all: [String] { Self.allCases.map({ $0.language })}
    }
}

调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func getBtLb(at idx: Int, title: LTxt.FeelingFullNess) -> (idxBt: UIButton, idxLb: UILabel) {
    let bt = UIButton(type: .custom).then { make in
        bgView.addSubview(make)
    }
    
    let lb = UILabel(font: .quicksandMedium(12~), textColor: .C_151515_70).then({ make in
        bgView.addSubview(make)
        make.text = title.language
    })
    return (bt, lb)
}

3 总结(感慨)

Swift 的协议,真的是个好东东,再次感受到编码的愉悦,上一次的愉悦来自 关于Socket推送处理的改进。    

4 附录:统和入参

来自 ChatGPT

统和入参 这个词汇通常出现在软件开发、系统设计或者业务流程的场景中,指的是在进行系统设计或开发时,将多个不同来源或类型的输入参数进行统一和整合的过程。这个过程的目标是简化接口设计、减少复杂性,以及提高代码的可维护性和灵活性。  

背景

在复杂的系统中,往往会有多个接口、服务或者模块之间需要进行数据传递。不同的模块可能需要不同类型的输入参数,而这些参数可能来源于不同的系统或用户输入。在这种情况下,如果每个模块都设计自己的独立参数结构,系统就会变得复杂且难以维护。   

意义

统和入参 的作用就是在这样的场景下,将这些不同的输入参数进行标准化和统一,以一种通用的、标准化的方式传递给系统内部的各个模块。这种方法有助于:

  • 减少冗余:避免每个模块都设计独立的参数结构,减少重复的代码和冗余的逻辑。
  • 提高可维护性:通过统一的参数接口,开发人员可以更容易地理解和维护代码。
  • 增强扩展性:当需要增加新的功能或模块时,可以直接使用已统一的参数接口,不需要对现有的系统做大的改动。  
总结

统和入参 是一种在系统设计中常用的策略,通过将多个不同来源的输入参数进行统一和整合,可以简化系统的复杂性,提高系统的可维护性和扩展性。这个概念特别适用于大型系统或者需要处理多种输入的场景。

Built with Hugo
主题 StackJimmy 设计