Add localization and localize settings pages

Add many new localization keys (en-US and zh-CN) for notifications, developer tools, about page, status bar, and video wallpaper. Update Notification, Dev, About and StatusBar view models to use LocalizationService, expose localized ObservableProperties, and refresh localized text at construction. Localize selection options and test notification texts, and fix notification severity handling. Wire up XAML to the new localized properties (About/Dev/StatusBar pages) and update the settings page title for notifications. Also adjust copyright line generation and replace hardcoded placeholders with bound Watermark properties.
This commit is contained in:
lincube
2026-05-04 04:09:51 +08:00
parent 6a30bc6fce
commit 1d7df5a105
9 changed files with 408 additions and 82 deletions

View File

@@ -535,6 +535,78 @@
"settings.about.codename_label": "Codename", "settings.about.codename_label": "Codename",
"settings.about.render_backend_label": "Render Backend", "settings.about.render_backend_label": "Render Backend",
"settings.about.render_backend_format": "Render Backend: {0}", "settings.about.render_backend_format": "Render Backend: {0}",
"settings.about.project_resources_header": "Project resources",
"settings.about.link_github": "GitHub Repository",
"settings.about.link_issues": "Issue Tracker",
"settings.about.copyright_format": "Copyright (c) 2024-{0} Lincube",
"settings.notifications.title": "Notifications",
"settings.notifications.description": "Configure global notifications, interaction behavior, limits, and send test toasts.",
"settings.notifications.section_header": "Notifications",
"settings.notifications.enable_header": "Enable notifications",
"settings.notifications.enable_desc": "Turn all notification toasts on or off.",
"settings.notifications.behavior_header": "Behavior",
"settings.notifications.hover_pause_header": "Pause on hover",
"settings.notifications.hover_pause_desc": "Pause the auto-dismiss timer while the pointer is over a notification.",
"settings.notifications.click_close_header": "Close on click",
"settings.notifications.click_close_desc": "Dismiss a notification when it is clicked.",
"settings.notifications.max_header": "Max per position",
"settings.notifications.max_desc": "Maximum notifications shown at once for each corner or edge.",
"settings.notifications.test_header": "Test",
"settings.notifications.test_notification_header": "Test notification",
"settings.notifications.test_notification_desc": "Pick a position and severity, then send a sample notification.",
"settings.notifications.default_position_header": "Default position",
"settings.notifications.default_position_desc": "Where notifications appear first.",
"settings.notifications.duration_header": "Visible duration",
"settings.notifications.duration_desc": "How long notifications stay on screen.",
"settings.notifications.position.top_left": "Top left",
"settings.notifications.position.top_right": "Top right",
"settings.notifications.position.top_center": "Top center",
"settings.notifications.position.bottom_left": "Bottom left",
"settings.notifications.position.bottom_right": "Bottom right",
"settings.notifications.position.bottom_center": "Bottom center",
"settings.notifications.position.center": "Center",
"settings.notifications.duration.2s": "2 seconds",
"settings.notifications.duration.4s": "4 seconds",
"settings.notifications.duration.6s": "6 seconds",
"settings.notifications.duration.8s": "8 seconds",
"settings.notifications.duration.10s": "10 seconds",
"settings.notifications.severity.info": "Info",
"settings.notifications.severity.success": "Success",
"settings.notifications.severity.warning": "Warning",
"settings.notifications.severity.error": "Error",
"settings.notifications.test.title_info": "Test notification",
"settings.notifications.test.message_info": "This is an informational test notification.",
"settings.notifications.test.title_success": "Succeeded",
"settings.notifications.test.message_success": "The task completed successfully.",
"settings.notifications.test.title_warning": "Warning",
"settings.notifications.test.message_warning": "Please review this notice.",
"settings.notifications.test.title_error": "Error",
"settings.notifications.test.message_error": "Something went wrong. Please try again.",
"settings.notifications.test.title_default": "Test notification",
"settings.notifications.test.message_default": "This is a test notification.",
"settings.dev.title": "Developer",
"settings.dev.description": "Debugging, diagnostics, and local plugin development options.",
"settings.dev.infobar.title": "Preview and developer features",
"settings.dev.infobar.message": "These options are intended for debugging, diagnostics, and local plugin development.",
"settings.dev.mode_header": "Developer mode",
"settings.dev.mode_description": "Enable developer-focused startup helpers and diagnostics.",
"settings.dev.three_finger_header": "Three-finger desktop swipe",
"settings.dev.three_finger_description": "Enable desktop page switching gestures when the current platform supports them.",
"settings.dev.fused_header": "Fused desktop experience",
"settings.dev.fused_description": "Enable the fused desktop shell and its related experimental entry points.",
"settings.dev.plugin_path_header": "Development plugin path",
"settings.dev.plugin_path_description": "Load a local plugin output directory for iterative debugging without packaging.",
"settings.dev.plugin_path_placeholder": "e.g. C:\\path\\to\\plugin\\bin\\Debug\\net10.0",
"settings.dev.startup_args_header": "Developer startup arguments",
"settings.dev.startup_args_description": "Use these launch arguments or environment variables to start the app in development scenarios.",
"settings.dev.cli_label": "Command-line arguments:",
"settings.dev.env_label": "Environment variables:",
"settings.dev.other_args_label": "Other arguments:",
"settings.dev.cli_example": "--dev-plugin <path> or -dp <path>",
"settings.dev.env_example": "LMD_DEV_PLUGIN=<path>",
"settings.dev.other_dev_mode": "--dev-mode / -dev Enable developer mode startup helpers.",
"settings.dev.other_hot_reload": "--hot-reload / -hr Enable hot reload for development builds.",
"settings.status_bar.text_capsule_placeholder": "Enter Markdown text…",
"settings.restart_dialog.title": "Restart required", "settings.restart_dialog.title": "Restart required",
"settings.restart_dialog.render_mode_message": "Restart the app to switch the rendering mode from \"{0}\" to \"{1}\". Restart now?", "settings.restart_dialog.render_mode_message": "Restart the app to switch the rendering mode from \"{0}\" to \"{1}\". Restart now?",
"settings.restart_dialog.restart": "Restart now", "settings.restart_dialog.restart": "Restart now",

