Instruction file imported from Xiaobonor/Everly-iOS (
.cursor/rules/localization-themes.mdc). Copyright stays with the author.
Everly 本地化與主題系統
多語言支持
Everly 支持多種語言,使用 iOS 原生本地化框架實現無縫語言切換。
本地化架構
┌─────────────────┐
│ │
│ 字符串資源文件 │
│ │
└────────┬────────┘
│
┌────────▼────────┐
│ │
│ 本地化管理器 │
│ │
└────────┬────────┘
│
┌────────▼────────┐
│ │
│ 應用UI │
│ │
└─────────────────┘
字符串資源組織
所有用戶可見文本應存儲在本地化字符串文件中,遵循以下結構:
Resources/
├── Localizations/
│ ├── en.lproj/
│ │ ├── Localizable.strings
│ │ ├── InfoPlist.strings
│ │ └── DiaryStrings.strings
│ ├── zh-Hant.lproj/
│ │ ├── Localizable.strings
│ │ ├── InfoPlist.strings
│ │ └── DiaryStrings.strings
│ └── ja.lproj/
│ ├── Localizable.strings
│ ├── InfoPlist.strings
│ └── DiaryStrings.strings
字符串文件格式
所有字符串文件應遵循標準鍵值對格式,並包含註釋:
/* 登錄屏幕 - 歡迎消息 */
"login.welcome" = "歡迎來到 Everly";
/* 登錄屏幕 - 谷歌登錄按鈕 */
"login.google_button" = "使用谷歌賬號登錄";
/* 錯誤消息 - 網絡連接失敗 */
"error.network" = "無法連接到服務器,請檢查網絡連接";
本地化實現
使用 SwiftUI 的內置本地化支持:
struct LocalizationExampleView: View {
var body: some View {
VStack {
Text("login.welcome")
.font(.largeTitle)
.padding()
Button(action: signInWithGoogle) {
Text("login.google_button")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}
private func signInWithGoogle() {
// 登錄邏輯
}
}
本地化擴展
為更簡潔的代碼,添加String擴展:
extension String {
var localized: String {
return NSLocalizedString(self, comment: "")
}
func localized(with arguments: CVarArg...) -> String {
return String(format: self.localized, arguments: arguments)
}
}
// 使用示例
Text("diary.entry_count".localized(with: entries.count))
動態語言切換
通過設置支持動態切換應用語言,無需重啟:
class LocalizationManager: ObservableObject {
@Published var currentLanguage: Language = .system
enum Language: String, CaseIterable, Identifiable {
case system = "system"
case english = "en"
case traditionalChinese = "zh-Hant"
case japanese = "ja"
var id: String { self.rawValue }
var displayName: String {
switch self {
case .system: return "跟隨系統".localized
case .english: return "English"
case .traditionalChinese: return "繁體中文"
case .japanese: return "日本語"
}
}
}
init() {
if let savedLanguage = UserDefaults.standard.string(forKey: "app_language"),
let language = Language(rawValue: savedLanguage) {
currentLanguage = language
}
}
func setLanguage(_ language: Language) {
currentLanguage = language
UserDefaults.standard.set(language.rawValue, forKey: "app_language")
// 更新Bundle的語言設置
if language != .system {
UserDefaults.standard.set([language.rawValue], forKey: "AppleLanguages")
} else {
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
}
UserDefaults.standard.synchronize()
// 發送通知以更新UI
NotificationCenter.default.post(name: Notification.Name("LanguageChanged"), object: nil)
}
}
語言選擇界面
提供語言選擇設置界面:
struct LanguageSettingsView: View {
@EnvironmentObject var localizationManager: LocalizationManager
@Environment(\.presentationMode) var presentationMode
var body: some View {
List {
ForEach(LocalizationManager.Language.allCases) { language in
Button(action: {
localizationManager.setLanguage(language)
presentationMode.wrappedValue.dismiss()
}) {
HStack {
Text(language.displayName)
Spacer()
if localizationManager.currentLanguage == language {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
}
.foregroundColor(.primary)
}
}
.navigationBarTitle("settings.language".localized)
}
}
主題系統
Everly 支持淺色和深色主題,以及自定義主題顏色。
主題架構
┌─────────────────┐
│ │
│ 顏色資產 │
│ │
└────────┬────────┘
│
┌────────▼────────┐
│ │
│ 主題管理器 │
│ │
└────────┬────────┘
│
┌────────▼────────┐
│ │
│ 應用UI │
│ │
└─────────────────┘
顏色資產組織
在 Assets.xcassets 中創建 Colors 文件夾,為每種顏色定義淺色和深色變體:
Assets.xcassets/
├── Colors/
│ ├── primaryBackground.colorset/
│ ├── secondaryBackground.colorset/
│ ├── primaryText.colorset/
│ ├── secondaryText.colorset/
│ ├── accentColor.colorset/
│ ├── cardBackground.colorset/
│ └── ...
主題定義
創建主題結構和顏色擴展:
enum ColorTheme: String, CaseIterable, Identifiable {
case mint = "mint" // 默認薄荷綠
case lavender = "lavender"
case peach = "peach"
case sky = "sky"
var id: String { self.rawValue }
var accentColor: Color {
switch self {
case .mint: return Color(hex: "#C4E8C2")
case .lavender: return Color(hex: "#D6C6E1")
case .peach: return Color(hex: "#F8C3B4")
case .sky: return Color(hex: "#A9D6E5")
}
}
var displayName: String {
switch self {
case .mint: return "薄荷綠"
case .lavender: return "薰衣草"
case .peach: return "蜜桃粉"
case .sky: return "天空藍"
}
}
}
struct AppTheme {
let colorTheme: ColorTheme
let isDarkMode: Bool
// 主要背景色
var primaryBackground: Color {
isDarkMode ? Color("darkBackground") : Color("lightBackground")
}
// 次要背景色
var secondaryBackground: Color {
isDarkMode ? Color("darkSecondaryBackground") : Color("lightSecondaryBackground")
}
// 主要文本色
var primaryText: Color {
isDarkMode ? Color.white : Color.black
}
// 次要文本色
var secondaryText: Color {
isDarkMode ? Color.gray : Color.gray.opacity(0.7)
}
// 強調色 (使用主題顏色)
var accent: Color {
colorTheme.accentColor
}
// 卡片背景
var cardBackground: Color {
isDarkMode ? Color("darkCardBackground") : Color("lightCardBackground")
}
// 其他顏色...
}
主題管理器
創建可觀察的主題管理器:
class ThemeManager: ObservableObject {
@Published var currentTheme: AppTheme
@Published var colorTheme: ColorTheme {
didSet {
UserDefaults.standard.set(colorTheme.rawValue, forKey: "app_color_theme")
updateCurrentTheme()
}
}
@Published var userInterfaceStyle: UIUserInterfaceStyle {
didSet {
UserDefaults.standard.set(userInterfaceStyle.rawValue, forKey: "app_interface_style")
updateCurrentTheme()
}
}
init() {
// 加載保存的主題設置
let savedTheme = UserDefaults.standard.string(forKey: "app_color_theme") ?? ColorTheme.mint.rawValue
self.colorTheme = ColorTheme(rawValue: savedTheme) ?? .mint
let savedStyle = UserDefaults.standard.integer(forKey: "app_interface_style")
self.userInterfaceStyle = UIUserInterfaceStyle(rawValue: savedStyle) ?? .unspecified
self.currentTheme = AppTheme(
colorTheme: self.colorTheme,
isDarkMode: userInterfaceStyle == .dark ||
(userInterfaceStyle == .unspecified && UITraitCollection.current.userInterfaceStyle == .dark)
)
}
private func updateCurrentTheme() {
currentTheme = AppTheme(
colorTheme: colorTheme,
isDarkMode: userInterfaceStyle == .dark ||
(userInterfaceStyle == .unspecified && UITraitCollection.current.userInterfaceStyle == .dark)
)
}
func setColorTheme(_ theme: ColorTheme) {
self.colorTheme = theme
}
func setUserInterfaceStyle(_ style: UIUserInterfaceStyle) {
self.userInterfaceStyle = style
// 更新窗口的外觀
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
windowScene.windows.forEach { window in
window.overrideUserInterfaceStyle = style
}
}
}
}
在SwiftUI中使用主題
將主題管理器作為環境對象注入應用:
@main
struct EverlyApp: App {
@StateObject private var themeManager = ThemeManager()
@StateObject private var localizationManager = LocalizationManager()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(themeManager)
.environmentObject(localizationManager)
.onAppear {
// 設置窗口的外觀
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
windowScene.windows.forEach { window in
window.overrideUserInterfaceStyle = themeManager.userInterfaceStyle
}
}
}
}
}
}
在視圖中使用主題:
struct ThemedView: View {
@EnvironmentObject var themeManager: ThemeManager
var body: some View {
VStack {
Text("settings.appearance".localized)
.font(.title)
.foregroundColor(themeManager.currentTheme.primaryText)
Button(action: { /* 操作 */ }) {
Text("common.confirm".localized)
.padding()
.background(themeManager.currentTheme.accent)
.foregroundColor(.white)
.cornerRadius(8)
}
}
.padding()
.background(themeManager.currentTheme.primaryBackground)
}
}
主題選擇界面
提供主題選擇設置界面:
struct AppearanceSettingsView: View {
@EnvironmentObject var themeManager: ThemeManager
var body: some View {
Form {
Section(header: Text("settings.dark_mode".localized)) {
Picker("settings.appearance".localized, selection: $themeManager.userInterfaceStyle) {
Text("settings.follow_system".localized)
.tag(UIUserInterfaceStyle.unspecified)
Text("settings.light".localized)
.tag(UIUserInterfaceStyle.light)
Text("settings.dark".localized)
.tag(UIUserInterfaceStyle.dark)
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("settings.color_theme".localized)) {
ForEach(ColorTheme.allCases) { theme in
Button(action: {
themeManager.setColorTheme(theme)
}) {
HStack {
Circle()
.fill(theme.accentColor)
.frame(width: 20, height: 20)
Text(theme.displayName)
.padding(.leading, 8)
Spacer()
if themeManager.colorTheme == theme {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
}
.foregroundColor(.primary)
}
}
}
.navigationBarTitle("settings.appearance".localized)
}
}
內容動態適配
確保所有內容根據用戶設置自動適配:
響應系統主題變化
監聽系統主題變化:
struct ThemeAwareModifier: ViewModifier {
@EnvironmentObject var themeManager: ThemeManager
@Environment(\.colorScheme) var colorScheme
func body(content: Content) -> some View {
content
.onReceive(NotificationCenter.default.publisher(for: UIApplication.traitCollectionDidChangeNotification)) { _ in
if themeManager.userInterfaceStyle == .unspecified {
themeManager.updateCurrentTheme()
}
}
}
}
extension View {
func themeAware() -> some View {
self.modifier(ThemeAwareModifier())
}
}
響應動態文字大小
支持動態文字大小:
struct ScalableText: View {
let key: LocalizedStringKey
let font: Font
init(_ key: LocalizedStringKey, font: Font = .body) {
self.key = key
self.font = font
}
var body: some View {
Text(key)
.font(font)
.dynamicTypeSize(...DynamicTypeSize.accessibility3)
}
}
實現最佳實踐
- 所有字符串外部化: 不要在代碼中硬編碼字符串
- 使用命名空間: 按功能對字符串鍵進行分組
- 提供註釋: 為翻譯者添加上下文註釋
- 避免字符串連接: 使用格式化字符串和參數
- 動態響應: 語言或主題變更時實時更新UI
- 本地化媒體: 不要忘記圖像和其他媒體資源
- 測試所有語言: 在每種支持的語言中測試應用UI
- 考慮文本長度: 設計時考慮不同語言的文本長度差異
- 本地化數字和日期: 使用適當的格式化器
- 無障礙考慮: 確保在所有主題下都有足夠的對比度
