增加语言设置

This commit is contained in:
ww-rm
2025-06-14 01:57:39 +08:00
parent b3010360b4
commit 7ec938b415
14 changed files with 265 additions and 137 deletions

View File

@@ -21,7 +21,6 @@ namespace Spine
{
[".skel"] = ".atlas",
[".json"] = ".atlas",
[".skel.bytes"] = ".atlas.bytes",
}.ToFrozenDictionary();
/// <summary>

View File

@@ -11,7 +11,7 @@
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml"/>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
<ResourceDictionary Source="/Resources/Geometries.xaml"/>
<ResourceDictionary Source="/Resources/Strings/zh-cn.xaml"/>
<ResourceDictionary Source="/Resources/Strings/zh.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style x:Key="MyToggleButton" TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource ToggleButtonSwitch}">

View File

@@ -1,5 +1,6 @@
using NLog;
using SpineViewer.Views;
using System.Collections.Frozen;
using System.Configuration;
using System.Data;
using System.Diagnostics;
@@ -7,8 +8,8 @@ using System.Globalization;
using System.Reflection;
using System.Windows;
namespace SpineViewer;
namespace SpineViewer
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
@@ -66,18 +67,9 @@ public partial class App : Application
var uiCulture = CultureInfo.CurrentUICulture.Name.ToLowerInvariant();
_logger.Info("Current UI Culture: {0}", uiCulture);
if (uiCulture.StartsWith("zh"))
{
; // 默认就是中文, 无需操作
}
else if (uiCulture.StartsWith("ja"))
{
dict.Source = new("Resources/Strings/ja-jp.xaml", UriKind.Relative);
}
else
{
dict.Source = new("Resources/Strings/en-us.xaml", UriKind.Relative);
}
if (uiCulture.StartsWith("zh")) { } // 默认就是中文, 无需操作
else if (uiCulture.StartsWith("ja")) Language = AppLanguage.JA;
else Language = AppLanguage.EN;
Resources.MergedDictionaries.Add(dict);
}
@@ -88,5 +80,36 @@ public partial class App : Application
_logger.Error("Dispatcher unhandled exception: {0}", e.Exception.Message);
e.Handled = true;
}
/// <summary>
/// 程序语言
/// </summary>
public AppLanguage Language
{
get => _language;
set
{
var uri = $"Resources/Strings/{value.ToString().ToLower()}.xaml";
try
{
Resources.MergedDictionaries.Add(new() { Source = new(uri, UriKind.Relative) });
_language = value;
}
catch (Exception ex)
{
_logger.Error("Failed to switch language to {0}, {1}", value, ex.Message);
_logger.Trace(ex.ToString());
}
}
}
private AppLanguage _language = AppLanguage.ZH;
}
public enum AppLanguage
{
ZH,
EN,
JA
}
}

View File