View File

@@ -69,6 +69,11 @@
"settings.wallpaper.storage_unavailable": "存储提供器不可用。", "settings.wallpaper.storage_unavailable": "存储提供器不可用。",
"settings.wallpaper.import_failed": "导入壁纸文件失败。", "settings.wallpaper.import_failed": "导入壁纸文件失败。",
"settings.wallpaper.image_applied": "图片壁纸已应用。", "settings.wallpaper.image_applied": "图片壁纸已应用。",
"settings.wallpaper.video_applied": "视频壁纸已应用。",
"settings.wallpaper.video_restored": "已从保存的设置恢复视频壁纸。",
"settings.wallpaper.video_not_found": "未找到视频壁纸文件。",
"settings.wallpaper.video_player_unavailable": "视频播放器不可用。",
"settings.wallpaper.video_play_failed_format": "播放视频壁纸失败:{0}",
"settings.wallpaper.unsupported_file": "所选文件类型不受支持。", "settings.wallpaper.unsupported_file": "所选文件类型不受支持。",
"settings.wallpaper.apply_failed_format": "应用壁纸失败:{0}", "settings.wallpaper.apply_failed_format": "应用壁纸失败:{0}",
"settings.wallpaper.mode_format": "壁纸模式:{0}。", "settings.wallpaper.mode_format": "壁纸模式:{0}。",
@@ -102,6 +107,7 @@
"settings.color.theme_ready_format": "主题色已就绪:{0}。", "settings.color.theme_ready_format": "主题色已就绪:{0}。",
"settings.color.theme_applied_format": "{0}主题色已应用:{1}。", "settings.color.theme_applied_format": "{0}主题色已应用:{1}。",
"settings.color.theme_updated_wallpaper": "壁纸已更新,莫奈色已刷新。", "settings.color.theme_updated_wallpaper": "壁纸已更新,莫奈色已刷新。",
"settings.color.theme_updated_video": "视频壁纸已更新,主题色已刷新。",
"settings.color.theme_cleared_wallpaper": "壁纸已清除,莫奈色已刷新。", "settings.color.theme_cleared_wallpaper": "壁纸已清除,莫奈色已刷新。",
"settings.status_bar.title": "状态栏", "settings.status_bar.title": "状态栏",
"settings.status_bar.description": "选择顶部状态栏显示的组件。", "settings.status_bar.description": "选择顶部状态栏显示的组件。",
@@ -541,6 +547,79 @@
"settings.footer": "LanMountainDesktop 设置", "settings.footer": "LanMountainDesktop 设置",
"filepicker.title": "选择壁纸", "filepicker.title": "选择壁纸",
"filepicker.image_files": "图片文件", "filepicker.image_files": "图片文件",
"filepicker.video_files": "视频文件",
"settings.notifications.title": "通知",
"settings.notifications.description": "配置全局通知开关、交互行为与数量上限,并可发送测试通知。",
"settings.notifications.section_header": "通知",
"settings.notifications.enable_header": "启用通知",
"settings.notifications.enable_desc": "开启或关闭全局通知功能。",
"settings.notifications.behavior_header": "行为",
"settings.notifications.hover_pause_header": "悬停暂停计时",
"settings.notifications.hover_pause_desc": "鼠标悬停时暂停自动关闭倒计时。",
"settings.notifications.click_close_header": "点击关闭",
"settings.notifications.click_close_desc": "点击通知后立即关闭。",
"settings.notifications.max_header": "每区域最大数量",
"settings.notifications.max_desc": "同一位置最多同时显示多少条通知。",
"settings.notifications.test_header": "测试",
"settings.notifications.test_notification_header": "测试通知",
"settings.notifications.test_notification_desc": "选择位置与类型后发送一条测试通知。",
"settings.notifications.default_position_header": "默认位置",
"settings.notifications.default_position_desc": "通知首次出现的位置。",
"settings.notifications.duration_header": "显示时长",
"settings.notifications.duration_desc": "通知在屏幕上保持可见的时间。",
"settings.notifications.position.top_left": "左上角",
"settings.notifications.position.top_right": "右上角",
"settings.notifications.position.top_center": "正上方",
"settings.notifications.position.bottom_left": "左下角",
"settings.notifications.position.bottom_right": "右下角",
"settings.notifications.position.bottom_center": "正下方",
"settings.notifications.position.center": "正中央",
"settings.notifications.duration.2s": "2 秒",
"settings.notifications.duration.4s": "4 秒",
"settings.notifications.duration.6s": "6 秒",
"settings.notifications.duration.8s": "8 秒",
"settings.notifications.duration.10s": "10 秒",
"settings.notifications.severity.info": "信息",
"settings.notifications.severity.success": "成功",
"settings.notifications.severity.warning": "警告",
"settings.notifications.severity.error": "错误",
"settings.notifications.test.title_info": "测试通知",
"settings.notifications.test.message_info": "这是一条信息类型的测试通知。",
"settings.notifications.test.title_success": "操作成功",
"settings.notifications.test.message_success": "任务已完成。",
"settings.notifications.test.title_warning": "警告提示",
"settings.notifications.test.message_warning": "请注意检查。",
"settings.notifications.test.title_error": "错误报告",
"settings.notifications.test.message_error": "操作失败,请重试。",
"settings.notifications.test.title_default": "测试通知",
"settings.notifications.test.message_default": "这是一条测试通知。",
"settings.dev.title": "开发者",
"settings.dev.description": "调试、诊断与本地插件开发相关选项。",
"settings.dev.infobar.title": "预览与开发者功能",
"settings.dev.infobar.message": "以下选项适用于调试、诊断与本地插件开发场景。",
"settings.dev.mode_header": "开发者模式",
"settings.dev.mode_description": "启用面向开发者的启动辅助与诊断信息。",
"settings.dev.three_finger_header": "三指滑动切换桌面页",
"settings.dev.three_finger_description": "在当前平台支持时,启用手势在桌面分页间切换。",
"settings.dev.fused_header": "融合桌面体验",
"settings.dev.fused_description": "启用融合桌面壳及相关实验入口。",
"settings.dev.plugin_path_header": "开发插件路径",
"settings.dev.plugin_path_description": "加载本地插件输出目录以便免打包迭代调试。",
"settings.dev.plugin_path_placeholder": "例如C:\\path\\to\\plugin\\bin\\Debug\\net10.0",
"settings.dev.startup_args_header": "开发者启动参数",
"settings.dev.startup_args_description": "可使用下列命令行参数或环境变量启动应用以进行开发。",
"settings.dev.cli_label": "命令行参数:",
"settings.dev.env_label": "环境变量:",
"settings.dev.other_args_label": "其它参数:",
"settings.dev.cli_example": "--dev-plugin <path> 或 -dp <path>",
"settings.dev.env_example": "LMD_DEV_PLUGIN=<path>",
"settings.dev.other_dev_mode": "--dev-mode / -dev 启用开发者模式启动辅助。",
"settings.dev.other_hot_reload": "--hot-reload / -hr 为开发构建启用热重载。",
"settings.about.project_resources_header": "项目资源",
"settings.about.link_github": "GitHub 仓库",
"settings.about.link_issues": "问题反馈",
"settings.about.copyright_format": "Copyright (c) 2024-{0} Lincube",
"settings.status_bar.text_capsule_placeholder": "请输入 Markdown 文本…",
"common.day": "日间", "common.day": "日间",
"common.night": "夜间", "common.night": "夜间",
"common.back": "返回", "common.back": "返回",

