多语言开发

This commit is contained in:
zeng 2025-05-10 11:23:25 +08:00
parent e8b2c4668f
commit fa0db110b6
11 changed files with 444 additions and 9 deletions

View 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>

View File

@ -29,7 +29,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// SPLoginManager.manager.requestVisitorLogin(completer: nil) // SPLoginManager.manager.requestVisitorLogin(completer: nil)
SPLoginManager.manager.updateUserInfo(completer: nil) SPLoginManager.manager.updateUserInfo(completer: nil)
///
SPLocalizedManager.shared.updateLocalizedData(completer: nil)
/// ///
registerAPNS() registerAPNS()

View File

@ -15,6 +15,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return } guard let windowScene = (scene as? UIWindowScene) else { return }
///
NotificationCenter.default.addObserver(self, selector: #selector(localizedDidChange), name: SPLocalizedManager.localizedDidChange, object: nil)
let tabBarController = SPTabBarController() let tabBarController = SPTabBarController()
SPAPPTool.mainTabBarController = tabBarController SPAPPTool.mainTabBarController = tabBarController
@ -93,4 +96,13 @@ extension SceneDelegate {
handleOpenAppMessage(webpageURL: nil) handleOpenAppMessage(webpageURL: nil)
} }
///
@objc private func localizedDidChange() {
let tabBarController = SPTabBarController()
SPAPPTool.mainTabBarController = tabBarController
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
}
} }

View File

@ -400,5 +400,9 @@ extension UIColor {
static func colorA8B8C3(alpha: CGFloat = 1) -> UIColor { static func colorA8B8C3(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0xA8B8C3, alpha: alpha) return color(hex: 0xA8B8C3, alpha: alpha)
} }
static func color321F1F(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0x321F1F, alpha: alpha)
}
} }

View 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)
}
}
}

View 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()
}
}
}
}

View File

@ -11,7 +11,7 @@ class SPMineViewController: SPViewController {
private lazy var dataArr: [SPMineItem] = { private lazy var dataArr: [SPMineItem] = {
let arr = [ 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: .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: .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), 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() let vc = SPSettingsViewController()
self.navigationController?.pushViewController(vc, animated: true) self.navigationController?.pushViewController(vc, animated: true)
case .language:
let vc = SPLanguageViewController()
self.navigationController?.pushViewController(vc, animated: true)
default: default:
break break

View 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?
}

View 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)
}
}
}

View File

@ -9,38 +9,55 @@ import UIKit
class SPLocalizedManager: NSObject { class SPLocalizedManager: NSObject {
static let shared = SPLocalizedManager() 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 { var currentLocalizedKey: String {
get { get {
// return UserDefaults.standard.string(forKey: userDefaultsKey) ?? Locale.preferredLanguages.first ?? "en" // return UserDefaults.standard.string(forKey: userDefaultsKey) ?? Locale.preferredLanguages.first ?? "en"
return "en" return UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) ?? "en"
} }
set { set {
UserDefaults.standard.set(newValue, forKey: userDefaultsKey) UserDefaults.standard.set(newValue, forKey: LocalizedUserDefaultsKey)
UserDefaults.standard.synchronize() UserDefaults.standard.synchronize()
NotificationCenter.default.post(name: SPLocalizedManager.localizedDidChange, object: nil)
} }
} }
// //
var isFollowingSystem: Bool { var isFollowingSystem: Bool {
return UserDefaults.standard.string(forKey: userDefaultsKey) == nil return UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) == nil
} }
// //
func resetToSystemLanguage() { func resetToSystemLanguage() {
UserDefaults.standard.removeObject(forKey: userDefaultsKey) UserDefaults.standard.removeObject(forKey: LocalizedUserDefaultsKey)
UserDefaults.standard.synchronize() UserDefaults.standard.synchronize()
} }
// //
func localizedString(forKey key: String, tableName: String? = nil) -> String { 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 bundlePath = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"),
let bundle = Bundle(path: bundlePath) { let bundle = Bundle(path: bundlePath) {
return bundle.localizedString(forKey: key, value: nil, table: tableName) return bundle.localizedString(forKey: key, value: nil, table: tableName)
} else { } else {
return NSLocalizedString(key, tableName: tableName, bundle: .main, value: "", comment: "") 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 { extension SPLocalizedManager {
static let localizedDidChange = Notification.Name(rawValue: "SPLocalizedManager.localizedDidChange") static let localizedDidChange = Notification.Name(rawValue: "SPLocalizedManager.localizedDidChange")

View 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]?
}