@@ -17,6 +17,7 @@ namespace SpineViewer.Models
/// </summary>
public partial class PreferenceModel : ObservableObject
{
#region
[ObservableProperty]
@@ -67,6 +68,13 @@ namespace SpineViewer.Models
#endregion
#region
[ObservableProperty]
private AppLanguage _appLanguage;
#endregion
#region
/// <summary>

View File

@@ -205,4 +205,7 @@
<s:String x:Key="Str_SpineLoadPreference">Model Loading Options</s:String>
<s:String x:Key="Str_AppPreference">Application Options</s:String>
<s:String x:Key="Str_Language">Language</s:String>
</ResourceDictionary>

View File

@@ -205,5 +205,8 @@
<s:String x:Key="Str_SpineLoadPreference">モデル読み込みオプション</s:String>
<s:String x:Key="Str_AppPreference">アプリケーションプション</s:String>
<s:String x:Key="Str_Language">言語</s:String>
</ResourceDictionary>

View File

@@ -205,4 +205,7 @@
<s:String x:Key="Str_SpineLoadPreference">模型加载选项</s:String>
<s:String x:Key="Str_AppPreference">应用程序选项</s:String>
<s:String x:Key="Str_Language">语言</s:String>
</ResourceDictionary>

View File

@@ -18,7 +18,7 @@ namespace SpineViewer.ViewModels.Exporters
{
public class FFmpegVideoExporterViewModel(MainWindowViewModel vmMain) : VideoExporterViewModel(vmMain)
{
public ImmutableArray<FFmpegVideoExporter.VideoFormat> VideoFormats { get; } = Enum.GetValues<FFmpegVideoExporter.VideoFormat>().ToImmutableArray();
public ImmutableArray<FFmpegVideoExporter.VideoFormat> VideoFormatOptions { get; } = Enum.GetValues<FFmpegVideoExporter.VideoFormat>().ToImmutableArray();
public FFmpegVideoExporter.VideoFormat Format { get => _format; set => SetProperty(ref _format, value); }
protected FFmpegVideoExporter.VideoFormat _format = FFmpegVideoExporter.VideoFormat.Mp4;

View File

@@ -20,7 +20,7 @@ namespace SpineViewer.ViewModels.Exporters
{
public class FrameExporterViewModel(MainWindowViewModel vmMain) : BaseExporterViewModel(vmMain)
{
public ImmutableArray<SKEncodedImageFormat> FrameFormats { get; } = Enum.GetValues<SKEncodedImageFormat>().ToImmutableArray();
public ImmutableArray<SKEncodedImageFormat> FrameFormatOptions { get; } = Enum.GetValues<SKEncodedImageFormat>().ToImmutableArray();
public SKEncodedImageFormat Format { get => _format; set => SetProperty(ref _format, value); }
protected SKEncodedImageFormat _format = SKEncodedImageFormat.Png;

View File

@@ -6,6 +6,7 @@ using SpineViewer.Models;
using SpineViewer.Services;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
@@ -105,7 +106,8 @@ namespace SpineViewer.ViewModels.MainWindow
DebugBoundingBoxes = DebugBoundingBoxes,
DebugPaths = DebugPaths,
DebugPoints = DebugPoints,
DebugClippings = DebugClippings
DebugClippings = DebugClippings,
AppLanguage = AppLanguage,
};
}
set
@@ -124,6 +126,7 @@ namespace SpineViewer.ViewModels.MainWindow
DebugPaths = value.DebugPaths;
DebugPoints = value.DebugPoints;
DebugClippings = value.DebugClippings;
AppLanguage = value.AppLanguage;
}
}
@@ -149,42 +152,88 @@ namespace SpineViewer.ViewModels.MainWindow
#endregion
// TODO: 是否自动记忆模型参数
#region
public bool UsePma { get => _usePma; set => SetProperty(ref _usePma, value); }
private bool _usePma;
// TODO: 是否自动记忆模型参数
public bool DebugTexture { get => _debugTexture; set => SetProperty(ref _debugTexture, value); }
private bool _debugTexture = true;
public bool UsePma
{
get => SpineObjectListViewModel.LoadOptions.UsePma;
set => SetProperty(SpineObjectListViewModel.LoadOptions.UsePma, value, v => SpineObjectListViewModel.LoadOptions.UsePma = v);
}
public bool DebugBounds { get => _debugBounds; set => SetProperty(ref _debugBounds, value); }
private bool _debugBounds;
public bool DebugTexture
{
get => SpineObjectListViewModel.LoadOptions.DebugTexture;
set => SetProperty(SpineObjectListViewModel.LoadOptions.DebugTexture, value, v => SpineObjectListViewModel.LoadOptions.DebugTexture = v);
}
public bool DebugBones { get => _debugBones; set => SetProperty(ref _debugBones, value); }
private bool _debugBones;
public bool DebugBounds
{
get => SpineObjectListViewModel.LoadOptions.DebugBounds;
set => SetProperty(SpineObjectListViewModel.LoadOptions.DebugBounds, value, v => SpineObjectListViewModel.LoadOptions.DebugBounds = v);
}
public bool DebugRegions { get => _debugRegions; set => SetProperty(ref _debugRegions, value); }
private bool _debugRegions;
public bool DebugBones
{
get => SpineObjectListViewModel.LoadOptions.DebugBones;
set => SetProperty(SpineObjectListViewModel.LoadOptions.DebugBones, value, v => SpineObjectListViewModel.LoadOptions.DebugBones = v);
}
public bool DebugMeshHulls { get => _debugMeshHulls; set => SetProperty(ref _debugMeshHulls, value); }
private bool _debugMeshHulls;
public bool DebugRegions
{
get => SpineObjectListViewModel.LoadOptions.DebugRegions;
set => SetProperty(SpineObjectListViewModel.LoadOptions.DebugRegions, value, v => SpineObjectListViewModel.LoadOptions.DebugRegions = v);
}
public bool DebugMeshes { get => _debugMeshes; set => SetProperty(ref _debugMeshes, value); }
private bool _debugMeshes;
public bool DebugMeshHulls
{
get => SpineObjectListViewModel.LoadOptions.DebugMeshHulls;
set => SetProperty(SpineObjectListViewModel.LoadOptions.DebugMeshHulls, value, v => SpineObjectListViewModel.LoadOptions.DebugMeshHulls = v);
}
public bool DebugBoundingBoxes { get => _debugBoundingBoxes; set => SetProperty(ref _debugBoundingBoxes, value); }
private bool _debugBoundingBoxes;
public bool DebugMeshes
{
get => SpineObjectListViewModel.LoadOptions.DebugMeshes;
set => SetProperty(SpineObjectListViewModel.LoadOptions.DebugMeshes, value, v => SpineObjectListViewModel.LoadOptions.DebugMeshes = v);
}
public bool DebugPaths { get => _debugPaths; set => SetProperty(ref _debugPaths, value); }
private bool _debugPaths;
public bool DebugBoundingBoxes
{
get => SpineObjectListViewModel.LoadOptions.DebugBoundingBoxes;
set => SetProperty(SpineObjectListViewModel.LoadOptions.DebugBoundingBoxes, value, v => SpineObjectListViewModel.LoadOptions.DebugBoundingBoxes = v);
}
public bool DebugPoints { get => _debugPoints; set => SetProperty(ref _debugPoints, value); }
private bool _debugPoints;
public bool DebugPaths
{
get => SpineObjectListViewModel.LoadOptions.DebugPaths;
set => SetProperty(SpineObjectListViewModel.LoadOptions.DebugPaths, value, v => SpineObjectListViewModel.LoadOptions.DebugPaths = v);
}
public bool DebugClippings { get => _debugClippings; set => SetProperty(ref _debugClippings, value); }
private bool _debugClippings;
public bool DebugPoints
{
get => SpineObjectListViewModel.LoadOptions.DebugPoints;
set => SetProperty(SpineObjectListViewModel.LoadOptions.DebugPoints, value, v => SpineObjectListViewModel.LoadOptions.DebugPoints = v);
}
public bool DebugClippings
{
get => SpineObjectListViewModel.LoadOptions.DebugClippings;
set => SetProperty(SpineObjectListViewModel.LoadOptions.DebugClippings, value, v => SpineObjectListViewModel.LoadOptions.DebugClippings = v);
}
#endregion
#region
public static ImmutableArray<AppLanguage> AppLanguageOptions { get; } = Enum.GetValues<AppLanguage>().ToImmutableArray();
public AppLanguage AppLanguage
{
get => ((App)App.Current).Language;
set => SetProperty(((App)App.Current).Language, value, v => ((App)App.Current).Language = v);
}
#endregion
}

