feat: 完善 HomeView 国际化支持
- 新增 LanguageManager 支持应用内语言切换 - 新增 Localizable.xcstrings 包含 78 个翻译键 - 修复 HomeView 硬编码文本,改用 String(localized:) - 支持简体中文、繁体中文、英文三种语言 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
88
to-live-photo/to-live-photo/LanguageManager.swift
Normal file
88
to-live-photo/to-live-photo/LanguageManager.swift
Normal file
@@ -0,0 +1,88 @@
|
||||
import SwiftUI
|
||||
|
||||
/// 语言管理器:支持应用内动态切换语言
|
||||
@Observable
|
||||
final class LanguageManager {
|
||||
|
||||
/// 支持的语言
|
||||
enum Language: String, CaseIterable, Identifiable {
|
||||
case system = "system"
|
||||
case zhHans = "zh-Hans"
|
||||
case zhHant = "zh-Hant"
|
||||
case en = "en"
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .system: return "跟随系统"
|
||||
case .zhHans: return "简体中文"
|
||||
case .zhHant: return "繁體中文"
|
||||
case .en: return "English"
|
||||
}
|
||||
}
|
||||
|
||||
var locale: Locale? {
|
||||
switch self {
|
||||
case .system: return nil
|
||||
case .zhHans: return Locale(identifier: "zh-Hans")
|
||||
case .zhHant: return Locale(identifier: "zh-Hant")
|
||||
case .en: return Locale(identifier: "en")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 单例
|
||||
static let shared = LanguageManager()
|
||||
|
||||
/// 当前选择的语言
|
||||
var current: Language {
|
||||
didSet {
|
||||
UserDefaults.standard.set(current.rawValue, forKey: "app_language")
|
||||
applyLanguage()
|
||||
}
|
||||
}
|
||||
|
||||
/// 可用语言列表
|
||||
var availableLanguages: [Language] {
|
||||
Language.allCases
|
||||
}
|
||||
|
||||
private init() {
|
||||
let savedLanguage = UserDefaults.standard.string(forKey: "app_language") ?? "system"
|
||||
self.current = Language(rawValue: savedLanguage) ?? .system
|
||||
applyLanguage()
|
||||
}
|
||||
|
||||
/// 应用语言设置
|
||||
private func applyLanguage() {
|
||||
if current == .system {
|
||||
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
|
||||
} else {
|
||||
UserDefaults.standard.set([current.rawValue], forKey: "AppleLanguages")
|
||||
}
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
|
||||
/// 获取本地化字符串
|
||||
func localizedString(_ key: String) -> String {
|
||||
if current == .system {
|
||||
return String(localized: String.LocalizationValue(key))
|
||||
}
|
||||
|
||||
guard let path = Bundle.main.path(forResource: current.rawValue, ofType: "lproj"),
|
||||
let bundle = Bundle(path: path) else {
|
||||
return String(localized: String.LocalizationValue(key))
|
||||
}
|
||||
|
||||
return NSLocalizedString(key, bundle: bundle, comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 便捷扩展
|
||||
extension String {
|
||||
/// 本地化字符串
|
||||
var localized: String {
|
||||
LanguageManager.shared.localizedString(self)
|
||||
}
|
||||
}
|
||||
1880
to-live-photo/to-live-photo/Localizable.xcstrings
Normal file
1880
to-live-photo/to-live-photo/Localizable.xcstrings
Normal file
File diff suppressed because it is too large
Load Diff
@@ -71,11 +71,11 @@ struct HomeView: View {
|
||||
}
|
||||
|
||||
VStack(spacing: DesignTokens.Spacing.sm) {
|
||||
Text("Live Photo 制作")
|
||||
Text(String(localized: "home.title"))
|
||||
.font(.system(size: DesignTokens.FontSize.xxl, weight: .bold))
|
||||
.foregroundColor(.textPrimary)
|
||||
|
||||
Text("选择视频,一键转换为动态壁纸")
|
||||
Text(String(localized: "home.subtitle"))
|
||||
.font(.system(size: DesignTokens.FontSize.base))
|
||||
.foregroundColor(.textSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
@@ -90,7 +90,7 @@ struct HomeView: View {
|
||||
HStack(spacing: DesignTokens.Spacing.sm) {
|
||||
Image(systemName: "video.badge.plus")
|
||||
.font(.system(size: 18, weight: .semibold))
|
||||
Text("选择视频")
|
||||
Text(String(localized: "home.selectVideo"))
|
||||
.font(.system(size: DesignTokens.FontSize.base, weight: .semibold))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
@@ -111,7 +111,7 @@ struct HomeView: View {
|
||||
HStack(spacing: DesignTokens.Spacing.sm) {
|
||||
ProgressView()
|
||||
.tint(.accentPurple)
|
||||
Text("正在加载视频...")
|
||||
Text(String(localized: "home.loading"))
|
||||
.font(.system(size: DesignTokens.FontSize.sm))
|
||||
.foregroundColor(.textSecondary)
|
||||
}
|
||||
@@ -149,7 +149,7 @@ struct HomeView: View {
|
||||
.foregroundColor(.accentOrange)
|
||||
}
|
||||
|
||||
Text("快速上手")
|
||||
Text(String(localized: "home.quickStart"))
|
||||
.font(.system(size: DesignTokens.FontSize.lg, weight: .semibold))
|
||||
.foregroundColor(.textPrimary)
|
||||
|
||||
@@ -157,15 +157,15 @@ struct HomeView: View {
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: DesignTokens.Spacing.md) {
|
||||
QuickStartStep(number: 1, text: "点击上方「选择视频」导入素材", color: .accentPurple)
|
||||
QuickStartStep(number: 2, text: "调整比例和时长,选择封面帧", color: .accentCyan)
|
||||
QuickStartStep(number: 3, text: "开启 AI 增强提升画质(可选)", color: .accentPink)
|
||||
QuickStartStep(number: 4, text: "生成后按引导设置为壁纸", color: .accentGreen)
|
||||
QuickStartStep(number: 1, text: String(localized: "home.quickStart.step1"), color: .accentPurple)
|
||||
QuickStartStep(number: 2, text: String(localized: "home.quickStart.step2"), color: .accentCyan)
|
||||
QuickStartStep(number: 3, text: String(localized: "home.quickStart.step3"), color: .accentPink)
|
||||
QuickStartStep(number: 4, text: String(localized: "home.quickStart.step4"), color: .accentGreen)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("完成后的作品会显示在这里")
|
||||
Text(String(localized: "home.emptyHint"))
|
||||
.font(.system(size: DesignTokens.FontSize.xs))
|
||||
.foregroundColor(.textMuted)
|
||||
Spacer()
|
||||
@@ -190,13 +190,13 @@ struct HomeView: View {
|
||||
.foregroundColor(.accentCyan)
|
||||
}
|
||||
|
||||
Text("最近作品")
|
||||
Text(String(localized: "home.recentWorks"))
|
||||
.font(.system(size: DesignTokens.FontSize.lg, weight: .semibold))
|
||||
.foregroundColor(.textPrimary)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("\(recentWorks.recentWorks.count) 个")
|
||||
Text(String(localized: "home.worksCount \(recentWorks.recentWorks.count)"))
|
||||
.font(.system(size: DesignTokens.FontSize.sm))
|
||||
.foregroundColor(.textMuted)
|
||||
}
|
||||
@@ -224,7 +224,7 @@ struct HomeView: View {
|
||||
|
||||
do {
|
||||
guard let movie = try await item.loadTransferable(type: VideoTransferable.self) else {
|
||||
errorMessage = "无法加载视频"
|
||||
errorMessage = String(localized: "home.loadFailed")
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user