多语言开发
This commit is contained in:
parent
e8b2c4668f
commit
fa0db110b6
78
MoviaBox.xcodeproj/xcshareddata/xcschemes/MoviaBox.xcscheme
Normal file
78
MoviaBox.xcodeproj/xcshareddata/xcschemes/MoviaBox.xcscheme
Normal file
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1DBC40582DA4EDFC0093FCB0"
|
||||
BuildableName = "MoviaBox.app"
|
||||
BlueprintName = "MoviaBox"
|
||||
ReferencedContainer = "container:MoviaBox.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1DBC40582DA4EDFC0093FCB0"
|
||||
BuildableName = "MoviaBox.app"
|
||||
BlueprintName = "MoviaBox"
|
||||
ReferencedContainer = "container:MoviaBox.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1DBC40582DA4EDFC0093FCB0"
|
||||
BuildableName = "MoviaBox.app"
|
||||
BlueprintName = "MoviaBox"
|
||||
ReferencedContainer = "container:MoviaBox.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -29,7 +29,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
// SPLoginManager.manager.requestVisitorLogin(completer: nil)
|
||||
SPLoginManager.manager.updateUserInfo(completer: nil)
|
||||
|
||||
///更新本地多语言
|
||||
SPLocalizedManager.shared.updateLocalizedData(completer: nil)
|
||||
///注册消息通知
|
||||
registerAPNS()
|
||||
|
||||
|
@ -15,6 +15,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
guard let windowScene = (scene as? UIWindowScene) else { return }
|
||||
///监听语言切换
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(localizedDidChange), name: SPLocalizedManager.localizedDidChange, object: nil)
|
||||
|
||||
let tabBarController = SPTabBarController()
|
||||
SPAPPTool.mainTabBarController = tabBarController
|
||||
|
||||
@ -93,4 +96,13 @@ extension SceneDelegate {
|
||||
|
||||
handleOpenAppMessage(webpageURL: nil)
|
||||
}
|
||||
|
||||
///语言切换
|
||||
@objc private func localizedDidChange() {
|
||||
let tabBarController = SPTabBarController()
|
||||
SPAPPTool.mainTabBarController = tabBarController
|
||||
|
||||
window?.rootViewController = tabBarController
|
||||
window?.makeKeyAndVisible()
|
||||
}
|
||||
}
|
||||
|
@ -400,5 +400,9 @@ extension UIColor {
|
||||
static func colorA8B8C3(alpha: CGFloat = 1) -> UIColor {
|
||||
return color(hex: 0xA8B8C3, alpha: alpha)
|
||||
}
|
||||
|
||||
static func color321F1F(alpha: CGFloat = 1) -> UIColor {
|
||||
return color(hex: 0x321F1F, alpha: alpha)
|
||||
}
|
||||
}
|
||||
|
||||
|
38
MoviaBox/Base/Networking/API/SPSettingAPI.swift
Normal file
38
MoviaBox/Base/Networking/API/SPSettingAPI.swift
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// SPSettingAPI.swift
|
||||
// MoviaBox
|
||||
//
|
||||
// Created by 佳尔 on 2025/5/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPSettingAPI: NSObject {
|
||||
|
||||
///获取语言列表
|
||||
static func requestLanguageList(completer: ((_ list: [SPLanguageModel]?) -> Void)?) {
|
||||
|
||||
var param = SPNetworkParameters(path: "/languges")
|
||||
param.method = .get
|
||||
|
||||
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPListModel<SPLanguageModel>>) in
|
||||
completer?(response.data?.list)
|
||||
}
|
||||
}
|
||||
|
||||
///获取语言数据
|
||||
static func requestLocalizedData(key: String, completer: ((_ model: SPLocalizedModel?) -> Void)?) {
|
||||
var param = SPNetworkParameters(path: "/translates")
|
||||
param.method = .get
|
||||
param.parameters = [
|
||||
"lang_key" : key
|
||||
]
|
||||
|
||||
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPLocalizedModel>) in
|
||||
completer?(response.data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
125
MoviaBox/Class/Mine/Controller/SPLanguageViewController.swift
Normal file
125
MoviaBox/Class/Mine/Controller/SPLanguageViewController.swift
Normal file
@ -0,0 +1,125 @@
|
||||
//
|
||||
// SPLanguageViewController.swift
|
||||
// MoviaBox
|
||||
//
|
||||
// Created by 佳尔 on 2025/5/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPLanguageViewController: SPViewController {
|
||||
|
||||
|
||||
private lazy var dataArr: [SPLanguageModel] = []
|
||||
|
||||
private lazy var currentLocalizedKey = SPLocalizedManager.shared.currentLocalizedKey
|
||||
|
||||
private lazy var tableView: SPTableView = {
|
||||
let tableView = SPTableView(frame: .zero, style: .insetGrouped)
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
tableView.sectionFooterHeight = 0
|
||||
tableView.tableHeaderView = UIView()
|
||||
tableView.rowHeight = 60
|
||||
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: kSPTabbarSafeBottomMargin + 10, right: 0)
|
||||
SPLanguageCell.registerCell(tableView: tableView)
|
||||
return tableView
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.edgesForExtendedLayout = .top
|
||||
self.title = "Language".localized
|
||||
|
||||
requestDataList()
|
||||
|
||||
_setupUI()
|
||||
|
||||
// SPSettingAPI.requestLocalizedData()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
self.navigationController?.setNavigationBarHidden(false, animated: true)
|
||||
setNavigationNormalStyle(backgroundColor: .clear, isTranslucent: true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPLanguageViewController {
|
||||
|
||||
private func _setupUI() {
|
||||
view.addSubview(tableView)
|
||||
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
make.top.equalToSuperview().offset(kSPNavBarHeight)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- UITableViewDelegate & UITableViewDataSource --------------
|
||||
extension SPLanguageViewController: UITableViewDelegate, UITableViewDataSource {
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let model = dataArr[indexPath.section]
|
||||
let cell = SPLanguageCell.dequeueReusableCell(tableView: tableView, indexPath: indexPath)
|
||||
cell.model = model
|
||||
cell.sp_isSelected = model.lang_key == currentLocalizedKey
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return self.dataArr.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
return 12
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
||||
return 0
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let model = dataArr[indexPath.section]
|
||||
if model.lang_key == currentLocalizedKey { return }
|
||||
|
||||
if let key = model.lang_key {
|
||||
SPHUD.show()
|
||||
SPLocalizedManager.shared.updateLocalizedData(key: key) { (finish) in
|
||||
if finish {
|
||||
SPLocalizedManager.shared.currentLocalizedKey = key
|
||||
NotificationCenter.default.post(name: SPLocalizedManager.localizedDidChange, object: nil)
|
||||
}
|
||||
SPHUD.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPLanguageViewController {
|
||||
|
||||
private func requestDataList() {
|
||||
SPSettingAPI.requestLanguageList { [weak self] list in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let list = list {
|
||||
self.dataArr = list
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -11,7 +11,7 @@ class SPMineViewController: SPViewController {
|
||||
|
||||
private lazy var dataArr: [SPMineItem] = {
|
||||
let arr = [
|
||||
// SPMineItem(type: .language, iconImage: UIImage(named: "language_icon_01"), title: "Language".localized),
|
||||
SPMineItem(type: .language, iconImage: UIImage(named: "language_icon_01"), title: "Language".localized),
|
||||
SPMineItem(type: .feedBack, iconImage: UIImage(named: "feed_back_icon_01"), title: "FeedBack".localized),
|
||||
SPMineItem(type: .privacyPolicy, iconImage: UIImage(named: "privacy_policy_icon_01"), title: "Privacy Policy".localized),
|
||||
SPMineItem(type: .userAgreement, iconImage: UIImage(named: "user_agreement_icon_01"), title: "User Agreement".localized),
|
||||
@ -138,6 +138,10 @@ extension SPMineViewController: UITableViewDelegate, UITableViewDataSource {
|
||||
let vc = SPSettingsViewController()
|
||||
self.navigationController?.pushViewController(vc, animated: true)
|
||||
|
||||
case .language:
|
||||
let vc = SPLanguageViewController()
|
||||
self.navigationController?.pushViewController(vc, animated: true)
|
||||
|
||||
|
||||
default:
|
||||
break
|
||||
|
22
MoviaBox/Class/Mine/Model/SPLanguageModel.swift
Normal file
22
MoviaBox/Class/Mine/Model/SPLanguageModel.swift
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// SPLanguageModel.swift
|
||||
// MoviaBox
|
||||
//
|
||||
// Created by 佳尔 on 2025/5/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SmartCodable
|
||||
|
||||
class SPLanguageModel: SPModel, SmartCodable {
|
||||
|
||||
var cn_name: String?
|
||||
var show_name: String?
|
||||
var id: String?
|
||||
var is_up_to_list: String?
|
||||
var lang_key: String?
|
||||
// var description: String?
|
||||
///1:当前语言
|
||||
var is_default: Int?
|
||||
|
||||
}
|
91
MoviaBox/Class/Mine/View/SPLanguageCell.swift
Normal file
91
MoviaBox/Class/Mine/View/SPLanguageCell.swift
Normal file
@ -0,0 +1,91 @@
|
||||
//
|
||||
// SPLanguageCell.swift
|
||||
// MoviaBox
|
||||
//
|
||||
// Created by 佳尔 on 2025/5/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPLanguageCell: SPTableViewCell {
|
||||
|
||||
var model: SPLanguageModel? {
|
||||
didSet {
|
||||
titleLabel.text = model?.show_name
|
||||
|
||||
if model?.is_default == 1 {
|
||||
self.contentView.layer.borderColor = UIColor.colorFF3232().cgColor
|
||||
self.selectedButton.isSelected = true
|
||||
} else {
|
||||
self.contentView.layer.borderColor = UIColor.clear.cgColor
|
||||
self.selectedButton.isSelected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sp_isSelected = false {
|
||||
didSet {
|
||||
if sp_isSelected {
|
||||
self.contentView.layer.borderColor = UIColor.colorFF3232().cgColor
|
||||
self.selectedButton.isSelected = true
|
||||
} else {
|
||||
self.contentView.layer.borderColor = UIColor.clear.cgColor
|
||||
self.selectedButton.isSelected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontMedium(ofSize: 14)
|
||||
label.textColor = .colorFFFFFF()
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var selectedButton: UIButton = {
|
||||
let button = UIButton(type: .custom)
|
||||
button.isUserInteractionEnabled = false
|
||||
button.setImage(UIImage(named: "check_icon_01"), for: .normal)
|
||||
button.setImage(UIImage(named: "check_icon_01_selected"), for: .selected)
|
||||
return button
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
self.contentView.backgroundColor = .color321F1F()
|
||||
self.contentView.layer.borderWidth = 1
|
||||
self.contentView.layer.cornerRadius = 10
|
||||
self.contentView.layer.masksToBounds = true
|
||||
|
||||
|
||||
_setupUI()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPLanguageCell {
|
||||
|
||||
private func _setupUI() {
|
||||
contentView.addSubview(titleLabel)
|
||||
contentView.addSubview(selectedButton)
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalToSuperview().offset(16)
|
||||
}
|
||||
|
||||
selectedButton.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.right.equalToSuperview().offset(-16)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -9,38 +9,55 @@ import UIKit
|
||||
|
||||
class SPLocalizedManager: NSObject {
|
||||
static let shared = SPLocalizedManager()
|
||||
private let LocalizedUserDefaultsKey = "SPLocalizedManager.LocalizedUserDefaultsKey"
|
||||
private let LocalizedDataUserDefaultsKey = "SPLocalizedManager.LocalizedDataUserDefaultsKey"
|
||||
|
||||
private let userDefaultsKey = "AppLocalized"
|
||||
|
||||
///语言列表
|
||||
var languageList: [SPLanguageModel]?
|
||||
|
||||
///多语言数据
|
||||
private lazy var localizedData: [String : String]? = UserDefaults.standard.object(forKey: LocalizedDataUserDefaultsKey) as? [String : String]
|
||||
{
|
||||
didSet {
|
||||
UserDefaults.standard.set(localizedData, forKey: LocalizedDataUserDefaultsKey)
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前语言代码(如果用户未手动设置,则返回系统语言)
|
||||
var currentLocalizedKey: String {
|
||||
get {
|
||||
// return UserDefaults.standard.string(forKey: userDefaultsKey) ?? Locale.preferredLanguages.first ?? "en"
|
||||
return "en"
|
||||
return UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) ?? "en"
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: userDefaultsKey)
|
||||
UserDefaults.standard.set(newValue, forKey: LocalizedUserDefaultsKey)
|
||||
UserDefaults.standard.synchronize()
|
||||
NotificationCenter.default.post(name: SPLocalizedManager.localizedDidChange, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否跟随系统
|
||||
var isFollowingSystem: Bool {
|
||||
return UserDefaults.standard.string(forKey: userDefaultsKey) == nil
|
||||
return UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) == nil
|
||||
}
|
||||
|
||||
// 还原为系统默认语言
|
||||
func resetToSystemLanguage() {
|
||||
UserDefaults.standard.removeObject(forKey: userDefaultsKey)
|
||||
UserDefaults.standard.removeObject(forKey: LocalizedUserDefaultsKey)
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
|
||||
// 获取本地化字符串
|
||||
func localizedString(forKey key: String, tableName: String? = nil) -> String {
|
||||
if let selectedLanguage = UserDefaults.standard.string(forKey: userDefaultsKey),
|
||||
if let localizedData = localizedData,
|
||||
let text = localizedData[key] {
|
||||
return text
|
||||
|
||||
} else if let selectedLanguage = UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey),
|
||||
let bundlePath = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"),
|
||||
let bundle = Bundle(path: bundlePath) {
|
||||
|
||||
return bundle.localizedString(forKey: key, value: nil, table: tableName)
|
||||
} else {
|
||||
return NSLocalizedString(key, tableName: tableName, bundle: .main, value: "", comment: "")
|
||||
@ -48,6 +65,31 @@ class SPLocalizedManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
extension SPLocalizedManager {
|
||||
///获取本地化数据
|
||||
func updateLocalizedData(key: String = SPLocalizedManager.shared.currentLocalizedKey, completer: ((_ finish: Bool) -> Void)?) {
|
||||
SPSettingAPI.requestLocalizedData(key: key) { [weak self] model in
|
||||
guard let self = self else { return }
|
||||
guard let model = model else {
|
||||
completer?(false)
|
||||
return
|
||||
}
|
||||
if let languageList = model.languages, languageList.count > 0 {
|
||||
self.languageList = languageList
|
||||
}
|
||||
|
||||
if let localizedData = model.translates {
|
||||
self.localizedData = localizedData
|
||||
completer?(true)
|
||||
} else {
|
||||
completer?(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
extension SPLocalizedManager {
|
||||
|
||||
static let localizedDidChange = Notification.Name(rawValue: "SPLocalizedManager.localizedDidChange")
|
||||
|
18
MoviaBox/Libs/SPLocalizedManager/SPLocalizedModel.swift
Normal file
18
MoviaBox/Libs/SPLocalizedManager/SPLocalizedModel.swift
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// SPLocalizedModel.swift
|
||||
// MoviaBox
|
||||
//
|
||||
// Created by 佳尔 on 2025/5/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SmartCodable
|
||||
|
||||
class SPLocalizedModel: SPModel, SmartCodable {
|
||||
|
||||
///翻译数据
|
||||
var translates: [String : String]?
|
||||
|
||||
var languages: [SPLanguageModel]?
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user