View File

@@ -13,23 +13,26 @@ namespace LanMountainDesktop.ViewModels;
public sealed partial class NotificationSettingsPageViewModel : ViewModelBase public sealed partial class NotificationSettingsPageViewModel : ViewModelBase
{ {
private readonly ISettingsFacadeService _settingsFacade; private readonly ISettingsFacadeService _settingsFacade;
private readonly LocalizationService _localizationService = new();
private readonly string _languageCode;
private bool _isInitializing; private bool _isInitializing;
public NotificationSettingsPageViewModel(ISettingsFacadeService settingsFacade) public NotificationSettingsPageViewModel(ISettingsFacadeService settingsFacade)
{ {
_settingsFacade = settingsFacade ?? throw new ArgumentNullException(nameof(settingsFacade)); _settingsFacade = settingsFacade ?? throw new ArgumentNullException(nameof(settingsFacade));
_languageCode = _localizationService.NormalizeLanguageCode(_settingsFacade.Region.Get().LanguageCode);
Positions = CreatePositionOptions(); Positions = CreatePositionOptions();
Durations = CreateDurationOptions(); Durations = CreateDurationOptions();
TestPositions = CreatePositionOptions(); TestPositions = CreatePositionOptions();
TestSeverities = CreateSeverityOptions(); TestSeverities = CreateSeverityOptions();
RefreshLocalizedText();
LoadSettings(); LoadSettings();
// Initialize test selections SelectedTestPosition = TestPositions[1];
SelectedTestPosition = TestPositions[1]; // TopRight SelectedTestSeverity = TestSeverities[0];
SelectedTestSeverity = TestSeverities[0]; // Info TestDurationSeconds = 4;
TestDurationSeconds = 4; // Default 4 seconds
} }
private void LoadSettings() private void LoadSettings()
@@ -44,11 +47,11 @@ public sealed partial class NotificationSettingsPageViewModel : ViewModelBase
MaxNotificationsPerPosition = snapshot.NotificationMaxPerPosition; MaxNotificationsPerPosition = snapshot.NotificationMaxPerPosition;
SelectedPosition = Positions.FirstOrDefault(p => SelectedPosition = Positions.FirstOrDefault(p =>
string.Equals(p.Value, snapshot.NotificationDefaultPosition, StringComparison.OrdinalIgnoreCase)) string.Equals(p.Value, snapshot.NotificationDefaultPosition, StringComparison.OrdinalIgnoreCase))
?? Positions[1]; ?? Positions[1];
SelectedDuration = Durations.FirstOrDefault(d => SelectedDuration = Durations.FirstOrDefault(d =>
int.TryParse(d.Value, out var seconds) && seconds == snapshot.NotificationDurationSeconds) int.TryParse(d.Value, out var seconds) && seconds == snapshot.NotificationDurationSeconds)
?? Durations[1]; ?? Durations[1];
_isInitializing = false; _isInitializing = false;
@@ -81,71 +84,117 @@ public sealed partial class NotificationSettingsPageViewModel : ViewModelBase
]); ]);
} }
private static ObservableCollection<SelectionOption> CreatePositionOptions() private ObservableCollection<SelectionOption> CreatePositionOptions()
{ {
return return
[ [
new SelectionOption("TopLeft", "左上角"), new SelectionOption("TopLeft", L("settings.notifications.position.top_left", "Top left")),
new SelectionOption("TopRight", "右上角"), new SelectionOption("TopRight", L("settings.notifications.position.top_right", "Top right")),
new SelectionOption("TopCenter", "正上方"), new SelectionOption("TopCenter", L("settings.notifications.position.top_center", "Top center")),
new SelectionOption("BottomLeft", "左下角"), new SelectionOption("BottomLeft", L("settings.notifications.position.bottom_left", "Bottom left")),
new SelectionOption("BottomRight", "右下角"), new SelectionOption("BottomRight", L("settings.notifications.position.bottom_right", "Bottom right")),
new SelectionOption("BottomCenter", "正下方"), new SelectionOption("BottomCenter", L("settings.notifications.position.bottom_center", "Bottom center")),
new SelectionOption("Center", "正中央") new SelectionOption("Center", L("settings.notifications.position.center", "Center"))
]; ];
} }
private static ObservableCollection<SelectionOption> CreateDurationOptions() private ObservableCollection<SelectionOption> CreateDurationOptions()
{ {
return return
[ [
new SelectionOption("2", "2 秒"), new SelectionOption("2", L("settings.notifications.duration.2s", "2 seconds")),
new SelectionOption("4", "4 秒"), new SelectionOption("4", L("settings.notifications.duration.4s", "4 seconds")),
new SelectionOption("6", "6 秒"), new SelectionOption("6", L("settings.notifications.duration.6s", "6 seconds")),
new SelectionOption("8", "8 秒"), new SelectionOption("8", L("settings.notifications.duration.8s", "8 seconds")),
new SelectionOption("10", "10 秒") new SelectionOption("10", L("settings.notifications.duration.10s", "10 seconds"))
]; ];
} }
private static ObservableCollection<SelectionOption> CreateSeverityOptions() private ObservableCollection<SelectionOption> CreateSeverityOptions()
{ {
return return
[ [
new SelectionOption("Info", "信息"), new SelectionOption("Info", L("settings.notifications.severity.info", "Info")),
new SelectionOption("Success", "成功"), new SelectionOption("Success", L("settings.notifications.severity.success", "Success")),
new SelectionOption("Warning", "警告"), new SelectionOption("Warning", L("settings.notifications.severity.warning", "Warning")),
new SelectionOption("Error", "错误") new SelectionOption("Error", L("settings.notifications.severity.error", "Error"))
]; ];
} }
[ObservableProperty] private string _notificationHeader = "通知"; private void RefreshLocalizedText()
[ObservableProperty] private string _enableNotificationHeader = "启用通知"; {
[ObservableProperty] private string _enableNotificationDescription = "开启或关闭全局通知功能"; NotificationHeader = L("settings.notifications.section_header", "Notifications");
[ObservableProperty] private string _defaultPositionHeader = "默认位置"; EnableNotificationHeader = L("settings.notifications.enable_header", "Enable notifications");
[ObservableProperty] private string _defaultPositionDescription = "通知弹出的默认位置"; EnableNotificationDescription = L("settings.notifications.enable_desc", "Turn all notification toasts on or off.");
[ObservableProperty] private string _durationHeader = "显示时长"; BehaviorHeader = L("settings.notifications.behavior_header", "Behavior");
[ObservableProperty] private string _durationDescription = "通知自动关闭的时间"; HoverPauseHeader = L("settings.notifications.hover_pause_header", "Pause on hover");
[ObservableProperty] private string _behaviorHeader = "行为"; HoverPauseDescription = L("settings.notifications.hover_pause_desc", "Pause auto-dismiss while hovering.");
[ObservableProperty] private string _hoverPauseHeader = "悬停暂停"; ClickCloseHeader = L("settings.notifications.click_close_header", "Close on click");
[ObservableProperty] private string _hoverPauseDescription = "鼠标悬停时暂停自动关闭计时"; ClickCloseDescription = L("settings.notifications.click_close_desc", "Dismiss when clicked.");
[ObservableProperty] private string _clickCloseHeader = "点击关闭"; MaxNotificationsHeader = L("settings.notifications.max_header", "Max per position");
[ObservableProperty] private string _clickCloseDescription = "点击通知后关闭"; MaxNotificationsDescription = L("settings.notifications.max_desc", "Maximum notifications per corner or edge.");
[ObservableProperty] private string _maxNotificationsHeader = "最大数量"; TestHeader = L("settings.notifications.test_header", "Test");
[ObservableProperty] private string _maxNotificationsDescription = "每个位置最多显示的通知数量"; TestNotificationHeader = L("settings.notifications.test_notification_header", "Test notification");
[ObservableProperty] private string _testHeader = "测试"; TestNotificationDescription = L("settings.notifications.test_notification_desc", "Send a sample notification.");
[ObservableProperty] private string _testNotificationHeader = "测试通知"; DefaultPositionHeader = L("settings.notifications.default_position_header", "Default position");
[ObservableProperty] private string _testNotificationDescription = "选择位置和类型,发送测试通知"; DefaultPositionDescription = L("settings.notifications.default_position_desc", "Where notifications appear first.");
[ObservableProperty] private string _sendTestButtonText = "发送"; DurationHeader = L("settings.notifications.duration_header", "Visible duration");
DurationDescription = L("settings.notifications.duration_desc", "How long notifications stay on screen.");
}
private string L(string key, string fallback)
=> _localizationService.GetString(_languageCode, key, fallback);
[ObservableProperty] private string _notificationHeader = string.Empty;
[ObservableProperty] private string _enableNotificationHeader = string.Empty;
[ObservableProperty] private string _enableNotificationDescription = string.Empty;
[ObservableProperty] private string _defaultPositionHeader = string.Empty;
[ObservableProperty] private string _defaultPositionDescription = string.Empty;
[ObservableProperty] private string _durationHeader = string.Empty;
[ObservableProperty] private string _durationDescription = string.Empty;
[ObservableProperty] private string _behaviorHeader = string.Empty;
[ObservableProperty] private string _hoverPauseHeader = string.Empty;
[ObservableProperty] private string _hoverPauseDescription = string.Empty;
[ObservableProperty] private string _clickCloseHeader = string.Empty;
[ObservableProperty] private string _clickCloseDescription = string.Empty;
[ObservableProperty] private string _maxNotificationsHeader = string.Empty;
[ObservableProperty] private string _maxNotificationsDescription = string.Empty;
[ObservableProperty] private string _testHeader = string.Empty;
[ObservableProperty] private string _testNotificationHeader = string.Empty;
[ObservableProperty] private string _testNotificationDescription = string.Empty;
[ObservableProperty] private bool _isNotificationEnabled = true; [ObservableProperty] private bool _isNotificationEnabled = true;
[ObservableProperty] private bool _isHoverPauseEnabled = true; [ObservableProperty] private bool _isHoverPauseEnabled = true;
[ObservableProperty] private bool _isClickCloseEnabled = true; [ObservableProperty] private bool _isClickCloseEnabled = true;
[ObservableProperty] private int _maxNotificationsPerPosition = 5; [ObservableProperty] private int _maxNotificationsPerPosition = 5;
[ObservableProperty] private SelectionOption? _selectedPosition; [ObservableProperty] private SelectionOption? _selectedPosition;
[ObservableProperty] private SelectionOption? _selectedDuration; [ObservableProperty] private SelectionOption? _selectedDuration;
[ObservableProperty] private SelectionOption? _selectedTestPosition; [ObservableProperty] private SelectionOption? _selectedTestPosition;
[ObservableProperty] private SelectionOption? _selectedTestSeverity; [ObservableProperty] private SelectionOption? _selectedTestSeverity;
[ObservableProperty] private int _testDurationSeconds = 4; [ObservableProperty] private int _testDurationSeconds = 4;
public ObservableCollection<SelectionOption> Positions { get; } public ObservableCollection<SelectionOption> Positions { get; }
@@ -154,10 +203,15 @@ public sealed partial class NotificationSettingsPageViewModel : ViewModelBase
public ObservableCollection<SelectionOption> TestSeverities { get; } public ObservableCollection<SelectionOption> TestSeverities { get; }
partial void OnIsNotificationEnabledChanged(bool value) => SaveSettings(); partial void OnIsNotificationEnabledChanged(bool value) => SaveSettings();
partial void OnIsHoverPauseEnabledChanged(bool value) => SaveSettings(); partial void OnIsHoverPauseEnabledChanged(bool value) => SaveSettings();
partial void OnIsClickCloseEnabledChanged(bool value) => SaveSettings(); partial void OnIsClickCloseEnabledChanged(bool value) => SaveSettings();
partial void OnMaxNotificationsPerPositionChanged(int value) => SaveSettings(); partial void OnMaxNotificationsPerPositionChanged(int value) => SaveSettings();
partial void OnSelectedPositionChanged(SelectionOption? value) => SaveSettings(); partial void OnSelectedPositionChanged(SelectionOption? value) => SaveSettings();
partial void OnSelectedDurationChanged(SelectionOption? value) => SaveSettings(); partial void OnSelectedDurationChanged(SelectionOption? value) => SaveSettings();
[RelayCommand] [RelayCommand]
@@ -169,24 +223,32 @@ public sealed partial class NotificationSettingsPageViewModel : ViewModelBase
var position = Enum.Parse<NotificationPosition>(SelectedTestPosition.Value); var position = Enum.Parse<NotificationPosition>(SelectedTestPosition.Value);
var severity = SelectedTestSeverity.Value; var severity = SelectedTestSeverity.Value;
var (title, message) = severity! switch var (title, message) = severity switch
{ {
"Info" => ("测试通知", "这是一条信息类型的通知"), "Info" => (
"Success" => ("操作成功", "任务已完成"), L("settings.notifications.test.title_info", "Test notification"),
"Warning" => ("警告提示", "请注意检查"), L("settings.notifications.test.message_info", "This is an informational test notification.")),
"Error" => ("错误报告", "操作失败,请重试"), "Success" => (
_ => ("测试通知", "这是一条测试通知") L("settings.notifications.test.title_success", "Succeeded"),
L("settings.notifications.test.message_success", "The task completed successfully.")),
"Warning" => (
L("settings.notifications.test.title_warning", "Warning"),
L("settings.notifications.test.message_warning", "Please review this notice.")),
"Error" => (
L("settings.notifications.test.title_error", "Error"),
L("settings.notifications.test.message_error", "Something went wrong. Please try again.")),
_ => (
L("settings.notifications.test.title_default", "Test notification"),
L("settings.notifications.test.message_default", "This is a test notification."))
}; };
// Create notification content with specified duration
var content = new NotificationContent( var content = new NotificationContent(
Title: title, Title: title,
Message: message, Message: message,
Severity: Enum.Parse<NotificationSeverity>(severity), Severity: Enum.Parse<NotificationSeverity>(severity!),
Position: position, Position: position,
Duration: TimeSpan.FromSeconds(TestDurationSeconds)); Duration: TimeSpan.FromSeconds(TestDurationSeconds));
// Use Show method which will automatically route to dialog or toast based on position
App.CurrentNotificationService?.Show(content); App.CurrentNotificationService?.Show(content);
} }
} }