View File

@@ -21,6 +21,12 @@ namespace SpineViewer.ViewModels.MainWindow
{
public class SpineObjectListViewModel : ObservableObject
{
/// <summary>
/// 加载选项
/// </summary>
public static SpineLoadOptions LoadOptions => _loadOptions;
private static readonly SpineLoadOptions _loadOptions = new();
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
/// <summary>
@@ -249,41 +255,6 @@ namespace SpineViewer.ViewModels.MainWindow
return true;
}
/// <summary>
/// 安全地在末尾添加一个模型, 发生错误会输出日志
/// </summary>
/// <returns>是否添加成功</returns>
public bool AddSpineObject(string skelPath, string? atlasPath = null)
{
try
{
// TODO: 判断是否记忆参数
var pre = _vmMain.PreferenceViewModel;
var sp = new SpineObjectModel(skelPath, atlasPath)
{
UsePma = pre.UsePma,
DebugTexture = pre.DebugTexture,
DebugBounds = pre.DebugBounds,
DebugRegions = pre.DebugRegions,
DebugMeshHulls = pre.DebugMeshHulls,
DebugMeshes = pre.DebugMeshes,
DebugBoundingBoxes = pre.DebugBoundingBoxes,
DebugPaths = pre.DebugPaths,
DebugPoints = pre.DebugPoints,
DebugClippings = pre.DebugClippings
};
lock (_spineObjectModels.Lock) _spineObjectModels.Add(sp);
return true;
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message);
}
return false;
}
/// <summary>
/// 从路径列表添加对象
/// </summary>
@@ -369,5 +340,54 @@ namespace SpineViewer.ViewModels.MainWindow
_logger.LogCurrentProcessMemoryUsage();
}
/// <summary>
/// 安全地在末尾添加一个模型, 发生错误会输出日志
/// </summary>
/// <returns>是否添加成功</returns>
public bool AddSpineObject(string skelPath, string? atlasPath = null)
{
try
{
var sp = new SpineObjectModel(skelPath, atlasPath)
{
UsePma = _loadOptions.UsePma,
DebugTexture = _loadOptions.DebugTexture,
DebugBounds = _loadOptions.DebugBounds,
DebugRegions = _loadOptions.DebugRegions,
DebugMeshHulls = _loadOptions.DebugMeshHulls,
DebugMeshes = _loadOptions.DebugMeshes,
DebugBoundingBoxes = _loadOptions.DebugBoundingBoxes,
DebugPaths = _loadOptions.DebugPaths,
DebugPoints = _loadOptions.DebugPoints,
DebugClippings = _loadOptions.DebugClippings
};
lock (_spineObjectModels.Lock) _spineObjectModels.Add(sp);
return true;
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message);
}
return false;
}
public class SpineLoadOptions
{
// TODO: 判断是否记忆参数
public bool UsePma { get; set; }
public bool DebugTexture { get; set; } = true;
public bool DebugBounds { get; set; }
public bool DebugBones { get; set; }
public bool DebugRegions { get; set; }
public bool DebugMeshHulls { get; set; }
public bool DebugMeshes { get; set; }
public bool DebugBoundingBoxes { get; set; }
public bool DebugPaths { get; set; }
public bool DebugPoints { get; set; }
public bool DebugClippings { get; set; }
}
}
}

