V1.2 新构架

This commit is contained in:
Aigio Liu
2021-02-20 16:45:44 +08:00
parent c2d95a1833
commit 4d76decf73
343 changed files with 41333 additions and 4 deletions
@@ -0,0 +1,133 @@
using System.Threading.Tasks;
using static System.Application.Services.IMainThreadPlatformService;
namespace System.Application
{
/// <summary>
/// 适用于桌面端的主线程帮助类,参考 Xamarin.Essentials.MainThread
/// <para><see cref="https://github.com/xamarin/Essentials/blob/main/Xamarin.Essentials/MainThread/MainThread.shared.cs"/></para>
/// </summary>
public static class MainThreadDesktop
{
public static bool IsMainThread => Instance.PlatformIsMainThread;
public static void BeginInvokeOnMainThread(Action action)
{
if (IsMainThread)
{
action();
}
else
{
Instance.PlatformBeginInvokeOnMainThread(action);
}
}
public static Task InvokeOnMainThreadAsync(Action action)
{
if (IsMainThread)
{
action();
#if NETSTANDARD1_0
return Task.FromResult(true);
#else
return Task.CompletedTask;
#endif
}
var tcs = new TaskCompletionSource<bool>();
BeginInvokeOnMainThread(() =>
{
try
{
action();
tcs.TrySetResult(true);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
});
return tcs.Task;
}
public static Task<T> InvokeOnMainThreadAsync<T>(Func<T> func)
{
if (IsMainThread)
{
return Task.FromResult(func());
}
var tcs = new TaskCompletionSource<T>();
BeginInvokeOnMainThread(() =>
{
try
{
var result = func();
tcs.TrySetResult(result);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
});
return tcs.Task;
}
public static Task InvokeOnMainThreadAsync(Func<Task> funcTask)
{
if (IsMainThread)
{
return funcTask();
}
var tcs = new TaskCompletionSource<object>();
BeginInvokeOnMainThread(
async () =>
{
try
{
await funcTask().ConfigureAwait(false);
tcs.SetResult(null);
}
catch (Exception e)
{
tcs.SetException(e);
}
});
return tcs.Task;
}
public static Task<T> InvokeOnMainThreadAsync<T>(Func<Task<T>> funcTask)
{
if (IsMainThread)
{
return funcTask();
}
var tcs = new TaskCompletionSource<T>();
BeginInvokeOnMainThread(
async () =>
{
try
{
var ret = await funcTask().ConfigureAwait(false);
tcs.SetResult(ret);
}
catch (Exception e)
{
tcs.SetException(e);
}
});
return tcs.Task;
}
}
}
@@ -0,0 +1,45 @@
using System.Application.Services;
using System.Application.Services.Implementation;
using System.Application.UI.ViewModels;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionExtensions
{
/// <summary>
/// 添加本地化、多语言服务
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddLocalizationService(this IServiceCollection services)
{
services.AddSingleton<ILocalizationService, LocalizationServiceImpl>();
return services;
}
/// <summary>
/// 添加视图模型组服务
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddViewModelCollectionService(this IServiceCollection services)
{
services.AddSingleton(new ViewModelCollectionServiceImpl(services));
services.AddSingleton<IViewModelCollectionService>(s => s.GetRequiredService<ViewModelCollectionServiceImpl>());
return services;
}
/// <summary>
/// 添加视图模型
/// </summary>
/// <typeparam name="TViewModel"></typeparam>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddViewModel<TViewModel>(this IServiceCollection services) where TViewModel : ViewModelBase
{
services.AddSingleton<TViewModel>();
return services;
}
}
}
@@ -0,0 +1,39 @@
using System.Application.UI.ViewModels;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace System.Application.Services
{
/// <summary>
/// 本地化、多语言服务
/// </summary>
public interface ILocalizationService
{
CultureInfo DefaultCurrentUICulture { get; }
/// <summary>
/// 更改语言
/// </summary>
/// <param name="cultureName"></param>
void ChangeLanguage(string cultureName);
/// <summary>
/// 绑定资源字符串的视图模型组
/// </summary>
ILocalizationViewModel[] ViewModels { get; }
public static IList<KeyValuePair<string, string>> Languages;
static ILocalizationService()
{
Languages = new Dictionary<string, string>()
{
{ "", "Auto" },
{ "en", "English" },
{ "zh-Hans", "Chinese(Simplified)" },
{ "zh-Hant", "Chinese(Traditional)" },
}.ToList();
}
}
}
@@ -0,0 +1,14 @@
namespace System.Application.Services
{
/// <summary>
/// 由平台实现的主线程帮助类
/// </summary>
public interface IMainThreadPlatformService
{
bool PlatformIsMainThread { get; }
void PlatformBeginInvokeOnMainThread(Action action);
public static IMainThreadPlatformService Instance => DI.Get<IMainThreadPlatformService>();
}
}
@@ -0,0 +1,23 @@
using System.Application.UI.ViewModels;
using System.Collections.Generic;
namespace System.Application.Services
{
/// <summary>
/// 视图模型组服务
/// </summary>
public interface IViewModelCollectionService
{
/// <summary>
/// 获取所有的视图模型
/// </summary>
IEnumerable<ViewModelBase> ViewModels { get; }
/// <summary>
/// 根据类型过滤获取视图模型
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
IEnumerable<T> GetViewModels<T>();
}
}
@@ -0,0 +1,64 @@
using System.Application.UI.ViewModels;
using System.Globalization;
using System.Linq;
namespace System.Application.Services.Implementation
{
internal sealed class LocalizationServiceImpl : ILocalizationService
{
readonly IViewModelCollectionService vmss;
public LocalizationServiceImpl(IViewModelCollectionService vmss)
{
this.vmss = vmss;
DefaultCurrentUICulture = CultureInfo.CurrentUICulture;
}
public CultureInfo DefaultCurrentUICulture { get; }
ILocalizationViewModel[]? viewModels;
public ILocalizationViewModel[] ViewModels
{
get
{
if (viewModels == null)
{
viewModels = vmss.GetViewModels<ILocalizationViewModel>().ToArray();
}
return viewModels;
}
}
static bool IsMatch(CultureInfo cultureInfo, string cultureName)
{
if (string.IsNullOrWhiteSpace(cultureInfo.Name))
{
return false;
}
if (cultureInfo.Name == cultureName)
{
return true;
}
else
{
return IsMatch(cultureInfo.Parent, cultureName);
}
}
public void ChangeLanguage(string cultureName)
{
if (IsMatch(CultureInfo.CurrentUICulture, cultureName)) return;
if (!ViewModels.Any()) return;
void ChangeLanguage()
{
CultureInfo.CurrentUICulture = new CultureInfo(cultureName);
foreach (var viewModel in ViewModels)
{
viewModel.OnChangeLanguage();
}
}
MainThreadDesktop.BeginInvokeOnMainThread(ChangeLanguage);
}
}
}
@@ -0,0 +1,32 @@
using Microsoft.Extensions.DependencyInjection;
using System.Application.UI.ViewModels;
using System.Collections.Generic;
using System.Linq;
namespace System.Application.Services.Implementation
{
internal sealed class ViewModelCollectionServiceImpl : IViewModelCollectionService
{
readonly IEnumerable<ServiceDescriptor> services;
public ViewModelCollectionServiceImpl(IEnumerable<ServiceDescriptor> services)
{
this.services = services;
}
public IEnumerable<ViewModelBase> ViewModels => GetViewModels<ViewModelBase>();
public IEnumerable<T> GetViewModels<T>()
{
var tType = typeof(T);
var vType = typeof(ViewModelBase);
var tIsvType = tType == vType;
return from s in services
where s.ServiceType == s.ImplementationType &&
s.Lifetime == ServiceLifetime.Singleton &&
tType.IsAssignableFrom(s.ImplementationType) &&
(tIsvType || vType.IsAssignableFrom(s.ImplementationType))
select (T)DI.Get(s.ServiceType);
}
}
}
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<SignAssembly>true</SignAssembly>
<DelaySign>false</DelaySign>
<AssemblyOriginatorKeyFile>..\Unicorn.pfx</AssemblyOriginatorKeyFile>
<RootNamespace>System.Application</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\System.Common.CoreLib\Properties\AssemblyInfo.cs">
<Link>Properties\AssemblyInfo.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="ReactiveUI" Version="13.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\System.Application.SteamTools.Client\System.Application.SteamTools.Client.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="UI\Resx\AppResources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>AppResources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="UI\Resx\AppResources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
@@ -0,0 +1,162 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace System.Application.UI.Resx {
using System;
/// <summary>
/// 一个强类型的资源类,用于查找本地化的字符串等。
/// </summary>
// 此类是由 StronglyTypedResourceBuilder
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class AppResources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal AppResources() {
}
/// <summary>
/// 返回此类使用的缓存的 ResourceManager 实例。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Application.UI.Resx.AppResources", typeof(AppResources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// 重写当前线程的 CurrentUICulture 属性,对
/// 使用此强类型资源类的所有资源查找执行重写。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// 查找类似 关于 的本地化字符串。
/// </summary>
public static string About {
get {
return ResourceManager.GetString("About", resourceCulture);
}
}
/// <summary>
/// 查找类似 社区加速 的本地化字符串。
/// </summary>
public static string CommunityFix {
get {
return ResourceManager.GetString("CommunityFix", resourceCulture);
}
}
/// <summary>
/// 查找类似 库存游戏 的本地化字符串。
/// </summary>
public static string GameList {
get {
return ResourceManager.GetString("GameList", resourceCulture);
}
}
/// <summary>
/// 查找类似 游戏功能 的本地化字符串。
/// </summary>
public static string GameRelated {
get {
return ResourceManager.GetString("GameRelated", resourceCulture);
}
}
/// <summary>
/// 查找类似 语言 的本地化字符串。
/// </summary>
public static string Language {
get {
return ResourceManager.GetString("Language", resourceCulture);
}
}
/// <summary>
/// 查找类似 设置 的本地化字符串。
/// </summary>
public static string Settings {
get {
return ResourceManager.GetString("Settings", resourceCulture);
}
}
/// <summary>
/// 查找类似 令牌设置 的本地化字符串。
/// </summary>
public static string Settings_Auth {
get {
return ResourceManager.GetString("Settings_Auth", resourceCulture);
}
}
/// <summary>
/// 查找类似 基本设置 的本地化字符串。
/// </summary>
public static string Settings_General {
get {
return ResourceManager.GetString("Settings_General", resourceCulture);
}
}
/// <summary>
/// 查找类似 本地令牌 的本地化字符串。
/// </summary>
public static string SteamAuth {
get {
return ResourceManager.GetString("SteamAuth", resourceCulture);
}
}
/// <summary>
/// 查找类似 账号切换 的本地化字符串。
/// </summary>
public static string UserFastChange {
get {
return ResourceManager.GetString("UserFastChange", resourceCulture);
}
}
/// <summary>
/// 查找类似 起始页 的本地化字符串。
/// </summary>
public static string Welcome {
get {
return ResourceManager.GetString("Welcome", resourceCulture);
}
}
}
}
@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="About" xml:space="preserve">
<value>About</value>
</data>
<data name="CommunityFix" xml:space="preserve">
<value>Fix Steam 302 Error</value>
</data>
<data name="GameList" xml:space="preserve">
<value>My Games</value>
</data>
<data name="GameRelated" xml:space="preserve">
<value>Game Related</value>
</data>
<data name="Language" xml:space="preserve">
<value>Language</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="Settings_Auth" xml:space="preserve">
<value>Auth Settings</value>
</data>
<data name="Settings_General" xml:space="preserve">
<value>General Settings</value>
</data>
<data name="SteamAuth" xml:space="preserve">
<value>Steam Auth</value>
</data>
<data name="UserFastChange" xml:space="preserve">
<value>Account Change</value>
</data>
<data name="Welcome" xml:space="preserve">
<value>Start Page</value>
</data>
</root>
@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="About" xml:space="preserve">
<value>关于</value>
</data>
<data name="CommunityFix" xml:space="preserve">
<value>社区加速</value>
</data>
<data name="GameList" xml:space="preserve">
<value>库存游戏</value>
</data>
<data name="GameRelated" xml:space="preserve">
<value>游戏功能</value>
</data>
<data name="Language" xml:space="preserve">
<value>语言</value>
</data>
<data name="Settings" xml:space="preserve">
<value>设置</value>
</data>
<data name="Settings_Auth" xml:space="preserve">
<value>令牌设置</value>
</data>
<data name="Settings_General" xml:space="preserve">
<value>基本设置</value>
</data>
<data name="SteamAuth" xml:space="preserve">
<value>本地令牌</value>
</data>
<data name="UserFastChange" xml:space="preserve">
<value>账号切换</value>
</data>
<data name="Welcome" xml:space="preserve">
<value>起始页</value>
</data>
</root>
@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="About" xml:space="preserve">
<value>關於</value>
</data>
<data name="CommunityFix" xml:space="preserve">
<value>社區加速</value>
</data>
<data name="GameList" xml:space="preserve">
<value>遊戲列表</value>
</data>
<data name="GameRelated" xml:space="preserve">
<value>遊戲功能</value>
</data>
<data name="Language" xml:space="preserve">
<value>語言</value>
</data>
<data name="Settings" xml:space="preserve">
<value>設定</value>
</data>
<data name="Settings_Auth" xml:space="preserve">
<value>令牌設定</value>
</data>
<data name="Settings_General" xml:space="preserve">
<value>基本設定</value>
</data>
<data name="SteamAuth" xml:space="preserve">
<value>本地令牌</value>
</data>
<data name="UserFastChange" xml:space="preserve">
<value>帳號切換</value>
</data>
<data name="Welcome" xml:space="preserve">
<value>起始頁</value>
</data>
</root>
@@ -0,0 +1,27 @@
using System.Application.UI.Resx;
using System.Linq;
using System.Reflection;
namespace System.Application.UI.ViewModels
{
/// <summary>
/// 本地化、多语言视图模型
/// </summary>
public interface ILocalizationViewModel
{
public void OnChangeLanguage()
{
var properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
var query = from p in properties
let attr = p.GetCustomAttribute<ResStringAttribute>()
where p.PropertyType == typeof(string) && attr != null
select (p, attr.ResId);
foreach (var (p, ResId) in query)
{
var resId = ResId ?? p.Name;
var value = AppResources.ResourceManager.GetString(resId);
p.SetValue(this, value);
}
}
}
}
@@ -0,0 +1,13 @@
namespace System.Application.UI.ViewModels
{
[AttributeUsage(AttributeTargets.Property)]
public sealed class ResStringAttribute : Attribute
{
public string? ResId { get; }
public ResStringAttribute(string? resId = null)
{
ResId = resId;
}
}
}
@@ -0,0 +1,24 @@
using ReactiveUI;
namespace System.Application.UI.ViewModels
{
public class ViewModelBase : ReactiveObject, ILocalizationViewModel
{
public ViewModelBase()
{
ILocalizationViewModel localization = this;
localization.OnChangeLanguage();
}
/// <summary>
/// 是否在设计器的上下文中运行
/// </summary>
public static bool IsInDesignMode { get; set; }
protected static TViewModel GetViewModel<TViewModel>() where TViewModel : ViewModelBase, new()
{
if (IsInDesignMode) return new TViewModel();
return DI.Get<TViewModel>();
}
}
}
@@ -0,0 +1,15 @@
using ReactiveUI;
namespace System.Application.UI.ViewModels
{
public class WindowViewModel : ViewModelBase
{
string title = string.Empty;
public string Title
{
get => title;
set => this.RaiseAndSetIfChanged(ref title, value);
}
}
}