View File

@@ -1590,6 +1590,18 @@ public sealed partial class AboutSettingsPageViewModel : ViewModelBase
[ObservableProperty] [ObservableProperty]
private string _renderBackendLabel = string.Empty; private string _renderBackendLabel = string.Empty;
[ObservableProperty]
private string _projectResourcesHeader = string.Empty;
[ObservableProperty]
private string _linkGitHubText = string.Empty;
[ObservableProperty]
private string _linkIssuesText = string.Empty;
[ObservableProperty]
private string _copyrightLine = string.Empty;
private void RefreshLocalizedText() private void RefreshLocalizedText()
{ {
PageTitle = L("settings.about.title", "About"); PageTitle = L("settings.about.title", "About");
@@ -1598,6 +1610,14 @@ public sealed partial class AboutSettingsPageViewModel : ViewModelBase
VersionLabel = L("settings.about.version_label", "Version"); VersionLabel = L("settings.about.version_label", "Version");
CodenameLabel = L("settings.about.codename_label", "Codename"); CodenameLabel = L("settings.about.codename_label", "Codename");
RenderBackendLabel = L("settings.about.render_backend_label", "Render Backend"); RenderBackendLabel = L("settings.about.render_backend_label", "Render Backend");
ProjectResourcesHeader = L("settings.about.project_resources_header", "Project resources");
LinkGitHubText = L("settings.about.link_github", "GitHub Repository");
LinkIssuesText = L("settings.about.link_issues", "Issue Tracker");
var year = Math.Max(2025, DateTime.UtcNow.Year);
CopyrightLine = string.Format(
System.Globalization.CultureInfo.InvariantCulture,
L("settings.about.copyright_format", "Copyright (c) 2024-{0} Lincube"),
year);
} }
private string L(string key, string fallback) private string L(string key, string fallback)
@@ -3352,16 +3372,21 @@ public sealed class PluginGeneratedSettingsPageViewModel
public sealed partial class DevSettingsPageViewModel : ViewModelBase public sealed partial class DevSettingsPageViewModel : ViewModelBase
{ {
private readonly ISettingsFacadeService _settingsFacade; private readonly ISettingsFacadeService _settingsFacade;
private readonly LocalizationService _localizationService = new();
private readonly string _languageCode;
private bool _isInitializing; private bool _isInitializing;
public DevSettingsPageViewModel(ISettingsFacadeService settingsFacade) public DevSettingsPageViewModel(ISettingsFacadeService settingsFacade)
{ {
_settingsFacade = settingsFacade; _settingsFacade = settingsFacade;
_languageCode = _localizationService.NormalizeLanguageCode(_settingsFacade.Region.Get().LanguageCode);
RefreshLocalizedText();
_isInitializing = true; _isInitializing = true;
LoadSettings(); LoadSettings();
_isInitializing = false; _isInitializing = false;
// 监听设置变更,防止被意外重置
_settingsFacade.Settings.Changed += OnSettingsChanged; _settingsFacade.Settings.Changed += OnSettingsChanged;
} }
@@ -3377,6 +3402,93 @@ public sealed partial class DevSettingsPageViewModel : ViewModelBase
[ObservableProperty] [ObservableProperty]
private bool _enableFusedDesktop; private bool _enableFusedDesktop;
[ObservableProperty]
private string _infoBarTitle = string.Empty;
[ObservableProperty]
private string _infoBarMessage = string.Empty;
[ObservableProperty]
private string _devModeHeader = string.Empty;
[ObservableProperty]
private string _devModeDescription = string.Empty;
[ObservableProperty]
private string _threeFingerHeader = string.Empty;
[ObservableProperty]
private string _threeFingerDescription = string.Empty;
[ObservableProperty]
private string _fusedHeader = string.Empty;
[ObservableProperty]
private string _fusedDescription = string.Empty;
[ObservableProperty]
private string _pluginPathHeader = string.Empty;
[ObservableProperty]
private string _pluginPathDescription = string.Empty;
[ObservableProperty]
private string _pluginPathPlaceholder = string.Empty;
[ObservableProperty]
private string _startupArgsHeader = string.Empty;
[ObservableProperty]
private string _startupArgsDescription = string.Empty;
[ObservableProperty]
private string _cliLabel = string.Empty;
[ObservableProperty]
private string _envLabel = string.Empty;
[ObservableProperty]
private string _otherArgsLabel = string.Empty;
[ObservableProperty]
private string _cliExample = string.Empty;
[ObservableProperty]
private string _envExample = string.Empty;
[ObservableProperty]
private string _otherDevModeLine = string.Empty;
[ObservableProperty]
private string _otherHotReloadLine = string.Empty;
private void RefreshLocalizedText()
{
InfoBarTitle = L("settings.dev.infobar.title", "Preview and developer features");
InfoBarMessage = L("settings.dev.infobar.message", "These options are intended for debugging and local plugin development.");
DevModeHeader = L("settings.dev.mode_header", "Developer mode");
DevModeDescription = L("settings.dev.mode_description", "Enable developer-focused startup helpers and diagnostics.");
ThreeFingerHeader = L("settings.dev.three_finger_header", "Three-finger desktop swipe");
ThreeFingerDescription = L("settings.dev.three_finger_description", "Enable desktop page switching gestures when supported.");
FusedHeader = L("settings.dev.fused_header", "Fused desktop experience");
FusedDescription = L("settings.dev.fused_description", "Enable the fused desktop shell and experimental entry points.");
PluginPathHeader = L("settings.dev.plugin_path_header", "Development plugin path");
PluginPathDescription = L("settings.dev.plugin_path_description", "Load a local plugin output directory without packaging.");
PluginPathPlaceholder = L("settings.dev.plugin_path_placeholder", "e.g. C:\\path\\to\\plugin\\bin\\Debug\\net10.0");
StartupArgsHeader = L("settings.dev.startup_args_header", "Developer startup arguments");
StartupArgsDescription = L("settings.dev.startup_args_description", "Command-line arguments and environment variables for development.");
CliLabel = L("settings.dev.cli_label", "Command-line arguments:");
EnvLabel = L("settings.dev.env_label", "Environment variables:");
OtherArgsLabel = L("settings.dev.other_args_label", "Other arguments:");
CliExample = L("settings.dev.cli_example", "--dev-plugin <path> or -dp <path>");
EnvExample = L("settings.dev.env_example", "LMD_DEV_PLUGIN=<path>");
OtherDevModeLine = L("settings.dev.other_dev_mode", "--dev-mode / -dev Enable developer mode startup helpers.");
OtherHotReloadLine = L("settings.dev.other_hot_reload", "--hot-reload / -hr Enable hot reload for development builds.");
}
private string L(string key, string fallback)
=> _localizationService.GetString(_languageCode, key, fallback);
partial void OnIsDevModeEnabledChanged(bool value) partial void OnIsDevModeEnabledChanged(bool value)
{ {
if (_isInitializing) return; if (_isInitializing) return;
@@ -3423,7 +3535,6 @@ public sealed partial class DevSettingsPageViewModel : ViewModelBase
return; return;
} }
// 如果是其他设置变更,重新加载我们的设置
_isInitializing = true; _isInitializing = true;
try try
{ {

View File

@@ -130,6 +130,9 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase
[ObservableProperty] [ObservableProperty]
private string _textCapsuleContentLabel = string.Empty; private string _textCapsuleContentLabel = string.Empty;
[ObservableProperty]
private string _textCapsulePlaceholder = string.Empty;
[ObservableProperty] [ObservableProperty]
private string _textCapsuleTransparentBackgroundLabel = string.Empty; private string _textCapsuleTransparentBackgroundLabel = string.Empty;
@@ -600,6 +603,7 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase
TextCapsuleDescription = L("settings.status_bar.text_capsule_description", "Display custom text with Markdown support on the status bar."); TextCapsuleDescription = L("settings.status_bar.text_capsule_description", "Display custom text with Markdown support on the status bar.");
TextCapsulePositionLabel = L("settings.status_bar.text_capsule_position_label", "Text capsule position"); TextCapsulePositionLabel = L("settings.status_bar.text_capsule_position_label", "Text capsule position");
TextCapsuleContentLabel = L("settings.status_bar.text_capsule_content_label", "Text content (Markdown supported)"); TextCapsuleContentLabel = L("settings.status_bar.text_capsule_content_label", "Text content (Markdown supported)");
TextCapsulePlaceholder = L("settings.status_bar.text_capsule_placeholder", "Enter Markdown text…");
TextCapsuleTransparentBackgroundLabel = L("settings.status_bar.text_capsule_transparent_background_label", "Transparent background"); TextCapsuleTransparentBackgroundLabel = L("settings.status_bar.text_capsule_transparent_background_label", "Transparent background");
NetworkSpeedHeader = L("settings.status_bar.network_speed_header", "Network Speed"); NetworkSpeedHeader = L("settings.status_bar.network_speed_header", "Network Speed");
NetworkSpeedDescription = L("settings.status_bar.network_speed_description", "Display real-time network upload and download speed."); NetworkSpeedDescription = L("settings.status_bar.network_speed_description", "Display real-time network upload and download speed.");

View File

@@ -81,7 +81,7 @@
</ui:FAInfoBar.IconSource> </ui:FAInfoBar.IconSource>
</ui:FAInfoBar> </ui:FAInfoBar>
<ui:FASettingsExpander Header="Project resources" <ui:FASettingsExpander Header="{Binding ProjectResourcesHeader}"
IsExpanded="True"> IsExpanded="True">
<ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF02AC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> <ui:FAFontIconSource Glyph="&#xF02AC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
@@ -96,16 +96,14 @@
</Style> </Style>
</WrapPanel.Styles> </WrapPanel.Styles>
<HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop"> <HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop">
<TextBlock Text="GitHub Repository" /> <TextBlock Text="{Binding LinkGitHubText}" />
</HyperlinkButton> </HyperlinkButton>
<HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop/issues"> <HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop/issues">
<TextBlock Text="Issue Tracker" /> <TextBlock Text="{Binding LinkIssuesText}" />
</HyperlinkButton> </HyperlinkButton>
</WrapPanel> </WrapPanel>
</ui:FASettingsExpanderItem.Footer> </ui:FASettingsExpanderItem.Footer>
<TextBlock> <TextBlock Text="{Binding CopyrightLine}" />
<Run Text="Copyright (c) 2024-" /><Run Text="2025" /> Lincube
</TextBlock>
</ui:FASettingsExpanderItem> </ui:FASettingsExpanderItem>
</ui:FASettingsExpander> </ui:FASettingsExpander>
</StackPanel> </StackPanel>

View File

@@ -10,16 +10,16 @@
<ui:FAInfoBar IsOpen="True" <ui:FAInfoBar IsOpen="True"
IsClosable="False" IsClosable="False"
Severity="Warning" Severity="Warning"
Title="Preview and developer features" Title="{Binding InfoBarTitle}"
Message="These options are intended for debugging, diagnostics, and local plugin development." Message="{Binding InfoBarMessage}"
Margin="0,0,0,16"> Margin="0,0,0,16">
<ui:FAInfoBar.IconSource> <ui:FAInfoBar.IconSource>
<ui:FAFontIconSource Glyph="&#xF0288;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> <ui:FAFontIconSource Glyph="&#xF0288;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FAInfoBar.IconSource> </ui:FAInfoBar.IconSource>
</ui:FAInfoBar> </ui:FAInfoBar>
<ui:FASettingsExpander Header="Developer mode" <ui:FASettingsExpander Header="{Binding DevModeHeader}"
Description="Enable developer-focused startup helpers and diagnostics."> Description="{Binding DevModeDescription}">
<ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF01AC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> <ui:FAFontIconSource Glyph="&#xF01AC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource> </ui:FASettingsExpander.IconSource>
@@ -28,8 +28,8 @@
</ui:FASettingsExpander.Footer> </ui:FASettingsExpander.Footer>
</ui:FASettingsExpander> </ui:FASettingsExpander>
<ui:FASettingsExpander Header="Three-finger desktop swipe" <ui:FASettingsExpander Header="{Binding ThreeFingerHeader}"
Description="Enable desktop page switching gestures when the current platform supports them."> Description="{Binding ThreeFingerDescription}">
<ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF02FC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> <ui:FAFontIconSource Glyph="&#xF02FC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource> </ui:FASettingsExpander.IconSource>
@@ -38,8 +38,8 @@
</ui:FASettingsExpander.Footer> </ui:FASettingsExpander.Footer>
</ui:FASettingsExpander> </ui:FASettingsExpander>
<ui:FASettingsExpander Header="Fused desktop experience" <ui:FASettingsExpander Header="{Binding FusedHeader}"
Description="Enable the fused desktop shell and its related experimental entry points."> Description="{Binding FusedDescription}">
<ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF01A8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> <ui:FAFontIconSource Glyph="&#xF01A8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource> </ui:FASettingsExpander.IconSource>
@@ -50,14 +50,14 @@
<Separator Classes="settings-separator" /> <Separator Classes="settings-separator" />
<ui:FASettingsExpander Header="Development plugin path" <ui:FASettingsExpander Header="{Binding PluginPathHeader}"
Description="Load a local plugin output directory for iterative debugging without packaging."> Description="{Binding PluginPathDescription}">
<ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF023C;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> <ui:FAFontIconSource Glyph="&#xF023C;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource> </ui:FASettingsExpander.IconSource>
<ui:FASettingsExpanderItem> <ui:FASettingsExpanderItem>
<TextBox Text="{Binding DevPluginPath}" <TextBox Text="{Binding DevPluginPath}"
PlaceholderText="C:\path\to\plugin\bin\Debug\net10.0" Watermark="{Binding PluginPathPlaceholder}"
Width="360" Width="360"
MinWidth="200" /> MinWidth="200" />
</ui:FASettingsExpanderItem> </ui:FASettingsExpanderItem>
@@ -65,26 +65,26 @@
<Separator Classes="settings-separator" /> <Separator Classes="settings-separator" />
<ui:FASettingsExpander Header="Developer startup arguments" <ui:FASettingsExpander Header="{Binding StartupArgsHeader}"
Description="Use these launch arguments or environment variables to start the app in development scenarios."> Description="{Binding StartupArgsDescription}">
<ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF1930;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> <ui:FAFontIconSource Glyph="&#xF1930;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource> </ui:FASettingsExpander.IconSource>
<ui:FASettingsExpanderItem> <ui:FASettingsExpanderItem>
<StackPanel Margin="0,8,0,0" <StackPanel Margin="0,8,0,0"
Spacing="8"> Spacing="8">
<TextBlock Text="Command-line arguments:" <TextBlock Text="{Binding CliLabel}"
FontWeight="SemiBold" /> FontWeight="SemiBold" />
<Border Background="{DynamicResource ControlFillColorDefaultBrush}" <Border Background="{DynamicResource ControlFillColorDefaultBrush}"
CornerRadius="8" CornerRadius="8"
Padding="12,8"> Padding="12,8">
<TextBlock FontFamily="Cascadia Code, Consolas, monospace" <TextBlock FontFamily="Cascadia Code, Consolas, monospace"
FontSize="12" FontSize="12"
Text="--dev-plugin &lt;path&gt; or -dp &lt;path&gt;" Text="{Binding CliExample}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</Border> </Border>
<TextBlock Text="Environment variables:" <TextBlock Text="{Binding EnvLabel}"
FontWeight="SemiBold" FontWeight="SemiBold"
Margin="0,8,0,0" /> Margin="0,8,0,0" />
<Border Background="{DynamicResource ControlFillColorDefaultBrush}" <Border Background="{DynamicResource ControlFillColorDefaultBrush}"
@@ -92,11 +92,11 @@
Padding="12,8"> Padding="12,8">
<TextBlock FontFamily="Cascadia Code, Consolas, monospace" <TextBlock FontFamily="Cascadia Code, Consolas, monospace"
FontSize="12" FontSize="12"
Text="LMD_DEV_PLUGIN=&lt;path&gt;" Text="{Binding EnvExample}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</Border> </Border>
<TextBlock Text="Other arguments:" <TextBlock Text="{Binding OtherArgsLabel}"
FontWeight="SemiBold" FontWeight="SemiBold"
Margin="0,8,0,0" /> Margin="0,8,0,0" />
<Border Background="{DynamicResource ControlFillColorDefaultBrush}" <Border Background="{DynamicResource ControlFillColorDefaultBrush}"
@@ -105,10 +105,10 @@
<StackPanel Spacing="4"> <StackPanel Spacing="4">
<TextBlock FontFamily="Cascadia Code, Consolas, monospace" <TextBlock FontFamily="Cascadia Code, Consolas, monospace"
FontSize="12" FontSize="12"
Text="--dev-mode / -dev Enable developer mode startup helpers." /> Text="{Binding OtherDevModeLine}" />
<TextBlock FontFamily="Cascadia Code, Consolas, monospace" <TextBlock FontFamily="Cascadia Code, Consolas, monospace"
FontSize="12" FontSize="12"
Text="--hot-reload / -hr Enable hot reload for development builds." /> Text="{Binding OtherHotReloadLine}" />
</StackPanel> </StackPanel>
</Border> </Border>
</StackPanel> </StackPanel>

View File

@@ -6,7 +6,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
[SettingsPageInfo( [SettingsPageInfo(
"notifications", "notifications",
"通知", "Notifications",
SettingsPageCategory.Components, SettingsPageCategory.Components,
IconKey = "Bell", IconKey = "Bell",
SortOrder = 5, SortOrder = 5,

View File

@@ -110,7 +110,7 @@
Height="100" Height="100"
IsEnabled="{Binding ShowTextCapsule}" IsEnabled="{Binding ShowTextCapsule}"
Text="{Binding TextCapsuleContent}" Text="{Binding TextCapsuleContent}"
PlaceholderText="Enter Markdown text..." /> Watermark="{Binding TextCapsulePlaceholder}" />
</Grid> </Grid>
</ui:FASettingsExpanderItem> </ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem> <ui:FASettingsExpanderItem>