View File

@@ -128,7 +128,7 @@
<!-- 视频格式 -->
<Label Grid.Row="13" Grid.Column="0" Content="{DynamicResource Str_VideoFormat}"/>
<ComboBox Grid.Row="13" Grid.Column="1" SelectedItem="{Binding Format}" ItemsSource="{Binding VideoFormats}"/>
<ComboBox Grid.Row="13" Grid.Column="1" SelectedItem="{Binding Format}" ItemsSource="{Binding VideoFormatOptions}"/>
<!-- 动图是否循环 -->
<Label Grid.Row="14" Grid.Column="0" Content="{DynamicResource Str_LoopPlay}" ToolTip="{DynamicResource Str_LoopPlayTooltip}"/>

View File

@@ -110,7 +110,7 @@
<!-- 图像格式 -->
<Label Grid.Row="9" Grid.Column="0" Content="{DynamicResource Str_ImageFormat}"/>
<ComboBox Grid.Row="9" Grid.Column="1" SelectedItem="{Binding Format}" ItemsSource="{Binding FrameFormats}"/>
<ComboBox Grid.Row="9" Grid.Column="1" SelectedItem="{Binding Format}" ItemsSource="{Binding FrameFormatOptions}"/>
<!-- 图像质量 -->
<Label Grid.Row="10" Grid.Column="0" Content="{DynamicResource Str_ImageQuality}" ToolTip="{DynamicResource Str_ImageQualityTooltip}"/>

View File

@@ -1,10 +1,12 @@
<Window xmlns:hc="https://handyorg.github.io/handycontrol" x:Class="SpineViewer.Views.PreferenceDialog"
<Window x:Class="SpineViewer.Views.PreferenceDialog"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SpineViewer.Views"
xmlns:models="clr-namespace:SpineViewer.Models"
xmlns:vm="clr-namespace:SpineViewer.ViewModels.MainWindow"
d:DataContext="{d:DesignInstance Type=models:PreferenceModel}"
mc:Ignorable="d"
Title="{DynamicResource Str_Preference}"
@@ -125,6 +127,24 @@
<ToggleButton Grid.Row="10" Grid.Column="1" IsChecked="{Binding DebugClippings}"/>
</Grid>
</GroupBox>
<GroupBox Header="{DynamicResource Str_AppPreference}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Col1"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_Language}"/>
<ComboBox Grid.Row="0" Grid.Column="1"
SelectedItem="{Binding AppLanguage}"
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>
</Grid>
</GroupBox>
</StackPanel>
</ScrollViewer>
</Border>