语言切换立即生效:视图、应用菜单与菜单栏随设置重建,不再需要重启
This commit is contained in:
1 parent
9af1aaaf27
commit
11fc451fae
12 files changed
+37
-27
No files matched your search
+4
-2
@@ -174,8 +174,10 @@ Source strings are Simplified Chinese. `Scripts/l10n_wrap.py` wraps every Chines
|
||||
`tr` returns the input unless English is active; otherwise it looks the text up in
|
||||
`Localization/Translations.swift` — exact keys first, then templates where `{}` stands for an
|
||||
interpolated value, longest literal fragments first, translating captured values once more. The
|
||||
language is chosen at launch (System follows the global `AppleLanguages`) and applies after a
|
||||
relaunch; dates use `L10n.locale`. `--snapshot <dir> --language en` renders English screenshots and
|
||||
language is configured at launch (System follows the global `AppleLanguages`). Changing it
|
||||
reconfigures `L10n`, rebuilds the main window and update prompt through `.id(language)`, rebuilds the
|
||||
app menu and redraws the menu bar; popovers are created on open. Names supplied by macOS follow the
|
||||
app's `AppleLanguages`, which changes at the next launch. Dates use `L10n.locale`. `--snapshot <dir> --language en` renders English screenshots and
|
||||
writes any untranslated string to `untranslated.txt`.
|
||||
|
||||
`--snapshot <dir>` renders every main-window page, popover, settings section and the menu bar in light and
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
- 蓝牙设备电量:本机信息页列出已连接的键盘、鼠标、触控板和耳机的电量(AirPods 分左耳、右耳、充电盒),低于 20% 标红;通知里新增“蓝牙设备电量低”(低于 15%)。
|
||||
- 全局快捷键(设置 · 通用):可为显示 / 隐藏主窗口、打开进程页、开关防休眠、释放内存录制快捷键,在任何应用中都能用,不需要辅助功能权限;被其他应用占用时提示更换。
|
||||
- 桌面小组件“系统概览”:小号显示 CPU、内存圆环与磁盘用量,中号显示 CPU、内存、磁盘、电池四个圆环和磁盘可用空间;在自己的沙盒进程里读取数据,主应用没开也能显示,刷新间隔由系统决定(几分钟一次)。
|
||||
- 英文界面(设置 · 通用 · 语言):跟随系统、简体中文、English 三选一,系统首选语言不是中文时默认显示英文;约 880 条界面文案、通知、错误提示和 Apple 智能解释的回答都有英文版本,日期按英文格式显示,切换后重启生效。
|
||||
- 英文界面(设置 · 通用 · 语言):跟随系统、简体中文、English 三选一,系统首选语言不是中文时默认显示英文;约 880 条界面文案、通知、错误提示和 Apple 智能解释的回答都有英文版本,日期按英文格式显示;切换后界面、菜单与菜单栏立即换成新语言,显示器、应用名称等由系统提供的文字在下次启动时切换。
|
||||
|
||||
#### 调整
|
||||
|
||||
|
||||
@@ -888,8 +888,7 @@ npm 缓存 npm Cache
|
||||
,{} 项未能移动 , {} couldn't be moved
|
||||
,已从程序坞移除 , removed from the Dock
|
||||
:{} : {}
|
||||
重新启动 OpenStats 后生效 Takes effect after OpenStats relaunches
|
||||
立即重启 Relaunch Now
|
||||
立即切换;显示器、应用名称等由系统提供的文字在下次启动时切换 Applies right away; names supplied by macOS, such as displays and apps, switch at next launch
|
||||
1 项 1 item
|
||||
1 个进程 1 process
|
||||
1 个应用,系统自带的不列出 1 app; built-in apps are not listed
|
||||
|
||||
@@ -53,6 +53,7 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
observeProbeSettings()
|
||||
applyAppearance()
|
||||
updateNetworkVisibility()
|
||||
appliedLanguage = model.settings.language
|
||||
Task { await model.geo.updateIfNeeded() }
|
||||
startUpdateChecks()
|
||||
model.alerts.openTab = { [weak self] tab in
|
||||
@@ -185,9 +186,11 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
_ = model.settings.probeEnabled
|
||||
_ = model.settings.probeInBackground
|
||||
_ = model.settings.enabledAlerts
|
||||
_ = model.settings.language
|
||||
} onChange: { [weak self] in
|
||||
Task { @MainActor in
|
||||
guard let self else { return }
|
||||
self.applyLanguageIfChanged()
|
||||
self.model.alerts.applySettings()
|
||||
await self.model.hub.update(self.model.demand)
|
||||
self.applyAppearance()
|
||||
@@ -219,6 +222,22 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private var appliedLanguage: AppLanguage?
|
||||
|
||||
/// 切换语言后重建应用菜单、菜单栏图标文字与已打开的弹窗
|
||||
private func applyLanguageIfChanged() {
|
||||
let language = model.settings.language
|
||||
guard appliedLanguage != nil, appliedLanguage != language else {
|
||||
appliedLanguage = language
|
||||
return
|
||||
}
|
||||
appliedLanguage = language
|
||||
NSApp.mainMenu = MainMenu.make(target: self, settingsAction: #selector(openSettingsFromMenu),
|
||||
updateAction: #selector(checkForUpdatesFromMenu))
|
||||
menuBar.dismissPopovers()
|
||||
menuBar.refreshImages()
|
||||
}
|
||||
|
||||
private func updateNetworkVisibility() {
|
||||
model.network.setVisibility(inMenuBar: model.settings.menuBarItems.contains(.network),
|
||||
detailVisible: model.isNetworkDetailVisible)
|
||||
|
||||
@@ -61,6 +61,8 @@ public struct MainWindowView: View {
|
||||
.background(DS.Palette.background)
|
||||
// 内容延伸到透明标题栏下方,由顶栏高度留出红绿灯按钮的位置
|
||||
.ignoresSafeArea()
|
||||
// 文案在各视图计算时翻译好,切换语言后整棵视图重建
|
||||
.id(model.settings.language)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,18 +81,10 @@ struct GeneralSettings: View {
|
||||
}
|
||||
}
|
||||
GroupRow {
|
||||
SettingRow(title: "语言 / Language",
|
||||
subtitle: settings.language.resolved == L10n.language ? nil : tr("重新启动 OpenStats 后生效")) {
|
||||
HStack(spacing: DS.Space.s2) {
|
||||
if settings.language.resolved != L10n.language {
|
||||
Button(tr("立即重启")) { model.relaunch() }
|
||||
.buttonStyle(DSButtonStyle(kind: .primary))
|
||||
.fixedSize()
|
||||
}
|
||||
SegmentedControl(selection: $settings.language,
|
||||
options: AppLanguage.allCases.map { ($0, $0.title) })
|
||||
.frame(width: DS.Size.sidebarWidth + DS.Space.s12)
|
||||
}
|
||||
SettingRow(title: "语言 / Language", subtitle: tr("立即切换;显示器、应用名称等由系统提供的文字在下次启动时切换")) {
|
||||
SegmentedControl(selection: $settings.language,
|
||||
options: AppLanguage.allCases.map { ($0, $0.title) })
|
||||
.frame(width: DS.Size.sidebarWidth + DS.Space.s12)
|
||||
}
|
||||
}
|
||||
GroupRow {
|
||||
|
||||
@@ -138,6 +138,7 @@ struct UpdatePromptView: View {
|
||||
.padding(.top, DS.Size.windowHeader)
|
||||
.frame(width: DS.Size.updateWindowWidth)
|
||||
.background(DS.Palette.background)
|
||||
.id(model.settings.language)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,12 +97,6 @@ public final class AppModel {
|
||||
openPopover == .network || (isMainWindowVisible && settings.panelTab == .network)
|
||||
}
|
||||
|
||||
/// 等当前进程退出后重新打开应用(切换语言后使用)
|
||||
func relaunch() {
|
||||
try? UpdateInstaller.relaunch(Bundle.main.bundleURL)
|
||||
quit()
|
||||
}
|
||||
|
||||
func refreshLaunchAtLogin() {
|
||||
launchAtLoginEnabled = SMAppService.mainApp.status == .enabled
|
||||
}
|
||||
|
||||
@@ -404,11 +404,12 @@ public final class AppSettings {
|
||||
public var alertCPUTemperature: Int {
|
||||
didSet { defaults.set(alertCPUTemperature, forKey: Keys.alertCPUTemperature) }
|
||||
}
|
||||
/// 界面语言,重启后生效
|
||||
/// 界面语言:界面文字立即切换;系统提供的名称(显示器、应用名)在下次启动时跟着切换
|
||||
public var language: AppLanguage {
|
||||
didSet {
|
||||
defaults.set(language.rawValue, forKey: Keys.language)
|
||||
language.applyToProcessLocale(defaults: defaults)
|
||||
L10n.configure(language)
|
||||
}
|
||||
}
|
||||
nonisolated public static let languageKey = "language"
|
||||
|
||||
@@ -25,7 +25,7 @@ public final class GeoDatabaseController {
|
||||
@ObservationIgnored private var asnDatabase: MaxMindDatabase?
|
||||
|
||||
static let manifestURL = URL(string: "https://getopenstats.com/geoip/manifest.json")!
|
||||
static let attribution = tr("本产品包含 MaxMind 创建的 GeoLite2 数据,可从 https://www.maxmind.com 获取。")
|
||||
static var attribution: String { tr("本产品包含 MaxMind 创建的 GeoLite2 数据,可从 https://www.maxmind.com 获取。") }
|
||||
private static let checkInterval: TimeInterval = 3 * 24 * 3600
|
||||
private static let lastCheckedKey = "geoDatabaseLastChecked"
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public final class HelperClient {
|
||||
|
||||
public var isReady: Bool { status == .enabled }
|
||||
|
||||
static let outdatedMessage = tr("辅助工具版本比应用旧,部分功能可能无法使用,请重新安装一次(需要管理员授权)。")
|
||||
static var outdatedMessage: String { tr("辅助工具版本比应用旧,部分功能可能无法使用,请重新安装一次(需要管理员授权)。") }
|
||||
|
||||
/// 未安装、待批准或版本过旧,需要用户处理
|
||||
var needsAttention: Bool { !isReady || isOutdated }
|
||||
|
||||
@@ -80,7 +80,7 @@ public final class MaintenanceController {
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated private static let cancelled = tr("已取消")
|
||||
nonisolated private static var cancelled: String { tr("已取消") }
|
||||
|
||||
/// 未安装辅助工具时的回退:通过 AppleScript 请求一次性管理员授权执行命令。
|
||||
/// 命令只由固定路径与已校验的参数拼成,这里再做 AppleScript 字符串转义
|
||||
|
||||
Reference in new issue
Block a user